contract/money: Rework test harness, fix TokenMint_V1 proof.

This commit is contained in:
parazyd
2023-02-23 18:45:56 +01:00
parent 592578ef43
commit 60755a114f
5 changed files with 307 additions and 183 deletions

View File

@@ -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,

View File

@@ -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)),

View File

@@ -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:

View File

@@ -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<OwnCoin>,
pub spent_coins: Vec<OwnCoin>,
}
impl Wallet {
async fn new(keypair: Keypair, faucet_pubkeys: &[PublicKey]) -> Result<Self> {
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<PublicKey>,
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<Self> {
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))
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
//! 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(())
}