Przeglądaj źródła

contract: WIP implementation of generic test harness for native contracts.

parazyd 3 lat temu
rodzic
commit
a2ddbcd4f8

+ 20 - 0
Cargo.lock

@@ -1355,6 +1355,25 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "darkfi-contract-test-harness"
+version = "0.4.1"
+dependencies = [
+ "blake3",
+ "bs58",
+ "darkfi",
+ "darkfi-consensus-contract",
+ "darkfi-dao-contract",
+ "darkfi-deployooor-contract",
+ "darkfi-money-contract",
+ "darkfi-sdk",
+ "darkfi-serial",
+ "log",
+ "rand",
+ "simplelog",
+ "sled",
+]
+
 [[package]]
 name = "darkfi-dao-contract"
 version = "0.4.1"
@@ -1413,6 +1432,7 @@ dependencies = [
  "bs58",
  "chacha20poly1305",
  "darkfi",
+ "darkfi-contract-test-harness",
  "darkfi-sdk",
  "darkfi-serial",
  "getrandom",

+ 1 - 0
Cargo.toml

@@ -47,6 +47,7 @@ members = [
     "src/serial/derive",
     "src/serial/derive-internal",
 
+    "src/contract/test-harness",
     "src/contract/money",
     "src/contract/dao",
     "src/contract/consensus",

+ 1 - 0
src/blockchain/contract_store.rs

@@ -163,6 +163,7 @@ impl ContractStateStore {
         }
 
         // We open the tree and clear it. This is unfortunately not atomic.
+        // TODO: FIXME: Can we make it atomic?
         let tree = db.open_tree(ptr)?;
         tree.clear()?;
 

+ 1 - 0
src/contract/test-harness/.gitignore

@@ -0,0 +1 @@
+vks.bin

+ 22 - 0
src/contract/test-harness/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "darkfi-contract-test-harness"
+version = "0.4.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[dependencies]
+darkfi = {path = "../../../", features = ["zk", "tx", "blockchain"]}
+darkfi-sdk = {path = "../../../src/sdk"}
+darkfi-serial = {path = "../../../src/serial", features = ["derive", "crypto"]}
+darkfi-dao-contract = {path = "../dao", features = ["client", "no-entrypoint"]}
+darkfi-money-contract = {path = "../money", features = ["client", "no-entrypoint"]}
+darkfi-consensus-contract = {path = "../consensus", features = ["client", "no-entrypoint"]}
+darkfi-deployooor-contract = {path = "../deployooor", features = ["no-entrypoint"]}
+
+blake3 = "1.4.0"
+bs58 = "0.5.0"
+log = "0.4.19"
+rand = "0.8.5"
+simplelog = "0.12.1"
+sled = "0.34.7"

+ 68 - 0
src/contract/test-harness/src/benchmarks.rs

@@ -0,0 +1,68 @@
+/* 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/>.
+ */
+
+use log::info;
+use std::time::Duration;
+
+use crate::TxAction;
+
+/// Auxiliary struct to calculate transaction actions benchmarks
+pub struct TxActionBenchmarks {
+    /// Vector holding each transaction size in Bytes
+    pub sizes: Vec<usize>,
+    /// Vector holding each transaction broadcasted size in Bytes
+    pub broadcasted_sizes: Vec<usize>,
+    /// Vector holding each transaction creation time
+    pub creation_times: Vec<Duration>,
+    /// Vector holding each transaction verify time
+    pub verify_times: Vec<Duration>,
+}
+
+impl TxActionBenchmarks {
+    pub fn new() -> Self {
+        Self {
+            sizes: vec![],
+            broadcasted_sizes: vec![],
+            creation_times: vec![],
+            verify_times: vec![],
+        }
+    }
+
+    pub fn statistics(&self, action: &TxAction) {
+        if !self.sizes.is_empty() {
+            let avg = self.sizes.iter().sum::<usize>();
+            let avg = avg / self.sizes.len();
+            info!("Average {:?} size: {:?} Bytes", action, avg);
+        }
+        if !self.broadcasted_sizes.is_empty() {
+            let avg = self.broadcasted_sizes.iter().sum::<usize>();
+            let avg = avg / self.broadcasted_sizes.len();
+            info!("Average {:?} broadcasted size: {:?} Bytes", action, avg);
+        }
+        if !self.creation_times.is_empty() {
+            let avg = self.creation_times.iter().sum::<Duration>();
+            let avg = avg / self.creation_times.len() as u32;
+            info!("Average {:?} creation time: {:?}", action, avg);
+        }
+        if !self.verify_times.is_empty() {
+            let avg = self.verify_times.iter().sum::<Duration>();
+            let avg = avg / self.verify_times.len() as u32;
+            info!("Average {:?} verification time: {:?}", action, avg);
+        }
+    }
+}

+ 288 - 0
src/contract/test-harness/src/lib.rs

@@ -0,0 +1,288 @@
+/* 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/>.
+ */
+
+use std::collections::HashMap;
+
+use darkfi::{
+    consensus::{
+        ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
+        TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
+    },
+    runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
+    wallet::{WalletDb, WalletPtr},
+    zk::{empty_witnesses, ProvingKey, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_dao_contract::{
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+};
+use darkfi_money_contract::{
+    client::{MoneyNote, OwnCoin},
+    model::Output,
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+    CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1, 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,
+};
+use darkfi_sdk::crypto::{
+    poseidon_hash, Keypair, MerkleTree, Nullifier, PublicKey, SecretKey, CONSENSUS_CONTRACT_ID,
+    DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
+};
+use darkfi_serial::{deserialize, serialize};
+use log::info;
+use rand::rngs::OsRng;
+
+mod benchmarks;
+use benchmarks::TxActionBenchmarks;
+mod vks;
+
+mod money_airdrop;
+mod money_token;
+
+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,
+        //simplelog::LevelFilter::Debug,
+        //simplelog::LevelFilter::Trace,
+        cfg.build(),
+        simplelog::TerminalMode::Mixed,
+        simplelog::ColorChoice::Auto,
+    ) {
+        return
+    }
+}
+
+/// Enum representing configured wallet holders
+#[derive(Debug, Eq, Hash, PartialEq)]
+pub enum Holder {
+    Faucet,
+    Alice,
+    Bob,
+    Charlie,
+    Rachel,
+}
+
+/// Enum representing transaction actions
+#[derive(Debug, Eq, Hash, PartialEq)]
+pub enum TxAction {
+    MoneyAirdrop,
+    MoneyTokenMint,
+    MoneyTokenFreeze,
+    MoneyGenesisMint,
+    MoneyTransfer,
+    MoneyOtcSwap,
+}
+
+pub struct Wallet {
+    pub keypair: Keypair,
+    pub token_mint_authority: Keypair,
+    pub state: ValidatorStatePtr,
+    pub money_merkle_tree: MerkleTree,
+    pub consensus_staked_merkle_tree: MerkleTree,
+    pub consensus_unstaked_merkle_tree: MerkleTree,
+    pub wallet: WalletPtr,
+    pub unspent_money_coins: Vec<OwnCoin>,
+    pub spent_money_coins: Vec<OwnCoin>,
+}
+
+impl Wallet {
+    pub 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()?;
+
+        // Use pregenerated vks
+        vks::inject(&sled_db)?;
+
+        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?;
+
+        // Create necessary Merkle trees for tracking
+        let money_merkle_tree = MerkleTree::new(100);
+        let consensus_staked_merkle_tree = MerkleTree::new(100);
+        let consensus_unstaked_merkle_tree = MerkleTree::new(100);
+
+        let unspent_money_coins = vec![];
+        let spent_money_coins = vec![];
+
+        let token_mint_authority = Keypair::random(&mut OsRng);
+
+        Ok(Self {
+            keypair,
+            token_mint_authority,
+            state,
+            money_merkle_tree,
+            consensus_staked_merkle_tree,
+            consensus_unstaked_merkle_tree,
+            wallet,
+            unspent_money_coins,
+            spent_money_coins,
+        })
+    }
+}
+
+pub struct TestHarness {
+    pub holders: HashMap<Holder, Wallet>,
+    pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
+    pub tx_action_benchmarks: HashMap<TxAction, TxActionBenchmarks>,
+}
+
+impl TestHarness {
+    pub async fn new(contracts: &[String]) -> Result<Self> {
+        let mut holders = HashMap::new();
+
+        let faucet_kp = Keypair::random(&mut OsRng);
+        let faucet_pubkeys = vec![faucet_kp.public];
+        let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?;
+        holders.insert(Holder::Faucet, faucet);
+
+        let alice_kp = Keypair::random(&mut OsRng);
+        let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
+        // Alice is inserted at end of function
+
+        let bob_kp = Keypair::random(&mut OsRng);
+        let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?;
+        holders.insert(Holder::Bob, bob);
+
+        let charlie_kp = Keypair::random(&mut OsRng);
+        let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?;
+        holders.insert(Holder::Charlie, charlie);
+
+        let rachel_kp = Keypair::random(&mut OsRng);
+        let rachel = Wallet::new(rachel_kp, &faucet_pubkeys).await?;
+        holders.insert(Holder::Rachel, rachel);
+
+        // 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();
+
+        macro_rules! mkpk {
+            ($db:expr, $ns:expr) => {
+                info!("Building ProvingKey for {}", $ns);
+                let zkas_bytes = $db.get(&serialize(&$ns))?.unwrap();
+                let (zkbin, _): (Vec<u8>, Vec<u8>) = deserialize(&zkas_bytes)?;
+                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));
+            };
+        }
+
+        if contracts.contains(&"money".to_string()) {
+            let db_handle = alice.state.read().await.blockchain.contracts.lookup(
+                &alice_sled,
+                &MONEY_CONTRACT_ID,
+                SMART_CONTRACT_ZKAS_DB_NAME,
+            )?;
+            mkpk!(db_handle, MONEY_CONTRACT_ZKAS_MINT_NS_V1);
+            mkpk!(db_handle, MONEY_CONTRACT_ZKAS_BURN_NS_V1);
+            mkpk!(db_handle, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1);
+            mkpk!(db_handle, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1);
+        }
+
+        if contracts.contains(&"consensus".to_string()) {
+            let db_handle = alice.state.read().await.blockchain.contracts.lookup(
+                &alice_sled,
+                &CONSENSUS_CONTRACT_ID,
+                SMART_CONTRACT_ZKAS_DB_NAME,
+            )?;
+            mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1);
+            mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1);
+            mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1);
+        }
+
+        if contracts.contains(&"dao".to_string()) {
+            let db_handle = alice.state.read().await.blockchain.contracts.lookup(
+                &alice_sled,
+                &DAO_CONTRACT_ID,
+                SMART_CONTRACT_ZKAS_DB_NAME,
+            )?;
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_EXEC_NS);
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_MINT_NS);
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS);
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS);
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS);
+            mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS);
+        }
+
+        // Build benchmarks map
+        let mut tx_action_benchmarks = HashMap::new();
+        tx_action_benchmarks.insert(TxAction::MoneyAirdrop, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::MoneyTokenMint, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::MoneyTokenFreeze, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::MoneyGenesisMint, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::MoneyOtcSwap, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::MoneyTransfer, TxActionBenchmarks::new());
+
+        // Alice jumps down the rabbit hole
+        holders.insert(Holder::Alice, alice);
+
+        Ok(Self { holders, proving_keys, tx_action_benchmarks })
+    }
+
+    pub fn statistics(&self) {
+        info!("==================== Statistics ====================");
+        for (action, tx_action_benchmark) in &self.tx_action_benchmarks {
+            tx_action_benchmark.statistics(action);
+        }
+        info!("====================================================");
+    }
+
+    pub fn gather_owncoin(
+        &mut self,
+        holder: Holder,
+        output: Output,
+        secret_key: Option<SecretKey>,
+    ) -> Result<OwnCoin> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let leaf_position = wallet.money_merkle_tree.mark().unwrap();
+        let secret_key = match secret_key {
+            Some(key) => key,
+            None => wallet.keypair.secret,
+        };
+
+        let note: MoneyNote = output.note.decrypt(&secret_key)?;
+        let oc = OwnCoin {
+            coin: output.coin,
+            note: note.clone(),
+            secret: secret_key,
+            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
+            leaf_position,
+        };
+
+        wallet.unspent_money_coins.push(oc.clone());
+
+        Ok(oc)
+    }
+}

+ 112 - 0
src/contract/test-harness/src/money_airdrop.rs

@@ -0,0 +1,112 @@
+/* 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/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, zk::halo2::Field, Result};
+use darkfi_money_contract::{
+    client::transfer_v1::TransferCallBuilder, model::MoneyTransferParamsV1, MoneyFunction,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, DARK_TOKEN_ID, MONEY_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn airdrop_native(
+        &mut self,
+        value: u64,
+        holder: Holder,
+    ) -> Result<(Transaction, MoneyTransferParamsV1)> {
+        let recipient = self.holders.get_mut(&holder).unwrap().keypair.public;
+        let faucet = self.holders.get_mut(&Holder::Faucet).unwrap();
+        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 tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyAirdrop).unwrap();
+        let timer = Instant::now();
+
+        let builder = TransferCallBuilder {
+            keypair: faucet.keypair,
+            recipient,
+            value,
+            token_id: *DARK_TOKEN_ID,
+            rcpt_spend_hook: pallas::Base::ZERO,
+            rcpt_user_data: pallas::Base::ZERO,
+            rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
+            change_spend_hook: pallas::Base::ZERO,
+            change_user_data: pallas::Base::ZERO,
+            change_user_data_blind: pallas::Base::random(&mut OsRng),
+            coins: vec![],
+            tree: faucet.money_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,
+        };
+
+        let debris = builder.build()?;
+
+        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, &debris.signature_secrets)?;
+        tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, debris.params))
+    }
+
+    pub async fn execute_airdrop_native_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &MoneyTransferParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyAirdrop).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
+        assert!(erroneous_txs.is_empty());
+        wallet.money_merkle_tree.append(MerkleNode::from(params.outputs[0].coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 163 - 0
src/contract/test-harness/src/money_token.rs

@@ -0,0 +1,163 @@
+/* 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/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, zk::halo2::Field, Result};
+use darkfi_money_contract::{
+    client::{token_freeze_v1::TokenFreezeCallBuilder, token_mint_v1::TokenMintCallBuilder},
+    model::{MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1},
+    MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, MONEY_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn token_mint(
+        &mut self,
+        amount: u64,
+        holder: Holder,
+        recipient: Holder,
+    ) -> Result<(Transaction, MoneyTokenMintParamsV1)> {
+        let rcpt = self.holders.get_mut(&recipient).unwrap().keypair.public;
+        let mint_authority = self.holders.get_mut(&holder).unwrap().token_mint_authority;
+        let (mint_pk, mint_zkbin) =
+            self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap();
+        let timer = Instant::now();
+
+        let builder = TokenMintCallBuilder {
+            mint_authority,
+            recipient: rcpt,
+            amount,
+            spend_hook: pallas::Base::ZERO,
+            user_data: pallas::Base::ZERO,
+            token_mint_zkbin: mint_zkbin.clone(),
+            token_mint_pk: mint_pk.clone(),
+        };
+
+        let debris = builder.build()?;
+
+        let mut data = vec![MoneyFunction::TokenMintV1 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];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, debris.params))
+    }
+
+    pub async fn execute_token_mint_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &MoneyTokenMintParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
+        assert!(erroneous_txs.is_empty());
+        wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+
+    pub fn token_freeze(
+        &mut self,
+        holder: Holder,
+    ) -> Result<(Transaction, MoneyTokenFreezeParamsV1)> {
+        let mint_authority = self.holders.get_mut(&holder).unwrap().token_mint_authority;
+        let (frz_pk, frz_zkbin) =
+            self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenFreeze).unwrap();
+        let timer = Instant::now();
+
+        let builder = TokenFreezeCallBuilder {
+            mint_authority,
+            token_freeze_zkbin: frz_zkbin.clone(),
+            token_freeze_pk: frz_pk.clone(),
+        };
+
+        let debris = builder.build()?;
+
+        let mut data = vec![MoneyFunction::TokenFreezeV1 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];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, debris.params))
+    }
+
+    pub async fn execute_token_freeze_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        _params: &MoneyTokenFreezeParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenFreeze).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
+        assert!(erroneous_txs.is_empty());
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 195 - 0
src/contract/test-harness/src/vks.rs

@@ -0,0 +1,195 @@
+/* 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/>.
+ */
+
+use std::{
+    fs::File,
+    io::{Read, Write},
+    path::PathBuf,
+    process::Command,
+};
+
+use darkfi::{
+    runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
+    zk::{empty_witnesses, VerifyingKey, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_dao_contract::{
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+};
+use darkfi_deployooor_contract::DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1;
+use darkfi_money_contract::{
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+    CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1, 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,
+};
+use darkfi_sdk::crypto::{
+    contract_id::DEPLOYOOOR_CONTRACT_ID, CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
+};
+use darkfi_serial::{deserialize, serialize};
+use log::debug;
+
+/// Update this if any circuits are changed
+const VKS_HASH: &str = "0a745d4055440b52a9e669264a8cbf51a726d1ca290a4a3c6dc4cad3e711a1f3";
+
+fn vks_path() -> Result<PathBuf> {
+    let output = Command::new("git").arg("rev-parse").arg("--show-toplevel").output()?.stdout;
+    let mut path = PathBuf::from(String::from_utf8(output[..output.len() - 1].to_vec())?);
+    path.push("src");
+    path.push("contract");
+    path.push("test-harness");
+    path.push("vks.bin");
+    Ok(path)
+}
+
+/// (Bincode, Namespace, VK)
+pub type Vks = Vec<(Vec<u8>, String, Vec<u8>)>;
+
+fn read_or_gen_vks() -> Result<Vks> {
+    let vks_path = vks_path()?;
+
+    if vks_path.exists() {
+        debug!("Found vks.bin");
+        let mut f = File::open(vks_path.clone())?;
+        let mut data = vec![];
+        f.read_to_end(&mut data)?;
+
+        let known_hash = blake3::Hash::from_hex(VKS_HASH)?;
+        let found_hash = blake3::hash(&data);
+
+        debug!("Known hash: {}", known_hash);
+        debug!("Found hash: {}", found_hash);
+
+        if known_hash == found_hash {
+            return Ok(deserialize(&data)?)
+        }
+
+        drop(f);
+    }
+
+    let bins = vec![
+        // Money
+        &include_bytes!("../../money/proof/mint_v1.zk.bin")[..],
+        &include_bytes!("../../money/proof/burn_v1.zk.bin")[..],
+        &include_bytes!("../../money/proof/token_mint_v1.zk.bin")[..],
+        &include_bytes!("../../money/proof/token_freeze_v1.zk.bin")[..],
+        // DAO
+        &include_bytes!("../../dao/proof/dao-mint.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-exec.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-propose-burn.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-propose-main.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-vote-burn.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-vote-main.zk.bin")[..],
+        // Consensus
+        &include_bytes!("../../consensus/proof/consensus_burn_v1.zk.bin")[..],
+        &include_bytes!("../../consensus/proof/consensus_mint_v1.zk.bin")[..],
+        &include_bytes!("../../consensus/proof/consensus_proposal_v1.zk.bin")[..],
+        // Deployooor
+        &include_bytes!("../../deployooor/proof/derive_contract_id.zk.bin")[..],
+    ];
+
+    let mut vks = vec![];
+
+    for bincode in bins.iter() {
+        let zkbin = ZkBinary::decode(&bincode)?;
+        debug!("Building VK for {}", zkbin.namespace);
+        let witnesses = empty_witnesses(&zkbin);
+        let circuit = ZkCircuit::new(witnesses, zkbin.clone());
+        let vk = VerifyingKey::build(13, &circuit);
+        let mut vk_buf = vec![];
+        vk.write(&mut vk_buf)?;
+        vks.push((bincode.to_vec(), zkbin.namespace, vk_buf))
+    }
+
+    debug!("Writing to {:?}", vks_path);
+    let mut f = File::create(vks_path)?;
+    let ser = serialize(&vks);
+    let hash = blake3::hash(&ser);
+    debug!("vks.bin {}", hash);
+    f.write_all(&ser)?;
+
+    Ok(vks)
+}
+
+pub(crate) fn inject(sled_db: &sled::Db) -> Result<()> {
+    // Use pregenerated vks
+    let vks = read_or_gen_vks()?;
+
+    // Inject them into the db
+    let money_zkas_tree_ptr = MONEY_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME);
+    let money_zkas_tree = sled_db.open_tree(money_zkas_tree_ptr)?;
+
+    let dao_zkas_tree_ptr = DAO_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME);
+    let dao_zkas_tree = sled_db.open_tree(dao_zkas_tree_ptr)?;
+
+    let consensus_zkas_tree_ptr = CONSENSUS_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME);
+    let consensus_zkas_tree = sled_db.open_tree(consensus_zkas_tree_ptr)?;
+
+    let deployooor_zkas_tree_ptr =
+        DEPLOYOOOR_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME);
+    let deployooor_zkas_tree = sled_db.open_tree(deployooor_zkas_tree_ptr)?;
+
+    for (bincode, namespace, vk) in vks.iter() {
+        match namespace.as_str() {
+            // Money circuits
+            MONEY_CONTRACT_ZKAS_MINT_NS_V1 |
+            MONEY_CONTRACT_ZKAS_BURN_NS_V1 |
+            MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1 |
+            MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1 => {
+                let key = serialize(&namespace.as_str());
+                let value = serialize(&(bincode.clone(), vk.clone()));
+                money_zkas_tree.insert(key, value)?;
+            }
+
+            // Deployooor circuits
+            DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1 => {
+                let key = serialize(&namespace.as_str());
+                let value = serialize(&(bincode.clone(), vk.clone()));
+                deployooor_zkas_tree.insert(key, value)?;
+            }
+
+            // DAO circuits
+            DAO_CONTRACT_ZKAS_DAO_MINT_NS |
+            DAO_CONTRACT_ZKAS_DAO_EXEC_NS |
+            DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS |
+            DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS |
+            DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS |
+            DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS => {
+                let key = serialize(&namespace.as_str());
+                let value = serialize(&(bincode.clone(), vk.clone()));
+                dao_zkas_tree.insert(key, value)?;
+            }
+
+            // Consensus circuits
+            CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1 |
+            CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1 |
+            CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1 => {
+                let key = serialize(&namespace.as_str());
+                let value = serialize(&(bincode.clone(), vk.clone()));
+                consensus_zkas_tree.insert(key, value)?;
+            }
+
+            x => panic!("Found unhandled zkas namespace {}", x),
+        }
+    }
+
+    Ok(())
+}