darkfi_contract_test_harness/
lib.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 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 std::{
20    collections::HashMap,
21    fs::OpenOptions,
22    io::{Cursor, Write},
23    slice,
24    sync::Arc,
25    time::Instant,
26};
27
28use darkfi::{
29    blockchain::{BlockInfo, Blockchain, BlockchainOverlay},
30    runtime::vm_runtime::{Runtime, TxLocalState},
31    tx::Transaction,
32    util::{
33        logger::{setup_test_logger, Level},
34        pcg::Pcg32,
35        time::Timestamp,
36    },
37    validator::{utils::deploy_native_contracts, Validator, ValidatorConfig, ValidatorPtr},
38    zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
39    zkas::ZkBinary,
40    Result,
41};
42use darkfi_dao_contract::model::{Dao, DaoBulla, DaoProposal, DaoProposalBulla, DaoVoteParams};
43use darkfi_money_contract::{
44    client::{MoneyNote, OwnCoin},
45    model::{
46        CoinAttributes, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, Nullifier, Output,
47        TokenAttributes, TokenId,
48    },
49    MoneyFunction,
50};
51use darkfi_sdk::{
52    bridgetree,
53    crypto::{
54        contract_id::MONEY_CONTRACT_ID,
55        poseidon_hash,
56        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
57        BaseBlind, FuncRef, Keypair, MerkleNode, MerkleTree, ScalarBlind, SecretKey,
58    },
59    pasta::pallas,
60};
61use darkfi_serial::{serialize, Encodable};
62use num_bigint::BigUint;
63use parking_lot::Mutex;
64use rand::rngs::OsRng;
65use sled_overlay::sled;
66use tracing::{debug, warn};
67
68/// Utility module for caching ZK proof PKs and VKs
69pub mod vks;
70
71/// `Money::Burn` functionality
72mod money_burn;
73/// `Money::Fee` functionality
74mod money_fee;
75/// `Money::GenesisMint` functionality
76mod money_genesis_mint;
77/// `Money::OtcSwap` functionality
78mod money_otc_swap;
79/// `Money::PoWReward` functionality
80mod money_pow_reward;
81/// `Money::TokenMint` functionality
82mod money_token;
83/// `Money::Transfer` functionality
84mod money_transfer;
85
86/// `Deployooor::Deploy` functionality
87mod contract_deploy;
88/// `Deployooor::Lock` functionality
89mod contract_lock;
90
91/// `Dao::Exec` functionality
92mod dao_exec;
93/// `Dao::Mint` functionality
94mod dao_mint;
95/// `Dao::Propose` functionality
96mod dao_propose;
97/// `Dao::Vote` functionality
98mod dao_vote;
99
100/// PoW target
101const POW_TARGET: u32 = 120;
102
103/// Initialize the logging mechanism
104pub fn init_logger() {
105    // We check this error so we can execute same-file tests in parallel.
106    // Otherwise subsequent calls fail to init the logger here.
107    if setup_test_logger(
108        &["sled"],
109        false,
110        Level::Info,
111        //Level::Verbose,
112        //Level::Debug,
113        //Level::Trace,
114    )
115    .is_err()
116    {
117        warn!(target: "test-harness", "Logger already initialized");
118    }
119}
120
121/// Enum representing available wallet holders
122#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
123pub enum Holder {
124    Alice,
125    Bob,
126    Charlie,
127    Dao,
128    Rachel,
129}
130
131/// Wallet instance for a single [`Holder`]
132pub struct Wallet {
133    /// Main holder keypair
134    pub keypair: Keypair,
135    /// Keypair for arbitrary token minting
136    pub token_mint_authority: Keypair,
137    /// Keypair for arbitrary contract deployment
138    pub contract_deploy_authority: Keypair,
139    /// Holder's [`Validator`] instance
140    pub validator: ValidatorPtr,
141    /// Holder's instance of the Merkle tree for the `Money` contract
142    pub money_merkle_tree: MerkleTree,
143    /// Holder's instance of the nullifiers SMT tree for the `Money` contract
144    pub money_null_smt: SmtMemoryFp,
145    /// Holder's instance of the nullifiers SMT tree for the `Money` contract
146    /// snapshotted for `DAO::Propose`
147    pub money_null_smt_snapshot: Option<SmtMemoryFp>,
148    /// Holder's instance of the Merkle tree for the `DAO` contract
149    /// holding DAO bullas
150    pub dao_merkle_tree: MerkleTree,
151    /// Holder's instance of the Merkle tree for the `DAO` contract
152    /// holding DAO proposals
153    pub dao_proposals_tree: MerkleTree,
154    /// Holder's set of unspent [`OwnCoin`]s from the `Money` contract
155    pub unspent_money_coins: Vec<OwnCoin>,
156    /// Holder's set of spent [`OwnCoin`]s from the `Money` contract
157    pub spent_money_coins: Vec<OwnCoin>,
158    /// Witnessed leaf positions of DAO bullas in the `dao_merkle_tree`
159    pub dao_leafs: HashMap<DaoBulla, bridgetree::Position>,
160    /// Dao Proposal snapshots
161    pub dao_prop_leafs: HashMap<DaoProposalBulla, (bridgetree::Position, MerkleTree)>,
162    /// Create bench.csv file
163    pub bench_wasm: bool,
164}
165
166impl Wallet {
167    /// Instantiate a new [`Wallet`] instance
168    pub async fn new(
169        keypair: Keypair,
170        token_mint_authority: Keypair,
171        contract_deploy_authority: Keypair,
172        genesis_block: BlockInfo,
173        vks: &vks::Vks,
174        verify_fees: bool,
175    ) -> Result<Self> {
176        // Create an in-memory sled db instance for this wallet
177        let sled_db = sled::Config::new().temporary(true).open()?;
178
179        // Inject the cached VKs into the database
180        let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?;
181        vks::inject(&overlay, vks)?;
182
183        deploy_native_contracts(&overlay, POW_TARGET).await?;
184        let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
185        overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
186        overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
187
188        // Create the `Validator` instance
189        let validator_config = ValidatorConfig {
190            confirmation_threshold: 3,
191            max_forks: 8,
192            pow_target: POW_TARGET,
193            pow_fixed_difficulty: Some(BigUint::from(1_u8)),
194            genesis_block,
195            verify_fees,
196        };
197        let validator = Validator::new(&sled_db, &validator_config).await?;
198
199        // The Merkle tree for the Money contract is initialized with a
200        // "null" leaf at position 0.
201        let mut money_merkle_tree = MerkleTree::new(1);
202        money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO));
203        money_merkle_tree.mark().unwrap();
204
205        let hasher = PoseidonFp::new();
206        let store = MemoryStorageFp::new();
207        let money_null_smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
208
209        Ok(Self {
210            keypair,
211            token_mint_authority,
212            contract_deploy_authority,
213            validator,
214            money_merkle_tree,
215            money_null_smt,
216            money_null_smt_snapshot: None,
217            dao_merkle_tree: MerkleTree::new(1),
218            dao_proposals_tree: MerkleTree::new(1),
219            unspent_money_coins: vec![],
220            spent_money_coins: vec![],
221            dao_leafs: HashMap::new(),
222            dao_prop_leafs: HashMap::new(),
223            bench_wasm: false,
224        })
225    }
226
227    pub async fn add_transaction(
228        &mut self,
229        callname: &str,
230        tx: Transaction,
231        block_height: u32,
232    ) -> Result<()> {
233        if self.bench_wasm {
234            let _ = benchmark_wasm_calls(callname, self.validator.clone(), &tx, block_height).await;
235        }
236
237        let validator = self.validator.read().await;
238        validator
239            .add_test_transactions(
240                slice::from_ref(&tx),
241                block_height,
242                validator.consensus.module.target,
243                true,
244                validator.verify_fees,
245            )
246            .await?;
247
248        // Write the data
249        let blockchain = &validator.blockchain;
250        let txs = &blockchain.transactions;
251        txs.insert(slice::from_ref(&tx)).expect("insert tx");
252        txs.insert_location(&[tx.hash()], block_height).expect("insert loc");
253
254        Ok(())
255    }
256
257    /// Mark a single nullifier as spent in the SMT and move any matching
258    /// OwnCoin from `unspent_money_coins` to `spent_money_coins`
259    pub fn mark_spent_nullifier(&mut self, nullifier: Nullifier, holder: &Holder) {
260        let n = nullifier.inner();
261        self.money_null_smt.insert_batch(vec![(n, n)]).expect("smt.insert_batch()");
262
263        if let Some(spent_coin) =
264            self.unspent_money_coins.iter().find(|x| x.nullifier() == nullifier).cloned()
265        {
266            debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
267            self.unspent_money_coins.retain(|x| x.nullifier() != nullifier);
268            self.spent_money_coins.push(spent_coin);
269        }
270    }
271
272    /// Process a set of [`Input`]s.
273    /// Insert nullifiers into the SMT and move any matching OwnCoins from
274    /// unspent to spent.
275    pub fn process_inputs(&mut self, inputs: &[Input], holder: &Holder) {
276        for input in inputs {
277            self.mark_spent_nullifier(input.nullifier, holder);
278        }
279    }
280
281    /// Process a set of [`Output`]s.
282    /// Append each coin to the Merkle tree and attempt to decrypt the note.
283    /// Returns any new OwnCoins found.
284    pub fn process_outputs(&mut self, outputs: &[Output], holder: &Holder) -> Vec<OwnCoin> {
285        let mut found = vec![];
286
287        for output in outputs {
288            if output.tx_local {
289                continue
290            }
291
292            self.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
293
294            let Ok(note) = output.note.decrypt::<MoneyNote>(&self.keypair.secret) else { continue };
295
296            let owncoin = OwnCoin {
297                coin: output.coin,
298                note: note.clone(),
299                secret: self.keypair.secret,
300                leaf_position: self.money_merkle_tree.mark().unwrap(),
301            };
302
303            debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
304            self.unspent_money_coins.push(owncoin.clone());
305            found.push(owncoin);
306        }
307
308        found
309    }
310
311    /// Process the fee component of a transaction (if present).
312    /// Handles the fee input nullifier and fee change output.
313    /// Returns any new OwnCoins found.
314    pub fn process_fee(
315        &mut self,
316        fee_params: &Option<MoneyFeeParamsV1>,
317        holder: &Holder,
318    ) -> Vec<OwnCoin> {
319        let Some(ref fp) = fee_params else { return vec![] };
320
321        self.mark_spent_nullifier(fp.input.nullifier, holder);
322        self.process_outputs(slice::from_ref(&fp.output), holder)
323    }
324
325    /// Return a reference to the Holder's `Validator` instance
326    pub fn validator(&self) -> &ValidatorPtr {
327        &self.validator
328    }
329}
330
331/// Native contract test harness instance
332pub struct TestHarness {
333    /// Initialized [`Holder`]s for this instance
334    pub holders: HashMap<Holder, Wallet>,
335    /// Ordered list of all holder keys (for broadcast operations)
336    pub holder_keys: Vec<Holder>,
337    /// Cached [`ProvingKey`]s for native contract ZK proving
338    pub proving_keys: HashMap<String, (ProvingKey, ZkBinary)>,
339    /// The genesis block for this harness
340    pub genesis_block: BlockInfo,
341    /// Marker to know if we're supposed to include tx fees
342    pub verify_fees: bool,
343}
344
345impl TestHarness {
346    /// Instantiate a new [`TestHarness`] given a slice of [`Holder`]s.
347    /// Additionally, a `verify_fees` boolean will enforce tx fee verification.
348    pub async fn new(holders: &[Holder], verify_fees: bool) -> Result<Self> {
349        // Create a genesis block
350        let mut genesis_block = BlockInfo::default();
351        genesis_block.header.timestamp = Timestamp::from_u64(1689772567);
352        let producer_tx = genesis_block.txs.pop().unwrap();
353        genesis_block.append_txs(vec![producer_tx]);
354
355        // Deterministic PRNG
356        let mut rng = Pcg32::new(42);
357
358        // Build or read cached ZK PKs and VKs
359        let (pks, vks) = vks::get_cached_pks_and_vks()?;
360        let mut proving_keys = HashMap::new();
361        for (bincode, namespace, pk) in pks {
362            let mut reader = Cursor::new(pk);
363            let zkbin = ZkBinary::decode(&bincode, false)?;
364            let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
365            let proving_key = ProvingKey::read(&mut reader, circuit)?;
366            proving_keys.insert(namespace, (proving_key, zkbin));
367        }
368
369        // Compute genesis contracts states monotree root
370        let sled_db = sled::Config::new().temporary(true).open()?;
371        let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?;
372        vks::inject(&overlay, &vks)?;
373        deploy_native_contracts(&overlay, POW_TARGET).await?;
374        let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
375        genesis_block.header.state_root =
376            overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
377
378        // Create `Wallet` instances
379        let mut holders_map = HashMap::new();
380        let mut holder_keys = Vec::with_capacity(holders.len());
381
382        for holder in holders {
383            let keypair = Keypair::random(&mut rng);
384            let token_mint_authority = Keypair::random(&mut rng);
385            let contract_deploy_authority = Keypair::random(&mut rng);
386
387            let wallet = Wallet::new(
388                keypair,
389                token_mint_authority,
390                contract_deploy_authority,
391                genesis_block.clone(),
392                &vks,
393                verify_fees,
394            )
395            .await?;
396
397            holders_map.insert(*holder, wallet);
398            holder_keys.push(*holder);
399        }
400
401        Ok(Self { holders: holders_map, holder_keys, proving_keys, genesis_block, verify_fees })
402    }
403
404    /// Get a reference to a Holder's Wallet
405    pub fn wallet(&self, holder: &Holder) -> &Wallet {
406        self.holders.get(holder).unwrap()
407    }
408
409    /// Get a mutable reference to a Holder's Wallet
410    pub fn wallet_mut(&mut self, holder: &Holder) -> &mut Wallet {
411        self.holders.get_mut(holder).unwrap()
412    }
413
414    /// Get a Holder's unspent OwnCoins
415    pub fn coins(&self, holder: &Holder) -> &[OwnCoin] {
416        &self.wallet(holder).unspent_money_coins
417    }
418
419    /// Get a Holder's unspent OwnCoins filtered by token ID
420    pub fn coins_by_token(&self, holder: &Holder, token_id: TokenId) -> Vec<OwnCoin> {
421        self.coins(holder).iter().filter(|c| c.note.token_id == token_id).cloned().collect()
422    }
423
424    /// Get the total balance of a Holder for a given token
425    pub fn balance(&self, holder: &Holder, token_id: TokenId) -> u64 {
426        self.coins(holder)
427            .iter()
428            .filter(|c| c.note.token_id == token_id)
429            .map(|c| c.note.value)
430            .sum()
431    }
432
433    /// Assert that all holders' Merkle trees are consistent
434    pub fn assert_trees(&self, holders: &[Holder]) {
435        assert!(!holders.is_empty());
436        let mut wallets = vec![];
437        for holder in holders {
438            wallets.push(self.holders.get(holder).unwrap());
439        }
440
441        let money_root = wallets[0].money_merkle_tree.root(0).unwrap();
442        for wallet in &wallets[1..] {
443            assert_eq!(money_root, wallet.money_merkle_tree.root(0).unwrap());
444        }
445    }
446
447    /// Assert all registered holders' Merkle trees are consistent
448    pub fn assert_all_trees(&self) {
449        if !self.holder_keys.is_empty() {
450            self.assert_trees(&self.holder_keys);
451        }
452    }
453
454    /// Mint a token for `recipient` and execute the tx on all registered
455    /// holders.
456    /// Returns the minted `TokenId`
457    pub async fn token_mint_to_all(
458        &mut self,
459        amount: u64,
460        holder: &Holder,
461        recipient: &Holder,
462        block_height: u32,
463    ) -> Result<TokenId> {
464        let token_blind = BaseBlind::random(&mut OsRng);
465        let (tx, mint_params, auth_params, fee_params) = self
466            .token_mint(amount, holder, recipient, token_blind, None, None, block_height)
467            .await?;
468
469        // Derive the Token ID
470        let token_id = self.derive_token_id(recipient, token_blind);
471
472        let holders = self.holder_keys.clone();
473        for h in &holders {
474            self.execute_token_mint_tx(
475                h,
476                tx.clone(),
477                &mint_params,
478                &auth_params,
479                &fee_params,
480                block_height,
481                true,
482            )
483            .await?;
484        }
485
486        self.assert_all_trees();
487
488        Ok(token_id)
489    }
490
491    /// Mint a token with a specific `token_blind` and execute on all
492    /// registered holders. Returns the [`TokenId`].
493    ///
494    /// Use this instead of `token_mint_to_all` when you need the same
495    /// token blind across multiple mints (e.g. DAO governance tokens).
496    pub async fn token_mint_with_blind_to_all(
497        &mut self,
498        amount: u64,
499        holder: &Holder,
500        recipient: &Holder,
501        token_blind: BaseBlind,
502        block_height: u32,
503    ) -> Result<TokenId> {
504        let (tx, mint_params, auth_params, fee_params) = self
505            .token_mint(amount, holder, recipient, token_blind, None, None, block_height)
506            .await?;
507
508        let token_id = self.derive_token_id(recipient, token_blind);
509
510        let holders = self.holder_keys.clone();
511        for h in &holders {
512            self.execute_token_mint_tx(
513                h,
514                tx.clone(),
515                &mint_params,
516                &auth_params,
517                &fee_params,
518                block_height,
519                true,
520            )
521            .await?;
522        }
523        self.assert_all_trees();
524
525        Ok(token_id)
526    }
527
528    /// Transfer `amount` of `token_id` from `sender` to `recipient` and
529    /// execute the tx on all registered holders.
530    pub async fn transfer_to_all(
531        &mut self,
532        amount: u64,
533        sender: &Holder,
534        recipient: &Holder,
535        token_id: TokenId,
536        block_height: u32,
537    ) -> Result<()> {
538        let owncoins = self.coins_by_token(sender, token_id);
539        let (tx, (params, fee_params), _spent) = self
540            .transfer(amount, sender, recipient, &owncoins, token_id, block_height, false)
541            .await?;
542
543        let holders = self.holder_keys.clone();
544        for h in &holders {
545            self.execute_transfer_tx(h, tx.clone(), &params, &fee_params, block_height, true)
546                .await?;
547        }
548
549        self.assert_all_trees();
550
551        Ok(())
552    }
553
554    /// Burn given [`OwnCoin`]s and execute the tx on all registered holders.
555    pub async fn burn_to_all(
556        &mut self,
557        holder: &Holder,
558        coins: &[OwnCoin],
559        block_height: u32,
560    ) -> Result<()> {
561        let (tx, (params, fee_params), _spent) = self.burn(holder, coins, block_height).await?;
562
563        let holders = self.holder_keys.clone();
564        for h in &holders {
565            self.execute_burn_tx(h, tx.clone(), &params, &fee_params, block_height, true).await?;
566        }
567
568        self.assert_all_trees();
569
570        Ok(())
571    }
572
573    /// Build a genesis mint for `holder` and execute on all registered holders.
574    /// Returns the found [`OwnCoin`]s.
575    pub async fn genesis_mint_to_all(
576        &mut self,
577        holder: &Holder,
578        amounts: &[u64],
579        block_height: u32,
580    ) -> Result<Vec<OwnCoin>> {
581        let (tx, params) = self.genesis_mint(holder, amounts, None, None).await?;
582        self.genesis_mint_to_all_with(tx, &params, block_height).await
583    }
584
585    /// Execute a pre-built genesis mint transaction on all registered holders.
586    /// Useful when you need to test the transaction before broadcasting
587    /// (e.g. malicious block height checks).
588    pub async fn genesis_mint_to_all_with(
589        &mut self,
590        tx: Transaction,
591        params: &MoneyGenesisMintParamsV1,
592        block_height: u32,
593    ) -> Result<Vec<OwnCoin>> {
594        let holders = self.holder_keys.clone();
595        let mut found = vec![];
596        for h in &holders {
597            found.extend(
598                self.execute_genesis_mint_tx(h, tx.clone(), params, block_height, true).await?,
599            );
600        }
601        self.assert_all_trees();
602        Ok(found)
603    }
604
605    /// Freeze a token authority for `holder` and execute on all registered
606    /// holders.
607    pub async fn token_freeze_to_all(&mut self, holder: &Holder, block_height: u32) -> Result<()> {
608        let (tx, freeze_params, fee_params) = self.token_freeze(holder, block_height).await?;
609
610        let holders = self.holder_keys.clone();
611        for h in &holders {
612            self.execute_token_freeze_tx(
613                h,
614                tx.clone(),
615                &freeze_params,
616                &fee_params,
617                block_height,
618                true,
619            )
620            .await?;
621        }
622        self.assert_all_trees();
623        Ok(())
624    }
625
626    /// Generate a new block mined by `miner` and broadcast to all registered
627    /// holders. Convenience wrapper around `generate_block` that uses
628    /// `holder_keys` instead of requiring the caller to pass holders.
629    pub async fn generate_block_all(&mut self, miner: &Holder) -> Result<Vec<OwnCoin>> {
630        let holders = self.holder_keys.clone();
631        self.generate_block(miner, &holders).await
632    }
633
634    /// Consolidate all coins of `token_id` owned by `holder` into a single
635    /// coin by transferring to self, then execute on all registered holders.
636    pub async fn consolidate_to_all(
637        &mut self,
638        holder: &Holder,
639        token_id: TokenId,
640        block_height: u32,
641    ) -> Result<()> {
642        let owncoins = self.coins_by_token(holder, token_id);
643        if owncoins.len() <= 1 {
644            // Nothing to consolidate
645            return Ok(())
646        }
647
648        let total: u64 = owncoins.iter().map(|c| c.note.value).sum();
649        let (tx, (params, fee_params), _spent) =
650            self.transfer(total, holder, holder, &owncoins, token_id, block_height, false).await?;
651
652        let holders = self.holder_keys.clone();
653        for h in &holders {
654            self.execute_transfer_tx(h, tx.clone(), &params, &fee_params, block_height, true)
655                .await?;
656        }
657
658        self.assert_all_trees();
659
660        Ok(())
661    }
662
663    /// Perform an OTC swap between two holders and execute on all registered
664    /// holders.
665    pub async fn otc_swap_to_all(
666        &mut self,
667        holder0: &Holder,
668        coin0: &OwnCoin,
669        holder1: &Holder,
670        coin1: &OwnCoin,
671        block_height: u32,
672    ) -> Result<()> {
673        let (tx, params, fee_params) =
674            self.otc_swap(holder0, coin0, holder1, coin1, block_height).await?;
675
676        let holders = self.holder_keys.clone();
677        for h in &holders {
678            self.execute_otc_swap_tx(h, tx.clone(), &params, &fee_params, block_height, true)
679                .await?;
680        }
681
682        self.assert_all_trees();
683
684        Ok(())
685    }
686
687    /// Build a `Dao::Mint` transaction and execute on all registered holders.
688    #[allow(clippy::too_many_arguments)]
689    pub async fn dao_mint_to_all(
690        &mut self,
691        holder: &Holder,
692        dao: &Dao,
693        dao_notes_secret_key: &SecretKey,
694        dao_proposer_secret_key: &SecretKey,
695        dao_proposals_secret_key: &SecretKey,
696        dao_votes_secret_key: &SecretKey,
697        dao_exec_secret_key: &SecretKey,
698        dao_early_exec_secret_key: &SecretKey,
699        block_height: u32,
700    ) -> Result<()> {
701        let (tx, params, fee_params) = self
702            .dao_mint(
703                holder,
704                dao,
705                dao_notes_secret_key,
706                dao_proposer_secret_key,
707                dao_proposals_secret_key,
708                dao_votes_secret_key,
709                dao_exec_secret_key,
710                dao_early_exec_secret_key,
711                block_height,
712            )
713            .await?;
714
715        let holders = self.holder_keys.clone();
716        for h in &holders {
717            self.execute_dao_mint_tx(h, tx.clone(), &params, &fee_params, block_height, true)
718                .await?;
719        }
720        self.assert_all_trees();
721        Ok(())
722    }
723
724    /// Build a `Dao::Propose` (transfer) transaction and execute on all
725    /// registered holders. Returns the [`DaoProposal`] for subsequent
726    /// voting/execution.
727    #[allow(clippy::too_many_arguments)]
728    pub async fn dao_propose_transfer_to_all(
729        &mut self,
730        proposer: &Holder,
731        proposal_coinattrs: &[CoinAttributes],
732        user_data: pallas::Base,
733        dao: &Dao,
734        dao_proposer_secret_key: &SecretKey,
735        block_height: u32,
736        duration_blockwindows: u64,
737    ) -> Result<DaoProposal> {
738        let (tx, params, fee_params, proposal_info) = self
739            .dao_propose_transfer(
740                proposer,
741                proposal_coinattrs,
742                user_data,
743                dao,
744                dao_proposer_secret_key,
745                block_height,
746                duration_blockwindows,
747            )
748            .await?;
749
750        let holders = self.holder_keys.clone();
751        for h in &holders {
752            self.execute_dao_propose_tx(h, tx.clone(), &params, &fee_params, block_height, true)
753                .await?;
754        }
755        self.assert_all_trees();
756        Ok(proposal_info)
757    }
758
759    /// Build a `Dao::Propose` (generic) transaction and execute on all
760    /// registered holders. Returns the [`DaoProposal`].
761    pub async fn dao_propose_generic_to_all(
762        &mut self,
763        proposer: &Holder,
764        user_data: pallas::Base,
765        dao: &Dao,
766        dao_proposer_secret_key: &SecretKey,
767        block_height: u32,
768        duration_blockwindows: u64,
769    ) -> Result<DaoProposal> {
770        let (tx, params, fee_params, proposal_info) = self
771            .dao_propose_generic(
772                proposer,
773                user_data,
774                dao,
775                dao_proposer_secret_key,
776                block_height,
777                duration_blockwindows,
778            )
779            .await?;
780
781        let holders = self.holder_keys.clone();
782        for h in &holders {
783            self.execute_dao_propose_tx(h, tx.clone(), &params, &fee_params, block_height, true)
784                .await?;
785        }
786        self.assert_all_trees();
787        Ok(proposal_info)
788    }
789
790    /// Build and broadcast a single `Dao::Vote` transaction on all registered
791    /// holders. Returns the [`DaoVoteParams`] (needed for vote counting).
792    pub async fn dao_vote_to_all(
793        &mut self,
794        voter: &Holder,
795        vote_option: bool,
796        dao: &Dao,
797        proposal: &DaoProposal,
798        block_height: u32,
799    ) -> Result<DaoVoteParams> {
800        let (tx, vote_params, fee_params) =
801            self.dao_vote(voter, vote_option, dao, proposal, block_height).await?;
802
803        let holders = self.holder_keys.clone();
804        for h in &holders {
805            self.execute_dao_vote_tx(h, tx.clone(), &fee_params, block_height, true).await?;
806        }
807        self.assert_all_trees();
808        Ok(vote_params)
809    }
810
811    /// Build and broadcast a `Dao::Exec` (transfer) transaction on all
812    /// registered holders.
813    #[allow(clippy::too_many_arguments)]
814    pub async fn dao_exec_transfer_to_all(
815        &mut self,
816        executor: &Holder,
817        dao: &Dao,
818        dao_exec_secret_key: &SecretKey,
819        dao_early_exec_secret_key: &Option<SecretKey>,
820        proposal_info: &DaoProposal,
821        proposal_coinattrs: Vec<CoinAttributes>,
822        total_yes_vote_value: u64,
823        total_all_vote_value: u64,
824        total_yes_vote_blind: ScalarBlind,
825        total_all_vote_blind: ScalarBlind,
826        block_height: u32,
827    ) -> Result<()> {
828        let (tx, xfer_params, fee_params) = self
829            .dao_exec_transfer(
830                executor,
831                dao,
832                dao_exec_secret_key,
833                dao_early_exec_secret_key,
834                proposal_info,
835                proposal_coinattrs,
836                total_yes_vote_value,
837                total_all_vote_value,
838                total_yes_vote_blind,
839                total_all_vote_blind,
840                block_height,
841            )
842            .await?;
843
844        let holders = self.holder_keys.clone();
845        for h in &holders {
846            self.execute_dao_exec_tx(
847                h,
848                tx.clone(),
849                Some(&xfer_params),
850                &fee_params,
851                block_height,
852                true,
853            )
854            .await?;
855        }
856        self.assert_all_trees();
857        Ok(())
858    }
859
860    /// Build and broadcast a `Dao::Exec` (generic) transaction on all
861    /// registered holders.
862    #[allow(clippy::too_many_arguments)]
863    pub async fn dao_exec_generic_to_all(
864        &mut self,
865        executor: &Holder,
866        dao: &Dao,
867        dao_exec_secret_key: &SecretKey,
868        dao_early_exec_secret_key: &Option<SecretKey>,
869        proposal_info: &DaoProposal,
870        total_yes_vote_value: u64,
871        total_all_vote_value: u64,
872        total_yes_vote_blind: ScalarBlind,
873        total_all_vote_blind: ScalarBlind,
874        block_height: u32,
875    ) -> Result<()> {
876        let (tx, fee_params) = self
877            .dao_exec_generic(
878                executor,
879                dao,
880                dao_exec_secret_key,
881                dao_early_exec_secret_key,
882                proposal_info,
883                total_yes_vote_value,
884                total_all_vote_value,
885                total_yes_vote_blind,
886                total_all_vote_blind,
887                block_height,
888            )
889            .await?;
890
891        let holders = self.holder_keys.clone();
892        for h in &holders {
893            self.execute_dao_exec_tx(h, tx.clone(), None, &fee_params, block_height, true).await?;
894        }
895        self.assert_all_trees();
896        Ok(())
897    }
898
899    /// Derive the [`TokenId`] that a given holder's `token_mint_authority`
900    /// would produce with a given blind.
901    pub fn derive_token_id(&self, holder: &Holder, token_blind: BaseBlind) -> TokenId {
902        let wallet = self.wallet(holder);
903        let mint_authority = wallet.token_mint_authority;
904
905        let auth_func_id = FuncRef {
906            contract_id: *MONEY_CONTRACT_ID,
907            func_code: MoneyFunction::AuthTokenMintV1 as u8,
908        }
909        .to_func_id();
910
911        let token_attrs = TokenAttributes {
912            auth_parent: auth_func_id,
913            user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]),
914            blind: token_blind,
915        };
916
917        token_attrs.to_token_id()
918    }
919}
920
921async fn benchmark_wasm_calls(
922    callname: &str,
923    validator: ValidatorPtr,
924    tx: &Transaction,
925    block_height: u32,
926) -> Result<()> {
927    let mut file = OpenOptions::new().create(true).append(true).open("bench.csv")?;
928
929    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
930
931    let validator = validator.read().await;
932    for (idx, call) in tx.calls.iter().enumerate() {
933        let overlay = BlockchainOverlay::new(&validator.blockchain).expect("blockchain overlay");
934        let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
935
936        let mut runtime = Runtime::new(
937            &wasm,
938            overlay.clone(),
939            tx_local_state.clone(),
940            call.data.contract_id,
941            block_height,
942            validator.consensus.module.target,
943            tx.hash(),
944            idx as u8,
945        )
946        .expect("runtime");
947
948        // Write call data
949        let mut payload = vec![];
950        tx.calls.encode(&mut payload)?;
951
952        let mut times = [0; 3];
953        let now = Instant::now();
954        let _metadata = runtime.metadata(&payload)?;
955        times[0] = now.elapsed().as_micros();
956
957        let now = Instant::now();
958        let mut update = vec![call.data.data[0]];
959        update.append(&mut runtime.exec(&payload)?);
960        times[1] = now.elapsed().as_micros();
961
962        let now = Instant::now();
963        runtime.apply(&update)?;
964        times[2] = now.elapsed().as_micros();
965
966        writeln!(
967            file,
968            "{},{},{},{},{},{},{}",
969            callname,
970            tx.hash(),
971            idx,
972            times[0],
973            times[1],
974            times[2],
975            serialize(tx).len(),
976        )?;
977    }
978
979    Ok(())
980}