From 60755a114f616c6ddfb3407c6b3defea6bd06c69 Mon Sep 17 00:00:00 2001 From: parazyd Date: Thu, 23 Feb 2023 18:45:56 +0100 Subject: [PATCH] contract/money: Rework test harness, fix TokenMint_V1 proof. --- src/contract/money/proof/token_mint_v1.zk | 3 + src/contract/money/src/client/mint_v1.rs | 1 + src/contract/money/src/entrypoint/mint_v1.rs | 2 +- src/contract/money/tests/harness.rs | 317 ++++++++----------- src/contract/money/tests/integration.rs | 167 ++++++++++ 5 files changed, 307 insertions(+), 183 deletions(-) create mode 100644 src/contract/money/tests/integration.rs diff --git a/src/contract/money/proof/token_mint_v1.zk b/src/contract/money/proof/token_mint_v1.zk index 81ac4d6c9..b1924d07d 100644 --- a/src/contract/money/proof/token_mint_v1.zk +++ b/src/contract/money/proof/token_mint_v1.zk @@ -14,6 +14,8 @@ contract "TokenMint_V1" { Base rcpt_x, # Recipient's public key y coordinate Base rcpt_y, + # Unique serial number for the minted coin + Base serial, # Random blinding factor for the minted coin Base coin_blind, # Allows composing this ZK proof to invoke other contracts @@ -45,6 +47,7 @@ circuit "TokenMint_V1" { rcpt_y, supply, token_id, + serial, spend_hook, user_data, coin_blind, diff --git a/src/contract/money/src/client/mint_v1.rs b/src/contract/money/src/client/mint_v1.rs index ed6c64d39..cd1a51892 100644 --- a/src/contract/money/src/client/mint_v1.rs +++ b/src/contract/money/src/client/mint_v1.rs @@ -206,6 +206,7 @@ pub(crate) fn create_token_mint_proof( Witness::Base(Value::known(pallas::Base::from(output.value))), Witness::Base(Value::known(rcpt_x)), Witness::Base(Value::known(rcpt_y)), + Witness::Base(Value::known(serial)), Witness::Base(Value::known(coin_blind)), Witness::Base(Value::known(spend_hook)), Witness::Base(Value::known(user_data)), diff --git a/src/contract/money/src/entrypoint/mint_v1.rs b/src/contract/money/src/entrypoint/mint_v1.rs index 8f4e2c507..72e403634 100644 --- a/src/contract/money/src/entrypoint/mint_v1.rs +++ b/src/contract/money/src/entrypoint/mint_v1.rs @@ -57,7 +57,7 @@ pub(crate) fn money_mint_get_metadata_v1( signature_pubkeys.push(params.input.signature_public); let value_coords = params.output.value_commit.to_affine().coordinates().unwrap(); - let token_coords = params.output.value_commit.to_affine().coordinates().unwrap(); + let token_coords = params.output.token_commit.to_affine().coordinates().unwrap(); // Since we expect a signature from the mint authority, we use those coordinates // as public inputs for the ZK proof: diff --git a/src/contract/money/tests/harness.rs b/src/contract/money/tests/harness.rs index 434b0c14d..3f2889386 100644 --- a/src/contract/money/tests/harness.rs +++ b/src/contract/money/tests/harness.rs @@ -23,218 +23,140 @@ use darkfi::{ TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION, }, tx::Transaction, - wallet::WalletDb, - zk::{empty_witnesses, ProvingKey, ZkCircuit}, + wallet::{WalletDb, WalletPtr}, + zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit}, zkas::ZkBinary, Result, }; +use darkfi_money_contract::client::OwnCoin; use darkfi_sdk::{ - crypto::{ - pasta_prelude::*, ContractId, Keypair, MerkleTree, PublicKey, DARK_TOKEN_ID, - MONEY_CONTRACT_ID, - }, + crypto::{Keypair, MerkleTree, PublicKey, DARK_TOKEN_ID, MONEY_CONTRACT_ID}, db::SMART_CONTRACT_ZKAS_DB_NAME, pasta::pallas, ContractCall, }; use darkfi_serial::{serialize, Encodable}; -use log::{info, warn}; use rand::rngs::OsRng; use darkfi_money_contract::{ - client::transfer_v1::TransferCallBuilder, model::MoneyTransferParamsV1 as MoneyTransferParams, - MoneyFunction::TransferV1 as MoneyTransfer, MONEY_CONTRACT_ZKAS_BURN_NS_V1, - MONEY_CONTRACT_ZKAS_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1, + client::{mint_v1::MintCallBuilder, transfer_v1::TransferCallBuilder}, + model::{MoneyMintParamsV1, MoneyTransferParamsV1}, + MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, + MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1, }; -pub fn init_logger() -> Result<()> { +pub fn init_logger() { let mut cfg = simplelog::ConfigBuilder::new(); cfg.add_filter_ignore("sled".to_string()); - if let Err(_) = simplelog::TermLogger::init( - //simplelog::LevelFilter::Info, + cfg.add_filter_ignore("blockchain::contractstore".to_string()); + let _ = simplelog::TermLogger::init( simplelog::LevelFilter::Debug, //simplelog::LevelFilter::Trace, cfg.build(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto, - ) { - warn!(target: "money_harness", "Logger already initialized"); - } + ); +} - Ok(()) +pub struct Wallet { + pub keypair: Keypair, + pub state: ValidatorStatePtr, + pub merkle_tree: MerkleTree, + pub wallet: WalletPtr, + pub coins: Vec, + pub spent_coins: Vec, +} + +impl Wallet { + async fn new(keypair: Keypair, faucet_pubkeys: &[PublicKey]) -> Result { + let wallet = WalletDb::new("sqlite::memory:", "foo").await?; + let sled_db = sled::Config::new().temporary(true).open()?; + + let state = ValidatorState::new( + &sled_db, + *TESTNET_BOOTSTRAP_TIMESTAMP, + *TESTNET_GENESIS_TIMESTAMP, + *TESTNET_GENESIS_HASH_BYTES, + *TESTNET_INITIAL_DISTRIBUTION, + wallet.clone(), + faucet_pubkeys.to_vec(), + false, + false, + ) + .await?; + + let merkle_tree = MerkleTree::new(100); + + let coins = vec![]; + let spent_coins = vec![]; + + Ok(Self { keypair, state, merkle_tree, wallet, coins, spent_coins }) + } } pub struct MoneyTestHarness { - pub faucet_kp: Keypair, - pub alice_kp: Keypair, - pub bob_kp: Keypair, - pub charlie_kp: Keypair, - pub faucet_pubkeys: Vec, - pub faucet_state: ValidatorStatePtr, - pub alice_state: ValidatorStatePtr, - pub bob_state: ValidatorStatePtr, - pub charlie_state: ValidatorStatePtr, - pub money_contract_id: ContractId, - pub proving_keys: HashMap<[u8; 32], Vec<(&'static str, ProvingKey)>>, - pub mint_zkbin: ZkBinary, - pub burn_zkbin: ZkBinary, - pub token_mint_zkbin: ZkBinary, - pub mint_pk: ProvingKey, - pub burn_pk: ProvingKey, - pub token_mint_pk: ProvingKey, - pub faucet_merkle_tree: MerkleTree, - pub alice_merkle_tree: MerkleTree, - pub bob_merkle_tree: MerkleTree, - pub charlie_merkle_tree: MerkleTree, + pub faucet: Wallet, + pub alice: Wallet, + pub bob: Wallet, + pub charlie: Wallet, + pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>, } impl MoneyTestHarness { pub async fn new() -> Result { let faucet_kp = Keypair::random(&mut OsRng); - let alice_kp = Keypair::random(&mut OsRng); - let bob_kp = Keypair::random(&mut OsRng); - let charlie_kp = Keypair::random(&mut OsRng); let faucet_pubkeys = vec![faucet_kp.public]; + let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?; - let faucet_wallet = WalletDb::new("sqlite::memory:", "foo").await?; - let alice_wallet = WalletDb::new("sqlite::memory:", "foo").await?; - let bob_wallet = WalletDb::new("sqlite::memory:", "foo").await?; - let charlie_wallet = WalletDb::new("sqlite::memory:", "foo").await?; + let alice_kp = Keypair::random(&mut OsRng); + let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?; - let faucet_sled_db = sled::Config::new().temporary(true).open()?; - let alice_sled_db = sled::Config::new().temporary(true).open()?; - let bob_sled_db = sled::Config::new().temporary(true).open()?; - let charlie_sled_db = sled::Config::new().temporary(true).open()?; + let bob_kp = Keypair::random(&mut OsRng); + let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?; - let faucet_state = ValidatorState::new( - &faucet_sled_db, - *TESTNET_BOOTSTRAP_TIMESTAMP, - *TESTNET_GENESIS_TIMESTAMP, - *TESTNET_GENESIS_HASH_BYTES, - *TESTNET_INITIAL_DISTRIBUTION, - faucet_wallet, - faucet_pubkeys.clone(), - false, - false, - ) - .await?; + let charlie_kp = Keypair::random(&mut OsRng); + let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?; - let alice_state = ValidatorState::new( - &alice_sled_db, - *TESTNET_BOOTSTRAP_TIMESTAMP, - *TESTNET_GENESIS_TIMESTAMP, - *TESTNET_GENESIS_HASH_BYTES, - *TESTNET_INITIAL_DISTRIBUTION, - alice_wallet, - faucet_pubkeys.clone(), - false, - false, - ) - .await?; - - let bob_state = ValidatorState::new( - &bob_sled_db, - *TESTNET_BOOTSTRAP_TIMESTAMP, - *TESTNET_GENESIS_TIMESTAMP, - *TESTNET_GENESIS_HASH_BYTES, - *TESTNET_INITIAL_DISTRIBUTION, - bob_wallet, - faucet_pubkeys.clone(), - false, - false, - ) - .await?; - - let charlie_state = ValidatorState::new( - &charlie_sled_db, - *TESTNET_BOOTSTRAP_TIMESTAMP, - *TESTNET_GENESIS_TIMESTAMP, - *TESTNET_GENESIS_HASH_BYTES, - *TESTNET_INITIAL_DISTRIBUTION, - charlie_wallet, - faucet_pubkeys.clone(), - false, - false, - ) - .await?; - - let money_contract_id = *MONEY_CONTRACT_ID; - - let alice_sled = alice_state.read().await.blockchain.sled_db.clone(); - let db_handle = alice_state.read().await.blockchain.contracts.lookup( + // Get the zkas circuits and build proving keys + let mut proving_keys = HashMap::new(); + let alice_sled = alice.state.read().await.blockchain.sled_db.clone(); + let db_handle = alice.state.read().await.blockchain.contracts.lookup( &alice_sled, - &money_contract_id, + &MONEY_CONTRACT_ID, SMART_CONTRACT_ZKAS_DB_NAME, )?; - let mint_zkbin = db_handle.get(&serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1))?.unwrap(); - let burn_zkbin = db_handle.get(&serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1))?.unwrap(); - let token_mint_zkbin = - db_handle.get(&serialize(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1))?.unwrap(); - info!(target: "money_harness", "Decoding bincode"); - let mint_zkbin = ZkBinary::decode(&mint_zkbin)?; - let burn_zkbin = ZkBinary::decode(&burn_zkbin)?; - let token_mint_zkbin = ZkBinary::decode(&token_mint_zkbin)?; - let mint_witnesses = empty_witnesses(&mint_zkbin); - let burn_witnesses = empty_witnesses(&burn_zkbin); - let token_mint_witnesses = empty_witnesses(&token_mint_zkbin); - let mint_circuit = ZkCircuit::new(mint_witnesses, mint_zkbin.clone()); - let burn_circuit = ZkCircuit::new(burn_witnesses, burn_zkbin.clone()); - let token_mint_circuit = ZkCircuit::new(token_mint_witnesses, token_mint_zkbin.clone()); + macro_rules! mkpk { + ($ns:expr) => { + let zkbin = db_handle.get(&serialize(&$ns))?.unwrap(); + let zkbin = ZkBinary::decode(&zkbin)?; + let witnesses = empty_witnesses(&zkbin); + let circuit = ZkCircuit::new(witnesses, zkbin.clone()); + let pk = ProvingKey::build(13, &circuit); + proving_keys.insert($ns, (pk, zkbin)); + }; + } - info!(target: "money_harness", "Creating zk proving keys"); - let k = 13; - let mut proving_keys = HashMap::<[u8; 32], Vec<(&str, ProvingKey)>>::new(); - let mint_pk = ProvingKey::build(k, &mint_circuit); - let burn_pk = ProvingKey::build(k, &burn_circuit); - let token_mint_pk = ProvingKey::build(k, &token_mint_circuit); - let pks = vec![ - (MONEY_CONTRACT_ZKAS_MINT_NS_V1, mint_pk.clone()), - (MONEY_CONTRACT_ZKAS_BURN_NS_V1, burn_pk.clone()), - (MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1, mint_pk.clone()), - ]; - proving_keys.insert(money_contract_id.inner().to_repr(), pks); + mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1); + mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1); + mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1); + mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1); - let faucet_merkle_tree = MerkleTree::new(100); - let alice_merkle_tree = MerkleTree::new(100); - let bob_merkle_tree = MerkleTree::new(100); - let charlie_merkle_tree = MerkleTree::new(100); - - Ok(Self { - faucet_kp, - alice_kp, - bob_kp, - charlie_kp, - faucet_pubkeys, - faucet_state, - alice_state, - bob_state, - charlie_state, - money_contract_id, - proving_keys, - mint_pk, - burn_pk, - token_mint_pk, - mint_zkbin, - burn_zkbin, - token_mint_zkbin, - faucet_merkle_tree, - alice_merkle_tree, - bob_merkle_tree, - charlie_merkle_tree, - }) + Ok(Self { faucet, alice, bob, charlie, proving_keys }) } - pub fn airdrop( + pub fn airdrop_native( &self, - amount: u64, - rcpt: PublicKey, - ) -> Result<(Transaction, MoneyTransferParams)> { - // TODO: verify change usage is correct - let call_debris = TransferCallBuilder { - keypair: self.faucet_kp, - recipient: rcpt, - value: amount, + value: u64, + recipient: PublicKey, + ) -> Result<(Transaction, MoneyTransferParamsV1)> { + 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(); + + let builder = TransferCallBuilder { + keypair: self.faucet.keypair, + recipient, + value, token_id: *DARK_TOKEN_ID, rcpt_spend_hook: pallas::Base::zero(), rcpt_user_data: pallas::Base::zero(), @@ -243,25 +165,56 @@ impl MoneyTestHarness { change_user_data: pallas::Base::zero(), change_user_data_blind: pallas::Base::random(&mut OsRng), coins: vec![], - tree: self.faucet_merkle_tree.clone(), - mint_zkbin: self.mint_zkbin.clone(), - mint_pk: self.mint_pk.clone(), - burn_zkbin: self.burn_zkbin.clone(), - burn_pk: self.burn_pk.clone(), + tree: self.faucet.merkle_tree.clone(), + mint_zkbin: mint_zkbin.clone(), + mint_pk: mint_pk.clone(), + burn_zkbin: burn_zkbin.clone(), + burn_pk: burn_pk.clone(), clear_input: true, - } - .build()?; + }; - let contract_id = *MONEY_CONTRACT_ID; + let debris = builder.build()?; - let mut data = vec![MoneyTransfer as u8]; - call_debris.params.encode(&mut data)?; - let calls = vec![ContractCall { contract_id, data }]; - let proofs = vec![call_debris.proofs]; + let mut data = vec![MoneyFunction::TransferV1 as u8]; + debris.params.encode(&mut data)?; + let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }]; + let proofs = vec![debris.proofs]; let mut tx = Transaction { calls, proofs, signatures: vec![] }; - let sigs = tx.create_sigs(&mut OsRng, &call_debris.signature_secrets)?; + let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?; tx.signatures = vec![sigs]; - Ok((tx, call_debris.params)) + Ok((tx, debris.params)) + } + + pub fn mint_token( + &self, + mint_authority: Keypair, + amount: u64, + recipient: PublicKey, + ) -> Result<(Transaction, MoneyMintParamsV1)> { + let (token_mint_pk, token_mint_zkbin) = + self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1).unwrap(); + + let builder = MintCallBuilder { + mint_authority, + recipient, + amount, + spend_hook: pallas::Base::zero(), + user_data: pallas::Base::zero(), + token_mint_zkbin: token_mint_zkbin.clone(), + token_mint_pk: token_mint_pk.clone(), + }; + + let debris = builder.build()?; + + let mut data = vec![MoneyFunction::MintV1 as u8]; + debris.params.encode(&mut data)?; + let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }]; + let proofs = vec![debris.proofs]; + let mut tx = Transaction { calls, proofs, signatures: vec![] }; + let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?; + tx.signatures = vec![sigs]; + + Ok((tx, debris.params)) } } diff --git a/src/contract/money/tests/integration.rs b/src/contract/money/tests/integration.rs new file mode 100644 index 000000000..14098b2f0 --- /dev/null +++ b/src/contract/money/tests/integration.rs @@ -0,0 +1,167 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2023 Dyne.org foundation + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! Integration test for functionalities of the money smart contract: +//! +//! * Airdrops of the native token from the faucet +//! * Arbitrary token minting +//! * Transfers/Payments +//! * Atomic swaps +//! * Token mint freezing +//! +//! With this test we want to confirm the money contract state transitions +//! work between multiple parties and are able to be verified. +//! +//! TODO: Malicious cases + +use darkfi::{tx::Transaction, Result}; +use darkfi_sdk::{ + crypto::{poseidon_hash, Keypair, MerkleNode, Nullifier, MONEY_CONTRACT_ID}, + incrementalmerkletree::Tree, + ContractCall, +}; +use darkfi_serial::Encodable; +use log::info; +use rand::rngs::OsRng; + +use darkfi_money_contract::{ + client::{freeze_v1::FreezeCallBuilder, MoneyNote, OwnCoin}, + MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, +}; + +mod harness; +use harness::{init_logger, MoneyTestHarness}; + +#[async_std::test] +async fn money_integration() -> Result<()> { + init_logger(); + + let mut th = MoneyTestHarness::new().await?; + + // Let's first airdrop some tokens to Alice. + let (alice_airdrop_tx, alice_airdrop_params) = + th.airdrop_native(200, th.alice.keypair.public)?; + + info!("[Faucet] Executing Alice airdrop tx"); + th.faucet.state.read().await.verify_transactions(&[alice_airdrop_tx.clone()], true).await?; + th.faucet.merkle_tree.append(&MerkleNode::from(alice_airdrop_params.outputs[0].coin.inner())); + + info!("[Alice] Executing Alice airdrop tx"); + th.alice.state.read().await.verify_transactions(&[alice_airdrop_tx.clone()], true).await?; + th.alice.merkle_tree.append(&MerkleNode::from(alice_airdrop_params.outputs[0].coin.inner())); + // Alice has to witness this coin because it's hers. + let leaf_position = th.alice.merkle_tree.witness().unwrap(); + + info!("[Bob] Executing Alice airdrop tx"); + th.bob.state.read().await.verify_transactions(&[alice_airdrop_tx.clone()], true).await?; + th.bob.merkle_tree.append(&MerkleNode::from(alice_airdrop_params.outputs[0].coin.inner())); + + info!("[Charlie] Executing Alice airdrop tx"); + th.charlie.state.read().await.verify_transactions(&[alice_airdrop_tx.clone()], true).await?; + th.charlie.merkle_tree.append(&MerkleNode::from(alice_airdrop_params.outputs[0].coin.inner())); + + assert_eq!(th.alice.merkle_tree.root(0).unwrap(), th.bob.merkle_tree.root(0).unwrap()); + assert_eq!(th.bob.merkle_tree.root(0).unwrap(), th.charlie.merkle_tree.root(0).unwrap()); + assert_eq!(th.faucet.merkle_tree.root(0).unwrap(), th.charlie.merkle_tree.root(0).unwrap()); + + // Alice builds an `OwnCoin` from her airdrop. + let note: MoneyNote = alice_airdrop_params.outputs[0].note.decrypt(&th.alice.keypair.secret)?; + let owncoin = OwnCoin { + coin: alice_airdrop_params.outputs[0].coin, + note: note.clone(), + secret: th.alice.keypair.secret, + nullifier: Nullifier::from(poseidon_hash([th.alice.keypair.secret.inner(), note.serial])), + leaf_position, + }; + th.alice.coins.push(owncoin); + + // Bob creates a new mint authority keypair and mints some tokens for Charlie. + let bob_token_authority = Keypair::random(&mut OsRng); + let (bob_charlie_mint_tx, bob_charlie_mint_params) = + th.mint_token(bob_token_authority, 500, th.charlie.keypair.public)?; + + info!("[Faucet] Executing BOBTOKEN mint to Charlie"); + th.faucet.state.read().await.verify_transactions(&[bob_charlie_mint_tx.clone()], true).await?; + th.faucet.merkle_tree.append(&MerkleNode::from(bob_charlie_mint_params.output.coin.inner())); + + info!("[Alice] Executing BOBTOKEN mint to Charlie"); + th.alice.state.read().await.verify_transactions(&[bob_charlie_mint_tx.clone()], true).await?; + th.alice.merkle_tree.append(&MerkleNode::from(bob_charlie_mint_params.output.coin.inner())); + + info!("[Bob] Executing BOBTOKEN mint to Charlie"); + th.bob.state.read().await.verify_transactions(&[bob_charlie_mint_tx.clone()], true).await?; + th.bob.merkle_tree.append(&MerkleNode::from(bob_charlie_mint_params.output.coin.inner())); + + info!("[Charlie] Executing BOBTOKEN mint to Charlie"); + th.charlie.state.read().await.verify_transactions(&[bob_charlie_mint_tx.clone()], true).await?; + th.charlie.merkle_tree.append(&MerkleNode::from(bob_charlie_mint_params.output.coin.inner())); + // Charlie has to witness this coin because it's his. + let leaf_position = th.charlie.merkle_tree.witness().unwrap(); + + assert_eq!(th.alice.merkle_tree.root(0).unwrap(), th.bob.merkle_tree.root(0).unwrap()); + assert_eq!(th.bob.merkle_tree.root(0).unwrap(), th.charlie.merkle_tree.root(0).unwrap()); + assert_eq!(th.faucet.merkle_tree.root(0).unwrap(), th.charlie.merkle_tree.root(0).unwrap()); + + // Charlie builds an `OwnCoin` from this mint. + let note: MoneyNote = + bob_charlie_mint_params.output.note.decrypt(&th.charlie.keypair.secret)?; + + let owncoin = OwnCoin { + coin: bob_charlie_mint_params.output.coin, + note: note.clone(), + secret: th.charlie.keypair.secret, + nullifier: Nullifier::from(poseidon_hash([th.charlie.keypair.secret.inner(), note.serial])), + leaf_position, + }; + th.charlie.coins.push(owncoin); + + // Let's attempt to freeze the BOBTOKEN mint, + // and after that we shouldn't be able to mint anymore. + let (token_freeze_pk, token_freeze_zkbin) = + th.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1).unwrap(); + + let bob_frz_builder = FreezeCallBuilder { + mint_authority: bob_token_authority, + token_freeze_zkbin: token_freeze_zkbin.clone(), + token_freeze_pk: token_freeze_pk.clone(), + }; + + let debris = bob_frz_builder.build()?; + let mut data = vec![MoneyFunction::FreezeV1 as u8]; + debris.params.encode(&mut data)?; + let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }]; + let proofs = vec![debris.proofs]; + let mut bob_frz_tx = Transaction { calls, proofs, signatures: vec![] }; + let sigs = bob_frz_tx.create_sigs(&mut OsRng, &[bob_token_authority.secret])?; + bob_frz_tx.signatures = vec![sigs]; + + info!("[Faucet] Executing BOBTOKEN freeze"); + th.faucet.state.read().await.verify_transactions(&[bob_frz_tx.clone()], true).await?; + + info!("[Alice] Executing BOBTOKEN freeze"); + th.alice.state.read().await.verify_transactions(&[bob_frz_tx.clone()], true).await?; + + info!("[Bob] Executing BOBTOKEN freeze"); + th.bob.state.read().await.verify_transactions(&[bob_frz_tx.clone()], true).await?; + + info!("[Charlie] Executing BOBTOKEN freeze"); + th.charlie.state.read().await.verify_transactions(&[bob_frz_tx.clone()], true).await?; + + // Thanks for reading + Ok(()) +}