Parcourir la source

contract/dao: Refactor contract code to match other native contracts.

parazyd il y a 3 ans
Parent
commit
b36861fa1d

+ 1 - 0
Cargo.lock

@@ -1373,6 +1373,7 @@ dependencies = [
  "simplelog",
  "sled",
  "sqlx",
+ "thiserror",
 ]
 
 [[package]]

+ 3 - 3
bin/drk/src/rpc_dao.rs

@@ -23,9 +23,9 @@ use darkfi::{
     zkas::ZkBinary,
 };
 use darkfi_dao_contract::{
-    dao_client,
-    dao_client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
-    dao_model::DaoBlindAggregateVote,
+    client as dao_client,
+    client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
+    model::DaoBlindAggregateVote,
     money_client, DaoFunction, 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,

+ 4 - 2
bin/drk/src/rpc_transfer.rs

@@ -16,6 +16,8 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use std::str::FromStr;
+
 use anyhow::{anyhow, Result};
 use darkfi::{
     tx::Transaction,
@@ -23,7 +25,7 @@ use darkfi::{
     zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
     zkas::ZkBinary,
 };
-use darkfi_dao_contract::dao_model::DaoBulla;
+use darkfi_dao_contract::model::DaoBulla;
 use darkfi_money_contract::{
     client::{transfer_v1::TransferCallBuilder, OwnCoin},
     MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
@@ -56,7 +58,7 @@ impl Drk {
                 return Err(anyhow!("Missing DAO bulla in parameters"))
             };
 
-            Some(DaoBulla::try_from(dao_bulla.as_str())?)
+            Some(DaoBulla::from_str(dao_bulla.as_str())?)
         } else {
             None
         };

+ 5 - 15
bin/drk/src/wallet_dao.rs

@@ -24,7 +24,7 @@ use darkfi::{
     wallet::walletdb::QueryType,
 };
 use darkfi_dao_contract::{
-    dao_client::{
+    client::{
         DaoProposeNote, DaoVoteNote, DAO_DAOS_COL_APPROVAL_RATIO_BASE,
         DAO_DAOS_COL_APPROVAL_RATIO_QUOT, DAO_DAOS_COL_BULLA_BLIND, DAO_DAOS_COL_CALL_INDEX,
         DAO_DAOS_COL_DAO_ID, DAO_DAOS_COL_GOV_TOKEN_ID, DAO_DAOS_COL_LEAF_POSITION,
@@ -39,14 +39,13 @@ use darkfi_dao_contract::{
         DAO_VOTES_COL_PROPOSAL_ID, DAO_VOTES_COL_TX_HASH, DAO_VOTES_COL_VOTE_ID,
         DAO_VOTES_COL_VOTE_OPTION, DAO_VOTES_COL_YES_VOTE_BLIND, DAO_VOTES_TABLE,
     },
-    dao_model::{DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams},
+    model::{DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams},
     DaoFunction,
 };
 use darkfi_sdk::{
     bridgetree,
     crypto::{
-        note::AeadEncryptedNote, poseidon_hash, MerkleNode, MerkleTree, PublicKey, SecretKey,
-        TokenId, DAO_CONTRACT_ID,
+        poseidon_hash, MerkleNode, MerkleTree, PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID,
     },
     pasta::pallas,
 };
@@ -1037,15 +1036,11 @@ impl Drk {
 
             for proposal in new_dao_proposals {
                 proposals_tree.append(MerkleNode::from(proposal.0.proposal_bulla));
-                let enc_note = AeadEncryptedNote {
-                    ciphertext: proposal.0.ciphertext,
-                    ephem_public: proposal.0.ephem_public,
-                };
 
                 // If we're able to decrypt this note, that's the way to link it
                 // to a specific DAO.
                 for dao in &daos {
-                    if let Ok(note) = enc_note.decrypt::<DaoProposeNote>(&dao.secret_key) {
+                    if let Ok(note) = proposal.0.note.decrypt::<DaoProposeNote>(&dao.secret_key) {
                         // We managed to decrypt it. Let's place this in a proper
                         // DaoProposal object. We assume we can just increment the
                         // ID by looking at how many proposals we already have.
@@ -1074,13 +1069,8 @@ impl Drk {
             }
 
             for vote in new_dao_votes {
-                let enc_note = AeadEncryptedNote {
-                    ciphertext: vote.0.ciphertext,
-                    ephem_public: vote.0.ephem_public,
-                };
-
                 for dao in &daos {
-                    if let Ok(note) = enc_note.decrypt::<DaoVoteNote>(&dao.secret_key) {
+                    if let Ok(note) = vote.0.note.decrypt::<DaoVoteNote>(&dao.secret_key) {
                         eprintln!("Managed to decrypt DAO proposal vote note");
                         let daos_proposals = self.get_dao_proposals(dao.id).await?;
                         let mut proposal_id = None;

+ 2 - 1
src/contract/dao/Cargo.toml

@@ -9,10 +9,11 @@ edition = "2021"
 crate-type = ["cdylib", "rlib"]
 
 [dependencies]
+bs58 = "0.5.0"
 darkfi-sdk = { path = "../../sdk" }
 darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
 darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
-bs58 = "0.5.0"
+thiserror = "1.0.40"
 
 # The following dependencies are used for the client API and
 # probably shouldn't be in WASM

+ 1 - 1
src/contract/dao/Makefile

@@ -44,4 +44,4 @@ test-no-run:
 clean:
 	rm -f $(PROOFS_BIN) $(WASM_BIN)
 
-.PHONY: all test clean
+.PHONY: all test test-integration test-no-run clean

+ 3 - 3
src/contract/dao/src/dao_client/exec.rs → src/contract/dao/src/client/exec.rs

@@ -32,7 +32,7 @@ use darkfi::{
 };
 
 use super::{DaoInfo, DaoProposalInfo};
-use crate::dao_model::{DaoBlindAggregateVote, DaoExecParams};
+use crate::model::{DaoBlindAggregateVote, DaoExecParams};
 
 pub struct DaoExecCall {
     pub proposal: DaoProposalInfo,
@@ -184,8 +184,8 @@ impl DaoExecCall {
 
         let params = DaoExecParams {
             proposal: proposal_bulla,
-            coin_0,
-            coin_1,
+            coin_0: coin_0.into(),
+            coin_1: coin_1.into(),
             blind_total_vote: DaoBlindAggregateVote { yes_vote_commit, all_vote_commit },
             input_value_commit,
         };

+ 1 - 1
src/contract/dao/src/dao_client/mint.rs → src/contract/dao/src/client/mint.rs

@@ -28,7 +28,7 @@ use darkfi_sdk::{
 use log::debug;
 use rand::rngs::OsRng;
 
-use crate::dao_model::DaoMintParams;
+use crate::model::DaoMintParams;
 
 #[derive(Clone)]
 pub struct DaoInfo {

+ 0 - 0
src/contract/dao/src/dao_client/mod.rs → src/contract/dao/src/client/mod.rs


+ 2 - 3
src/contract/dao/src/dao_client/propose.rs → src/contract/dao/src/client/propose.rs

@@ -34,7 +34,7 @@ use darkfi::{
     Result,
 };
 
-use crate::dao_model::{DaoProposeParams, DaoProposeParamsInput};
+use crate::model::{DaoProposeParams, DaoProposeParamsInput};
 
 use super::DaoInfo;
 
@@ -249,8 +249,7 @@ impl DaoProposeCall {
             dao_merkle_root: self.dao_merkle_root,
             proposal_bulla,
             token_commit,
-            ciphertext: enc_note.ciphertext,
-            ephem_public: enc_note.ephem_public,
+            note: enc_note,
             inputs,
         };
 

+ 3 - 9
src/contract/dao/src/dao_client/vote.rs → src/contract/dao/src/client/vote.rs

@@ -36,7 +36,7 @@ use darkfi::{
 };
 
 use super::{DaoInfo, DaoProposalInfo};
-use crate::dao_model::{DaoVoteParams, DaoVoteParamsInput};
+use crate::model::{DaoVoteParams, DaoVoteParamsInput};
 
 #[derive(SerialEncodable, SerialDecodable)]
 pub struct DaoVoteNote {
@@ -270,14 +270,8 @@ impl DaoVoteCall {
         let enc_note =
             AeadEncryptedNote::encrypt(&note, &self.vote_keypair.public, &mut OsRng).unwrap();
 
-        let params = DaoVoteParams {
-            token_commit,
-            proposal_bulla,
-            yes_vote_commit,
-            ciphertext: enc_note.ciphertext,
-            ephem_public: enc_note.ephem_public,
-            inputs,
-        };
+        let params =
+            DaoVoteParams { token_commit, proposal_bulla, yes_vote_commit, note: enc_note, inputs };
 
         Ok((params, proofs))
     }

+ 0 - 172
src/contract/dao/src/dao_model.rs

@@ -1,172 +0,0 @@
-/* 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 darkfi_sdk::{
-    crypto::{pasta_prelude::*, MerkleNode, Nullifier, PublicKey},
-    error::ContractError,
-    pasta::pallas,
-};
-use darkfi_serial::{SerialDecodable, SerialEncodable};
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct DaoBulla(pallas::Base);
-
-impl DaoBulla {
-    pub fn inner(&self) -> pallas::Base {
-        self.0
-    }
-}
-
-impl From<pallas::Base> for DaoBulla {
-    fn from(x: pallas::Base) -> Self {
-        Self(x)
-    }
-}
-
-impl TryFrom<&str> for DaoBulla {
-    type Error = ContractError;
-
-    fn try_from(s: &str) -> Result<Self, Self::Error> {
-        let bytes: [u8; 32] = match bs58::decode(s).into_vec() {
-            Ok(v) => {
-                if v.len() != 32 {
-                    return Err(ContractError::IoError(
-                        "Decoded bs58 string for DaoBulla is not 32 bytes long".to_string(),
-                    ))
-                }
-
-                v.try_into().unwrap()
-            }
-            Err(e) => {
-                return Err(ContractError::IoError(format!(
-                    "Failed to decode bs58 for DaoBulla: {}",
-                    e
-                )))
-            }
-        };
-
-        match pallas::Base::from_repr(bytes).into() {
-            Some(v) => Ok(Self(v)),
-            None => Err(ContractError::IoError("Bytes for DaoBulla are noncanonical".to_string())),
-        }
-    }
-}
-
-impl core::fmt::Display for DaoBulla {
-    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-        let disp: String = bs58::encode(self.0.to_repr()).into_string();
-        write!(f, "{}", disp)
-    }
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoMintParams {
-    pub dao_bulla: DaoBulla,
-    pub dao_pubkey: PublicKey,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoMintUpdate {
-    pub dao_bulla: DaoBulla,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoProposeParams {
-    pub dao_merkle_root: MerkleNode,
-    pub token_commit: pallas::Base,
-    pub proposal_bulla: pallas::Base,
-    pub ciphertext: Vec<u8>,
-    pub ephem_public: PublicKey,
-    pub inputs: Vec<DaoProposeParamsInput>,
-}
-
-#[derive(Clone, SerialEncodable, SerialDecodable)]
-pub struct DaoProposeParamsInput {
-    pub value_commit: pallas::Point,
-    pub merkle_root: MerkleNode,
-    pub signature_public: PublicKey,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoProposeUpdate {
-    pub proposal_bulla: pallas::Base,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoVoteParams {
-    pub token_commit: pallas::Base,
-    pub proposal_bulla: pallas::Base,
-    pub yes_vote_commit: pallas::Point,
-    pub ciphertext: Vec<u8>,
-    pub ephem_public: PublicKey,
-    pub inputs: Vec<DaoVoteParamsInput>,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoVoteParamsInput {
-    pub nullifier: Nullifier,
-    pub vote_commit: pallas::Point,
-    pub merkle_root: MerkleNode,
-    pub signature_public: PublicKey,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoVoteUpdate {
-    pub proposal_bulla: pallas::Base,
-    pub proposal_votes: DaoBlindAggregateVote,
-    pub vote_nullifiers: Vec<Nullifier>,
-}
-
-/// Represents a single or multiple blinded votes. These can be summed together.
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoBlindAggregateVote {
-    /// Weighted vote commit
-    pub yes_vote_commit: pallas::Point,
-    /// All value staked in the vote
-    pub all_vote_commit: pallas::Point,
-}
-
-impl DaoBlindAggregateVote {
-    pub fn aggregate(&mut self, other: Self) {
-        self.yes_vote_commit += other.yes_vote_commit;
-        self.all_vote_commit += other.all_vote_commit;
-    }
-}
-
-impl Default for DaoBlindAggregateVote {
-    fn default() -> Self {
-        Self {
-            yes_vote_commit: pallas::Point::identity(),
-            all_vote_commit: pallas::Point::identity(),
-        }
-    }
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoExecParams {
-    pub proposal: pallas::Base,
-    pub coin_0: pallas::Base,
-    pub coin_1: pallas::Base,
-    pub blind_total_vote: DaoBlindAggregateVote,
-    pub input_value_commit: pallas::Point,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-pub struct DaoExecUpdate {
-    pub proposal: pallas::Base,
-}

+ 106 - 452
src/contract/dao/src/entrypoint.rs

@@ -19,35 +19,40 @@
 use std::io::Cursor;
 
 use darkfi_sdk::{
-    crypto::{
-        pasta_prelude::*, ContractId, MerkleNode, MerkleTree, PublicKey, DAO_CONTRACT_ID,
-        MONEY_CONTRACT_ID,
-    },
-    db::{db_contains_key, db_del, db_get, db_init, db_lookup, db_set, zkas_db_set},
+    crypto::{ContractId, MerkleTree},
+    db::{db_get, db_init, db_lookup, db_set, zkas_db_set},
     error::{ContractError, ContractResult},
-    merkle_add, msg,
-    pasta::pallas,
+    msg,
     util::set_return_data,
     ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
 
-use darkfi_money_contract::{
-    model::MoneyTransferParamsV1 as MoneyTransferParams,
-    MoneyFunction::TransferV1 as MoneyTransfer, MONEY_CONTRACT_COIN_ROOTS_TREE,
-    MONEY_CONTRACT_NULLIFIERS_TREE,
+use crate::{
+    model::{DaoExecUpdate, DaoMintUpdate, DaoProposeUpdate, DaoVoteUpdate},
+    DaoFunction, DAO_CONTRACT_DB_DAO_BULLAS, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS,
+    DAO_CONTRACT_DB_INFO_TREE, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_DB_VOTE_NULLIFIERS,
+    DAO_CONTRACT_KEY_DAO_MERKLE_TREE, DAO_CONTRACT_KEY_DB_VERSION,
 };
 
-use crate::{
-    dao_model::{
-        DaoBlindAggregateVote, DaoExecParams, DaoExecUpdate, DaoMintParams, DaoMintUpdate,
-        DaoProposeParams, DaoProposeUpdate, DaoVoteParams, DaoVoteUpdate,
-    },
-    DaoFunction, 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,
+/// `Dao::Mint` functions
+mod mint;
+use mint::{dao_mint_get_metadata, dao_mint_process_instruction, dao_mint_process_update};
+
+/// `Dao::Propose` functions
+mod propose;
+use propose::{
+    dao_propose_get_metadata, dao_propose_process_instruction, dao_propose_process_update,
 };
 
+/// `Dao::Vote` functions
+mod vote;
+use vote::{dao_vote_get_metadata, dao_vote_process_instruction, dao_vote_process_update};
+
+/// `Dao::Exec` functions
+mod exec;
+use exec::{dao_exec_get_metadata, dao_exec_process_instruction, dao_exec_process_update};
+
 darkfi_sdk::define_contract!(
     init: init_contract,
     exec: process_instruction,
@@ -55,21 +60,10 @@ darkfi_sdk::define_contract!(
     metadata: get_metadata
 );
 
-/// General info for the DAO
-pub const DB_INFO: &str = "dao_info";
-/// Name of the DAO bulla tree in DB_INFO
-pub const KEY_DAO_MERKLE_TREE: &str = "dao_merkle_tree";
-
-/// DAO bullas
-pub const DB_DAO_BULLAS: &str = "dao_bullas";
-/// Keeps track of all merkle roots DAO bullas
-pub const DB_DAO_MERKLE_ROOTS: &str = "dao_roots";
-
-/// Proposal bullas. The key is the current aggregated vote
-pub const DB_PROPOSAL_BULLAS: &str = "dao_proposals";
-/// Nullifiers to prevent double voting
-pub const DAO_VOTE_NULLS: &str = "dao_vote_nulls";
-
+/// This entrypoint function runs when the contract is (re)deployed and initialized.
+/// We use this function to initialize all the necessary databases and prepare them
+/// with initial data if necessary. This is also the place where we bundle the zkas
+/// circuits that are to be used with functions provided by the contract.
 fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     // The zkas circuits can simply be embedded in the wasm and set up by
     // the initialization.
@@ -80,20 +74,20 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     zkas_db_set(&include_bytes!("../proof/dao-propose-burn.zk.bin")[..])?;
     zkas_db_set(&include_bytes!("../proof/dao-propose-main.zk.bin")[..])?;
 
-    // Setup db for general info
-    let dao_info_db = match db_lookup(cid, DB_INFO) {
+    // Set up db for general info
+    let dao_info_db = match db_lookup(cid, DAO_CONTRACT_DB_INFO_TREE) {
         Ok(v) => v,
-        Err(_) => db_init(cid, DB_INFO)?,
+        Err(_) => db_init(cid, DAO_CONTRACT_DB_INFO_TREE)?,
     };
 
-    // Setup the entries in the header table
-    match db_get(dao_info_db, &serialize(&KEY_DAO_MERKLE_TREE))? {
+    // Set up the entries in the header table
+    match db_get(dao_info_db, &serialize(&DAO_CONTRACT_KEY_DAO_MERKLE_TREE))? {
         Some(bytes) => {
             // We found some bytes, try to deserialize into a tree.
             // For now, if this doesn't work, we bail.
             let mut decoder = Cursor::new(&bytes);
             <u32 as Decodable>::decode(&mut decoder)?;
-            <Vec<MerkleTree> as Decodable>::decode(&mut decoder)?;
+            <MerkleTree as Decodable>::decode(&mut decoder)?;
         }
         None => {
             // We didn't find a tree, so just make a new one.
@@ -103,475 +97,135 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
             tree_data.write_u32(0)?;
             tree.encode(&mut tree_data)?;
 
-            db_set(dao_info_db, &serialize(&KEY_DAO_MERKLE_TREE), &tree_data)?;
+            db_set(dao_info_db, &serialize(&DAO_CONTRACT_KEY_DAO_MERKLE_TREE), &tree_data)?;
         }
-    };
+    }
 
-    // Setup db to avoid double creating DAOs
-    let _ = match db_lookup(cid, DB_DAO_BULLAS) {
+    // Set up db to avoid double creating DAOs
+    let _ = match db_lookup(cid, DAO_CONTRACT_DB_DAO_BULLAS) {
         Ok(v) => v,
-        Err(_) => db_init(cid, DB_DAO_BULLAS)?,
+        Err(_) => db_init(cid, DAO_CONTRACT_DB_DAO_BULLAS)?,
     };
 
-    // Setup db for DAO bulla merkle roots
-    let _ = match db_lookup(cid, DB_DAO_MERKLE_ROOTS) {
+    // Set up db for DAO bulla Merkle roots
+    let _ = match db_lookup(cid, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS) {
         Ok(v) => v,
-        Err(_) => db_init(cid, DB_DAO_MERKLE_ROOTS)?,
+        Err(_) => db_init(cid, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS)?,
     };
 
-    // Setup db for proposal votes (k: ProposalBulla, v: BlindAggregateVote)
-    let _ = match db_lookup(cid, DB_PROPOSAL_BULLAS) {
+    // Set up db for proposal votes
+    // k: ProposalBulla
+    // v: (BlindAggregateVote, bool) (the bool marks if the proposal is finished)
+    let _ = match db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS) {
         Ok(v) => v,
-        Err(_) => db_init(cid, DB_PROPOSAL_BULLAS)?,
+        Err(_) => db_init(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?,
     };
 
-    let _ = match db_lookup(cid, DAO_VOTE_NULLS) {
+    // TODO: These nullifiers should exist per-proposal, we also need to snapshot
+    //       the money state do avoid double-vote
+    let _ = match db_lookup(cid, DAO_CONTRACT_DB_VOTE_NULLIFIERS) {
         Ok(v) => v,
-        Err(_) => db_init(cid, DAO_VOTE_NULLS)?,
+        Err(_) => db_init(cid, DAO_CONTRACT_DB_VOTE_NULLIFIERS)?,
     };
 
+    // Update db version
+    db_set(
+        dao_info_db,
+        &serialize(&DAO_CONTRACT_KEY_DB_VERSION),
+        &serialize(&env!("CARGO_PKG_VERSION")),
+    )?;
+
     Ok(())
 }
 
-fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
-    let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
-    assert!(call_idx < call.len() as u32);
-
-    let self_ = &call[call_idx as usize];
-    let func = DaoFunction::try_from(self_.data[0])?;
-
-    if call.len() != 1 {
-        // Enforce a strict structure for our tx
-        assert_eq!(call.len(), 2);
-        assert_eq!(call_idx, 1);
-
-        // We can unpack user_data and check the function call is correct.
-        // But in this contract, only DAO::exec() can be invoked by other ones.
-        // So just check the function call is correct.
-
-        // NOTE: we may wish to improve this since it cripples user composability.
-
-        assert_eq!(func, DaoFunction::Exec);
+/// This function is used by the wasm VM's host to fetch the necessary metadata
+/// for verifying signatures and ZK proofs. The payload given here are all the
+/// contract calls in the transaction.
+fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx >= calls.len() as u32 {
+        msg!("[DAO:get_metadata()] Error: call_idx >= calls.len()");
+        return Err(ContractError::Internal)
     }
 
-    match func {
+    match DaoFunction::try_from(calls[call_idx as usize].data[0])? {
         DaoFunction::Mint => {
-            let params: DaoMintParams = deserialize(&self_.data[1..])?;
-            let dao_bulla = params.dao_bulla.inner();
-
-            // Check the DAO bulla doesn't already exist
-            let bulla_db = db_lookup(cid, DB_DAO_BULLAS)?;
-            if db_contains_key(bulla_db, &serialize(&dao_bulla))? {
-                msg!("DAO already exists: {:?}", dao_bulla);
-                return Err(ContractError::Custom(1))
-            }
-
-            let update = DaoMintUpdate { dao_bulla: params.dao_bulla };
-            let mut update_data = vec![];
-            update_data.write_u8(DaoFunction::Mint as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[DAO Mint] State update set!");
-
-            Ok(())
+            let metadata = dao_mint_get_metadata(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
         }
 
         DaoFunction::Propose => {
-            let params: DaoProposeParams = deserialize(&self_.data[1..])?;
-
-            // Check the Merkle roots for the input coins are valid
-            let money_cid = *MONEY_CONTRACT_ID;
-            let coin_roots_db = db_lookup(money_cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-            for input in &params.inputs {
-                if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-                    msg!("Invalid input Merkle root: {}", input.merkle_root);
-                    return Err(ContractError::Custom(2))
-                }
-            }
-
-            // Is the DAO bulla generated in the ZK proof valid
-            let dao_roots_db = db_lookup(cid, DB_DAO_MERKLE_ROOTS)?;
-            if !db_contains_key(dao_roots_db, &serialize(&params.dao_merkle_root))? {
-                msg!("Invalid DAO Merkle root: {}", params.dao_merkle_root);
-                return Err(ContractError::Custom(3))
-            }
-
-            let proposal_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            // Make sure proposal doesn't already exist
-            // Otherwise it will reset voting again
-            if db_contains_key(proposal_db, &serialize(&params.proposal_bulla))? {
-                msg!("Proposal already exists: {:?}", params.proposal_bulla);
-                return Err(ContractError::Custom(4))
-            }
-
-            let update = DaoProposeUpdate { proposal_bulla: params.proposal_bulla };
-            let mut update_data = vec![];
-            update_data.write_u8(DaoFunction::Propose as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[DAO Propose] State update set!");
-
-            Ok(())
+            let metadata = dao_propose_get_metadata(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
         }
 
         DaoFunction::Vote => {
-            let params: DaoVoteParams = deserialize(&self_.data[1..])?;
-
-            let money_cid = *MONEY_CONTRACT_ID;
-
-            // Check proposal bulla exists
-            let proposal_votes_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            let Some(proposal_votes) = db_get(proposal_votes_db, &serialize(&params.proposal_bulla))? else {
-                msg!("Invalid proposal {:?}", params.proposal_bulla);
-                return Err(ContractError::Custom(4))
-            };
-            let mut proposal_votes: DaoBlindAggregateVote = deserialize(&proposal_votes)?;
-
-            // Check the Merkle roots and nullifiers for the input coins are valid
-            let money_roots_db = db_lookup(money_cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-            let money_nullifier_db = db_lookup(money_cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-            let dao_vote_nulls_db = db_lookup(cid, DAO_VOTE_NULLS)?;
-
-            let mut vote_nullifiers = vec![];
-
-            for input in &params.inputs {
-                if !db_contains_key(money_roots_db, &serialize(&input.merkle_root))? {
-                    msg!("Invalid input Merkle root: {:?}", input.merkle_root);
-                    return Err(ContractError::Custom(5))
-                }
-
-                if db_contains_key(money_nullifier_db, &serialize(&input.nullifier))? {
-                    msg!("Coin is already spent");
-                    return Err(ContractError::Custom(6))
-                }
-
-                // Prefix nullifier with proposal bulla so nullifiers from different proposals
-                // don't interfere with each other.
-                let null_key = serialize(&(params.proposal_bulla, input.nullifier));
-
-                if vote_nullifiers.contains(&input.nullifier) ||
-                    db_contains_key(dao_vote_nulls_db, &null_key)?
-                {
-                    msg!("Attempted double vote");
-                    return Err(ContractError::Custom(7))
-                }
-
-                proposal_votes.all_vote_commit += input.vote_commit;
-                vote_nullifiers.push(input.nullifier);
-            }
-
-            proposal_votes.yes_vote_commit += params.yes_vote_commit;
-
-            let update = DaoVoteUpdate {
-                proposal_bulla: params.proposal_bulla,
-                proposal_votes,
-                vote_nullifiers,
-            };
-            let mut update_data = vec![];
-            update_data.write_u8(DaoFunction::Vote as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[DAO Vote] State update set!");
-
-            Ok(())
+            let metadata = dao_vote_get_metadata(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
         }
 
         DaoFunction::Exec => {
-            let params: DaoExecParams = deserialize(&self_.data[1..])?;
-
-            // =============================
-            // Enforce tx has correct format
-            // =============================
-            // 1. There should be only two calls
-            assert!(call.len() == 2);
-
-            // 2. func_call_index == 1
-            assert!(call_idx == 1);
-
-            // 3. First item should be a MoneyTransfer call
-            assert!(call[0].contract_id == *MONEY_CONTRACT_ID);
-            assert!(call[0].data[0] == MoneyTransfer as u8);
-
-            // 4. MoneyTransfer has exactly 2 outputs
-            let mt_params: MoneyTransferParams = deserialize(&call[0].data[1..])?;
-            assert!(mt_params.outputs.len() == 2);
-
-            // ======
-            // Checks
-            // ======
-            // 1. Check both coins in MoneyTransfer are equal to our coin_0, coin_1
-            assert!(mt_params.outputs[0].coin.inner() == params.coin_0);
-            assert!(mt_params.outputs[1].coin.inner() == params.coin_1);
-
-            // 2. Sum of MoneyTransfer input value commits == our input value commit
-            let mut input_valcoms = pallas::Point::identity();
-            for input in mt_params.inputs {
-                input_valcoms += input.value_commit;
-            }
-            assert!(input_valcoms == params.input_value_commit);
-
-            // 3. Get the ProposalVote from DAO state
-            let proposal_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            let Some(proposal_votes) = db_get(proposal_db, &serialize(&params.proposal))? else {
-                msg!("Proposal {:?} not found in db", params.proposal);
-                return Err(ContractError::Custom(1));
-            };
-            let proposal_votes: DaoBlindAggregateVote = deserialize(&proposal_votes)?;
-
-            // 4. Check yes_vote_commit and all_vote_commit are the same as in BlindAggregateVote
-            assert!(proposal_votes.yes_vote_commit == params.blind_total_vote.yes_vote_commit);
-            assert!(proposal_votes.all_vote_commit == params.blind_total_vote.all_vote_commit);
-
-            let update = DaoExecUpdate { proposal: params.proposal };
-            let mut update_data = vec![];
-            update_data.write_u8(DaoFunction::Exec as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[DAO Exec] State update set!");
-
-            Ok(())
+            let metadata = dao_exec_get_metadata(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
         }
     }
 }
 
-fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
-    match DaoFunction::try_from(ix[0])? {
-        DaoFunction::Mint => {
-            let update: DaoMintUpdate = deserialize(&ix[1..])?;
-            let dao_bulla = update.dao_bulla.inner();
-
-            let info_db = db_lookup(cid, DB_INFO)?;
-            let bulla_db = db_lookup(cid, DB_DAO_BULLAS)?;
-            let roots_db = db_lookup(cid, DB_DAO_MERKLE_ROOTS)?;
-
-            db_set(bulla_db, &serialize(&dao_bulla), &[])?;
-
-            let node = MerkleNode::from(dao_bulla);
-            merkle_add(info_db, roots_db, &serialize(&KEY_DAO_MERKLE_TREE), &[node])?;
+/// This function verifies a state transition and produces a state update
+/// if everything is successful.
+fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx >= calls.len() as u32 {
+        msg!("[DAO::process_instruction()] Error: call_idx >= calls.len()");
+        return Err(ContractError::Internal)
+    }
 
-            Ok(())
+    match DaoFunction::try_from(calls[call_idx as usize].data[0])? {
+        DaoFunction::Mint => {
+            let update_data = dao_mint_process_instruction(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
         }
 
         DaoFunction::Propose => {
-            let update: DaoProposeUpdate = deserialize(&ix[1..])?;
-
-            let proposal_vote_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            let pv = DaoBlindAggregateVote::default();
-
-            db_set(proposal_vote_db, &serialize(&update.proposal_bulla), &serialize(&pv))?;
-
-            Ok(())
+            let update_data = dao_propose_process_instruction(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
         }
 
         DaoFunction::Vote => {
-            let update: DaoVoteUpdate = deserialize(&ix[1..])?;
-
-            // Perform this code:
-            //   total_yes_vote_commit += update.yes_vote_commit
-            //   total_all_vote_commit += update.all_vote_commit
-
-            let proposal_vote_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            db_set(
-                proposal_vote_db,
-                &serialize(&update.proposal_bulla),
-                &serialize(&update.proposal_votes),
-            )?;
-
-            // We are essentially doing: vote_nulls.append(update.nulls)
-
-            let dao_vote_nulls_db = db_lookup(cid, DAO_VOTE_NULLS)?;
-
-            for nullifier in update.vote_nullifiers {
-                // Uniqueness is enforced for (proposal_bulla, nullifier)
-                let key = serialize(&(update.proposal_bulla, nullifier));
-                db_set(dao_vote_nulls_db, &key, &[])?;
-            }
-
-            Ok(())
+            let update_data = dao_vote_process_instruction(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
         }
 
         DaoFunction::Exec => {
-            let update: DaoExecUpdate = deserialize(&ix[1..])?;
-
-            // Remove proposal from db
-            let proposal_vote_db = db_lookup(cid, DB_PROPOSAL_BULLAS)?;
-            db_del(proposal_vote_db, &serialize(&update.proposal))?;
-
-            Ok(())
+            let update_data = dao_exec_process_instruction(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
         }
     }
 }
 
-fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
-    let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
-    assert!(call_idx < call.len() as u32);
-
-    let self_ = &call[call_idx as usize];
-
-    match DaoFunction::try_from(self_.data[0])? {
+/// This function attempts to write a given state update provided the previous
+/// steps of the contract call execution were successful. The payload given to
+/// the functioon is the update data retrieved from `process_instruction()`.
+fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
+    match DaoFunction::try_from(update_data[0])? {
         DaoFunction::Mint => {
-            let params: DaoMintParams = deserialize(&self_.data[1..])?;
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-            let signature_pubkeys: Vec<PublicKey> = vec![params.dao_pubkey];
-
-            let (pub_x, pub_y) = params.dao_pubkey.xy();
-
-            zk_public_values.push((
-                DAO_CONTRACT_ZKAS_DAO_MINT_NS.to_string(),
-                vec![pub_x, pub_y, params.dao_bulla.inner()],
-            ));
-
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-            signature_pubkeys.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
+            let update: DaoMintUpdate = deserialize(&update_data[1..])?;
+            Ok(dao_mint_process_update(cid, update)?)
         }
 
         DaoFunction::Propose => {
-            let params: DaoProposeParams = deserialize(&self_.data[1..])?;
-            assert!(!params.inputs.is_empty());
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-            let mut signature_pubkeys: Vec<PublicKey> = vec![];
-
-            let mut total_funds_commit = pallas::Point::identity();
-
-            for input in &params.inputs {
-                signature_pubkeys.push(input.signature_public);
-                total_funds_commit += input.value_commit;
-
-                let value_coords = input.value_commit.to_affine().coordinates().unwrap();
-                let (sig_x, sig_y) = input.signature_public.xy();
-
-                zk_public_values.push((
-                    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS.to_string(),
-                    vec![
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        params.token_commit,
-                        input.merkle_root.inner(),
-                        sig_x,
-                        sig_y,
-                    ],
-                ));
-            }
-
-            let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
-            zk_public_values.push((
-                DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS.to_string(),
-                vec![
-                    params.token_commit,
-                    params.dao_merkle_root.inner(),
-                    params.proposal_bulla,
-                    *total_funds_coords.x(),
-                    *total_funds_coords.y(),
-                ],
-            ));
-
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-            signature_pubkeys.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
+            let update: DaoProposeUpdate = deserialize(&update_data[1..])?;
+            Ok(dao_propose_process_update(cid, update)?)
         }
 
         DaoFunction::Vote => {
-            let params: DaoVoteParams = deserialize(&self_.data[1..])?;
-            assert!(!params.inputs.is_empty());
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-            let mut signature_pubkeys: Vec<PublicKey> = vec![];
-
-            let mut all_vote_commit = pallas::Point::identity();
-
-            for input in &params.inputs {
-                signature_pubkeys.push(input.signature_public);
-                all_vote_commit += input.vote_commit;
-
-                let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
-                let (sig_x, sig_y) = input.signature_public.xy();
-
-                zk_public_values.push((
-                    DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS.to_string(),
-                    vec![
-                        input.nullifier.inner(),
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        params.token_commit,
-                        input.merkle_root.inner(),
-                        sig_x,
-                        sig_y,
-                    ],
-                ));
-            }
-
-            let yes_vote_commit_coords = params.yes_vote_commit.to_affine().coordinates().unwrap();
-            let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
-
-            zk_public_values.push((
-                DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS.to_string(),
-                vec![
-                    params.token_commit,
-                    params.proposal_bulla,
-                    *yes_vote_commit_coords.x(),
-                    *yes_vote_commit_coords.y(),
-                    *all_vote_commit_coords.x(),
-                    *all_vote_commit_coords.y(),
-                ],
-            ));
-
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-            signature_pubkeys.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
+            let update: DaoVoteUpdate = deserialize(&update_data[1..])?;
+            Ok(dao_vote_process_update(cid, update)?)
         }
 
         DaoFunction::Exec => {
-            let params: DaoExecParams = deserialize(&self_.data[1..])?;
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-            let signature_pubkeys: Vec<PublicKey> = vec![];
-
-            let blind_vote = params.blind_total_vote;
-            let yes_vote_coords = blind_vote.yes_vote_commit.to_affine().coordinates().unwrap();
-            let all_vote_coords = blind_vote.all_vote_commit.to_affine().coordinates().unwrap();
-            let input_value_coords = params.input_value_commit.to_affine().coordinates().unwrap();
-
-            msg!("params.proposal: {:?}", params.proposal);
-            zk_public_values.push((
-                DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
-                vec![
-                    params.proposal,
-                    params.coin_0,
-                    params.coin_1,
-                    *yes_vote_coords.x(),
-                    *yes_vote_coords.y(),
-                    *all_vote_coords.x(),
-                    *all_vote_coords.y(),
-                    *input_value_coords.x(),
-                    *input_value_coords.y(),
-                    DAO_CONTRACT_ID.inner(),
-                    pallas::Base::zero(),
-                    pallas::Base::zero(),
-                ],
-            ));
-
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-            signature_pubkeys.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
+            let update: DaoExecUpdate = deserialize(&update_data[1..])?;
+            Ok(dao_exec_process_update(cid, update)?)
         }
     }
 }

+ 168 - 0
src/contract/dao/src/entrypoint/exec.rs

@@ -0,0 +1,168 @@
+/* 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 darkfi_money_contract::{model::MoneyTransferParamsV1, MoneyFunction};
+use darkfi_sdk::{
+    crypto::{contract_id::MONEY_CONTRACT_ID, pasta_prelude::*, ContractId, PublicKey},
+    db::{db_del, db_get, db_lookup},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::DaoError,
+    model::{DaoBlindAggregateVote, DaoExecParams, DaoExecUpdate},
+    DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+};
+
+/// `get_metdata` function for `Dao::Exec`
+pub(crate) fn dao_exec_get_metadata(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoExecParams = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let signature_pubkeys: Vec<PublicKey> = vec![];
+
+    let blind_vote = params.blind_total_vote;
+    let yes_vote_coords = blind_vote.yes_vote_commit.to_affine().coordinates().unwrap();
+    let all_vote_coords = blind_vote.all_vote_commit.to_affine().coordinates().unwrap();
+    let input_value_coords = params.input_value_commit.to_affine().coordinates().unwrap();
+
+    zk_public_inputs.push((
+        DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
+        vec![
+            params.proposal,
+            params.coin_0.inner(),
+            params.coin_1.inner(),
+            *yes_vote_coords.x(),
+            *yes_vote_coords.y(),
+            *all_vote_coords.x(),
+            *all_vote_coords.y(),
+            *input_value_coords.x(),
+            *input_value_coords.y(),
+            cid.inner(),
+            pallas::Base::ZERO,
+            pallas::Base::ZERO,
+        ],
+    ));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Dao::Exec`
+pub(crate) fn dao_exec_process_instruction(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoExecParams = deserialize(&self_.data[1..])?;
+
+    // ==========================================
+    // Enforce the transaction has correct format
+    // ==========================================
+    if calls.len() != 2 ||
+        call_idx != 1 ||
+        calls[0].contract_id != *MONEY_CONTRACT_ID ||
+        calls[0].data[0] != MoneyFunction::TransferV1 as u8
+    {
+        msg!("[Dao::Exec] Error: Transaction has incorrect format");
+        return Err(DaoError::ExecCallInvalidFormat.into())
+    }
+
+    // MoneyTransfer should have exactly 2 outputs
+    let mt_params: MoneyTransferParamsV1 = deserialize(&calls[0].data[1..])?;
+    if mt_params.outputs.len() != 2 {
+        msg!("[Dao::Exec] Error: Money outputs != 2");
+        return Err(DaoError::ExecCallInvalidFormat.into())
+    }
+
+    // ======
+    // Checks
+    // ======
+    // 1. Check coins in MoneyTransfer are the same as our coin 0 and coin 1
+    if mt_params.outputs[0].coin != params.coin_0 ||
+        mt_params.outputs[1].coin != params.coin_1 ||
+        mt_params.outputs.len() != 2
+    {
+        msg!("[Dao::Exec] Error: Coin commitments mismatch");
+        return Err(DaoError::ExecCallOutputsMismatch.into())
+    }
+
+    // 2. Sum of MoneyTransfer input value commits == our input value commit
+    let mut input_valcoms = pallas::Point::identity();
+    for input in &mt_params.inputs {
+        input_valcoms += input.value_commit;
+    }
+    if input_valcoms != params.input_value_commit {
+        msg!("[Dao::Exec] Error: Value commitments mismatch");
+        return Err(DaoError::ExecCallValueMismatch.into())
+    }
+
+    // 3. Get the ProposalVote from DAO state
+    let proposal_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+    let Some(data) = db_get(proposal_db, &serialize(&params.proposal))? else {
+        msg!("[Dao::Exec] Error: Proposal {:?} not found", params.proposal);
+        return Err(DaoError::ProposalNonexistent.into())
+    };
+    let (proposal_votes, ended): (DaoBlindAggregateVote, bool) = deserialize(&data)?;
+
+    if ended {
+        msg!("[Dao::Exec] Error: Proposal {:?} ended", params.proposal);
+        return Err(DaoError::ProposalEnded.into())
+    }
+
+    // 4. Check yes_vote commit and all_vote_commit are the same as in BlindAggregateVote
+    if proposal_votes.yes_vote_commit != params.blind_total_vote.yes_vote_commit ||
+        proposal_votes.all_vote_commit != params.blind_total_vote.all_vote_commit
+    {
+        return Err(DaoError::VoteCommitMismatch.into())
+    }
+
+    // Create state update
+    let update = DaoExecUpdate { proposal: params.proposal };
+    let mut update_data = vec![];
+    update_data.write_u8(DaoFunction::Exec as u8)?;
+    update.encode(&mut update_data)?;
+    Ok(update_data)
+}
+
+/// `process_update` function for `Dao::Exec`
+pub(crate) fn dao_exec_process_update(cid: ContractId, update: DaoExecUpdate) -> ContractResult {
+    // Grab all db handles we want to work on
+    let proposal_vote_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+
+    // Remove proposal from db
+    db_del(proposal_vote_db, &serialize(&update.proposal))?;
+
+    Ok(())
+}

+ 104 - 0
src/contract/dao/src/entrypoint/mint.rs

@@ -0,0 +1,104 @@
+/* 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 darkfi_sdk::{
+    crypto::{ContractId, MerkleNode, PublicKey},
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    merkle_add, msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::DaoError,
+    model::{DaoMintParams, DaoMintUpdate},
+    DaoFunction, DAO_CONTRACT_DB_DAO_BULLAS, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS,
+    DAO_CONTRACT_DB_INFO_TREE, DAO_CONTRACT_KEY_DAO_MERKLE_TREE, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+};
+
+/// `get_metadata` function for `Dao::Mint`
+pub(crate) fn dao_mint_get_metadata(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoMintParams = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let signature_pubkeys: Vec<PublicKey> = vec![params.dao_pubkey];
+
+    // In this Mint ZK proof, we constrain the DAO bulla and the signature pubkey
+    let (pub_x, pub_y) = params.dao_pubkey.xy();
+
+    zk_public_inputs.push((
+        DAO_CONTRACT_ZKAS_DAO_MINT_NS.to_string(),
+        vec![pub_x, pub_y, params.dao_bulla.inner()],
+    ));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Dao::Mint`
+pub(crate) fn dao_mint_process_instruction(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoMintParams = deserialize(&self_.data[1..])?;
+
+    // Check the DAO bulla doesn't already exist
+    let bulla_db = db_lookup(cid, DAO_CONTRACT_DB_DAO_BULLAS)?;
+    if db_contains_key(bulla_db, &serialize(&params.dao_bulla.inner()))? {
+        msg!("[DAO::Mint] Error: DAO already exists {}", params.dao_bulla);
+        return Err(DaoError::DaoAlreadyExists.into())
+    }
+
+    // Create state update
+    let update = DaoMintUpdate { dao_bulla: params.dao_bulla };
+    let mut update_data = vec![];
+    update_data.write_u8(DaoFunction::Mint as u8)?;
+    update.encode(&mut update_data)?;
+
+    Ok(update_data)
+}
+
+/// `process_update` function for `Dao::Mint`
+pub(crate) fn dao_mint_process_update(cid: ContractId, update: DaoMintUpdate) -> ContractResult {
+    // Grab all db handles we want to work on
+    let info_db = db_lookup(cid, DAO_CONTRACT_DB_INFO_TREE)?;
+    let bulla_db = db_lookup(cid, DAO_CONTRACT_DB_DAO_BULLAS)?;
+    let roots_db = db_lookup(cid, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS)?;
+
+    db_set(bulla_db, &serialize(&update.dao_bulla), &[])?;
+
+    let dao = vec![MerkleNode::from(update.dao_bulla.inner())];
+    merkle_add(info_db, roots_db, &serialize(&DAO_CONTRACT_KEY_DAO_MERKLE_TREE), &dao)?;
+
+    Ok(())
+}

+ 156 - 0
src/contract/dao/src/entrypoint/propose.rs

@@ -0,0 +1,156 @@
+/* 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 darkfi_money_contract::MONEY_CONTRACT_COIN_ROOTS_TREE;
+use darkfi_sdk::{
+    crypto::{contract_id::MONEY_CONTRACT_ID, pasta_prelude::*, ContractId, PublicKey},
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::DaoError,
+    model::{DaoBlindAggregateVote, DaoProposeParams, DaoProposeUpdate},
+    DaoFunction, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS, DAO_CONTRACT_DB_PROPOSAL_BULLAS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+};
+
+/// `get_metdata` function for `Dao::Propose`
+pub(crate) fn dao_propose_get_metadata(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoProposeParams = deserialize(&self_.data[1..])?;
+
+    if params.inputs.is_empty() {
+        msg!("[DAO::Propose] Error: Proposal inputs are empty");
+        return Err(DaoError::ProposalInputsEmpty.into())
+    }
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let mut signature_pubkeys: Vec<PublicKey> = vec![];
+
+    // Commitment calculation for all inputs
+    let mut total_funds_commit = pallas::Point::identity();
+
+    // Iterate through inputs
+    for input in &params.inputs {
+        signature_pubkeys.push(input.signature_public);
+        total_funds_commit += input.value_commit;
+
+        let value_coords = input.value_commit.to_affine().coordinates().unwrap();
+        let (sig_x, sig_y) = input.signature_public.xy();
+
+        zk_public_inputs.push((
+            DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS.to_string(),
+            vec![
+                *value_coords.x(),
+                *value_coords.y(),
+                params.token_commit,
+                input.merkle_root.inner(),
+                sig_x,
+                sig_y,
+            ],
+        ));
+    }
+
+    let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
+    zk_public_inputs.push((
+        DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS.to_string(),
+        vec![
+            params.token_commit,
+            params.dao_merkle_root.inner(),
+            params.proposal_bulla,
+            *total_funds_coords.x(),
+            *total_funds_coords.y(),
+        ],
+    ));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Dao::Propose`
+pub(crate) fn dao_propose_process_instruction(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoProposeParams = deserialize(&self_.data[1..])?;
+
+    // Check the Merkle roots for the input coins are valid
+    let coin_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    for input in &params.inputs {
+        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
+            msg!("[Dao::Propose] Error: Invalid input Merkle root: {}", input.merkle_root);
+            return Err(DaoError::InvalidInputMerkleRoot.into())
+        }
+    }
+
+    // Is the DAO bulla generated in the ZK proof valid
+    let dao_roots_db = db_lookup(cid, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS)?;
+    if !db_contains_key(dao_roots_db, &serialize(&params.dao_merkle_root))? {
+        msg!("[Dao::Propose] Error: Invalid DAO Merkle root: {}", params.dao_merkle_root);
+        return Err(DaoError::InvalidDaoMerkleRoot.into())
+    }
+
+    // Make sure the proposal doesn't already exist
+    let proposal_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+    if db_contains_key(proposal_db, &serialize(&params.proposal_bulla))? {
+        msg!("[Dao::Propose] Error: Proposal already exists: {:?}", params.proposal_bulla);
+        return Err(DaoError::ProposalAlreadyExists.into())
+    }
+
+    // Create state update
+    let update = DaoProposeUpdate { proposal_bulla: params.proposal_bulla };
+    let mut update_data = vec![];
+    update_data.write_u8(DaoFunction::Propose as u8)?;
+    update.encode(&mut update_data)?;
+    Ok(update_data)
+}
+
+/// `process_update` function for `Dao::Propose`
+pub(crate) fn dao_propose_process_update(
+    cid: ContractId,
+    update: DaoProposeUpdate,
+) -> ContractResult {
+    // Grab all db handles we want to work on
+    let proposal_vote_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+
+    // Initial vote aggregate
+    let pv = DaoBlindAggregateVote::default();
+    let ended = false;
+
+    // Set the new proposal in the db
+    db_set(proposal_vote_db, &serialize(&update.proposal_bulla), &serialize(&(pv, ended)))?;
+
+    Ok(())
+}

+ 196 - 0
src/contract/dao/src/entrypoint/vote.rs

@@ -0,0 +1,196 @@
+/* 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 darkfi_money_contract::{MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE};
+use darkfi_sdk::{
+    crypto::{contract_id::MONEY_CONTRACT_ID, pasta_prelude::*, ContractId, PublicKey},
+    db::{db_contains_key, db_get, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::DaoError,
+    model::{DaoBlindAggregateVote, DaoVoteParams, DaoVoteUpdate},
+    DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_DB_VOTE_NULLIFIERS,
+    DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+};
+
+/// `get_metdata` function for `Dao::Vote`
+pub(crate) fn dao_vote_get_metadata(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoVoteParams = deserialize(&self_.data[1..])?;
+
+    if params.inputs.is_empty() {
+        msg!("[Dao::Vote] Error: Vote inputs are empty");
+        return Err(DaoError::VoteInputsEmpty.into())
+    }
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let mut signature_pubkeys: Vec<PublicKey> = vec![];
+
+    // Commitment calculation for all votes
+    let mut all_vote_commit = pallas::Point::identity();
+
+    // Iterate through inputs
+    for input in &params.inputs {
+        signature_pubkeys.push(input.signature_public);
+        all_vote_commit += input.vote_commit;
+
+        let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
+        let (sig_x, sig_y) = input.signature_public.xy();
+
+        zk_public_inputs.push((
+            DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS.to_string(),
+            vec![
+                input.nullifier.inner(),
+                *value_coords.x(),
+                *value_coords.y(),
+                params.token_commit,
+                input.merkle_root.inner(),
+                sig_x,
+                sig_y,
+            ],
+        ));
+    }
+
+    let yes_vote_commit_coords = params.yes_vote_commit.to_affine().coordinates().unwrap();
+    let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
+
+    zk_public_inputs.push((
+        DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS.to_string(),
+        vec![
+            params.token_commit,
+            params.proposal_bulla,
+            *yes_vote_commit_coords.x(),
+            *yes_vote_commit_coords.y(),
+            *all_vote_commit_coords.x(),
+            *all_vote_commit_coords.y(),
+        ],
+    ));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Dao::Vote`
+pub(crate) fn dao_vote_process_instruction(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: DaoVoteParams = deserialize(&self_.data[1..])?;
+
+    // Check proposal bulla exists
+    let proposal_votes_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+    let Some(data) = db_get(proposal_votes_db, &serialize(&params.proposal_bulla))? else {
+        msg!("[Dao::Vote] Error: Proposal doesn't exist: {:?}", params.proposal_bulla);
+        return Err(DaoError::ProposalNonexistent.into())
+    };
+
+    // Get the current votes, and additionally confirm proposal hasn't ended
+    // TODO: Proposals should have a set length of time
+    let (mut proposal_votes, ended): (DaoBlindAggregateVote, bool) = deserialize(&data)?;
+    if ended {
+        msg!("[Dao::Vote] Error: Proposal ended: {:?}", params.proposal_bulla);
+        return Err(DaoError::ProposalEnded.into())
+    }
+
+    // Check the Merkle roots and nullifiers for the input coins are valid
+    let money_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let money_nullifier_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let dao_vote_nullifier_db = db_lookup(cid, DAO_CONTRACT_DB_VOTE_NULLIFIERS)?;
+    let mut vote_nullifiers = vec![];
+
+    for input in &params.inputs {
+        if !db_contains_key(money_roots_db, &serialize(&input.merkle_root))? {
+            msg!("[Dao::Vote] Error: Invalid input Merkle root: {}", input.merkle_root);
+            return Err(DaoError::InvalidInputMerkleRoot.into())
+        }
+
+        if db_contains_key(money_nullifier_db, &serialize(&input.nullifier))? {
+            msg!("[Dao::Vote] Error: Coin is already spent");
+            return Err(DaoError::CoinAlreadySpent.into())
+        }
+
+        // Prefix nullifier with proposal bulla so nullifiers from different proposals
+        // don't interfere with each other.
+        let null_key = serialize(&(params.proposal_bulla, input.nullifier));
+
+        if vote_nullifiers.contains(&input.nullifier) ||
+            db_contains_key(dao_vote_nullifier_db, &null_key)?
+        {
+            msg!("[Dao::Vote] Error: Attempted double vote");
+            return Err(DaoError::DoubleVote.into())
+        }
+
+        proposal_votes.all_vote_commit += input.vote_commit;
+        vote_nullifiers.push(input.nullifier);
+    }
+
+    proposal_votes.yes_vote_commit += params.yes_vote_commit;
+
+    // Create state update
+    let update =
+        DaoVoteUpdate { proposal_bulla: params.proposal_bulla, proposal_votes, vote_nullifiers };
+
+    let mut update_data = vec![];
+    update_data.write_u8(DaoFunction::Vote as u8)?;
+    update.encode(&mut update_data)?;
+    Ok(update_data)
+}
+
+/// `process_update` function for `Dao::Vote`
+pub(crate) fn dao_vote_process_update(cid: ContractId, update: DaoVoteUpdate) -> ContractResult {
+    // Grab all db handles we want to work on
+    let proposal_vote_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
+
+    // Perform this code:
+    //   total_yes_vote_commit += update.yes_vote_commit
+    //   total_all_vote_commit += update.all_vote_commit
+    db_set(
+        proposal_vote_db,
+        &serialize(&update.proposal_bulla),
+        &serialize(&(update.proposal_votes, false)),
+    )?;
+
+    // We are essentially doing: vote_nulls.append(update_nulls)
+    let dao_vote_nulls_db = db_lookup(cid, DAO_CONTRACT_DB_VOTE_NULLIFIERS)?;
+
+    for nullifier in update.vote_nullifiers {
+        // Uniqueness is enforced for (proposal_bulla, nullifier)
+        let key = serialize(&(update.proposal_bulla, nullifier));
+        db_set(dao_vote_nulls_db, &key, &[])?;
+    }
+
+    Ok(())
+}

+ 85 - 0
src/contract/dao/src/error.rs

@@ -0,0 +1,85 @@
+/* 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 darkfi_sdk::error::ContractError;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum DaoError {
+    #[error("DAO already exists")]
+    DaoAlreadyExists,
+
+    #[error("Proposal inputs are empty")]
+    ProposalInputsEmpty,
+
+    #[error("Invalid input Merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("Invalid DAO Merkle root")]
+    InvalidDaoMerkleRoot,
+
+    #[error("Proposal already exists")]
+    ProposalAlreadyExists,
+
+    #[error("Vote inputs are empty")]
+    VoteInputsEmpty,
+
+    #[error("Proposal doesn't exist")]
+    ProposalNonexistent,
+
+    #[error("Proposal ended")]
+    ProposalEnded,
+
+    #[error("Coin is already spent")]
+    CoinAlreadySpent,
+
+    #[error("Attempted double vote")]
+    DoubleVote,
+
+    #[error("Exec call has invalid tx format")]
+    ExecCallInvalidFormat,
+
+    #[error("Exec call mismatched outputs")]
+    ExecCallOutputsMismatch,
+
+    #[error("Exec call value commitment mismatch")]
+    ExecCallValueMismatch,
+
+    #[error("Vote commitments mismatch")]
+    VoteCommitMismatch,
+}
+
+impl From<DaoError> for ContractError {
+    fn from(e: DaoError) -> Self {
+        match e {
+            DaoError::DaoAlreadyExists => Self::Custom(1),
+            DaoError::ProposalInputsEmpty => Self::Custom(2),
+            DaoError::InvalidInputMerkleRoot => Self::Custom(3),
+            DaoError::InvalidDaoMerkleRoot => Self::Custom(4),
+            DaoError::ProposalAlreadyExists => Self::Custom(5),
+            DaoError::VoteInputsEmpty => Self::Custom(6),
+            DaoError::ProposalNonexistent => Self::Custom(7),
+            DaoError::ProposalEnded => Self::Custom(8),
+            DaoError::CoinAlreadySpent => Self::Custom(9),
+            DaoError::DoubleVote => Self::Custom(10),
+            DaoError::ExecCallInvalidFormat => Self::Custom(11),
+            DaoError::ExecCallOutputsMismatch => Self::Custom(12),
+            DaoError::ExecCallValueMismatch => Self::Custom(13),
+            DaoError::VoteCommitMismatch => Self::Custom(14),
+        }
+    }
+}

+ 49 - 28
src/contract/dao/src/lib.rs

@@ -16,35 +16,12 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::error::ContractError;
-
-#[cfg(not(feature = "no-entrypoint"))]
-pub mod entrypoint;
-
-#[cfg(feature = "client")]
-/// Transaction building API for clients interacting with DAO contract
-pub mod dao_client;
+//! Smart contract implementing Anonymous DAOs on DarkFi
 
-pub mod dao_model;
-
-#[cfg(feature = "client")]
-/// Transaction building API for clients interacting with money contract
-pub mod money_client;
-
-#[cfg(feature = "client")]
-/// Decrypt incoming transaction notes to track coins sent to us
-pub mod wallet_cache;
-
-// These are the zkas circuit namespaces
-pub const DAO_CONTRACT_ZKAS_DAO_MINT_NS: &str = "DaoMint";
-pub const DAO_CONTRACT_ZKAS_DAO_EXEC_NS: &str = "DaoExec";
-pub const DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS: &str = "DaoVoteInput";
-pub const DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS: &str = "DaoVoteMain";
-pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS: &str = "DaoProposeInput";
-pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";
+use darkfi_sdk::error::ContractError;
 
+/// Functions available in the contract
 #[repr(u8)]
-#[derive(PartialEq, Debug)]
 pub enum DaoFunction {
     Mint = 0x00,
     Propose = 0x01,
@@ -55,8 +32,8 @@ pub enum DaoFunction {
 impl TryFrom<u8> for DaoFunction {
     type Error = ContractError;
 
-    fn try_from(x: u8) -> core::result::Result<DaoFunction, Self::Error> {
-        match x {
+    fn try_from(b: u8) -> core::result::Result<Self, Self::Error> {
+        match b {
             0x00 => Ok(DaoFunction::Mint),
             0x01 => Ok(DaoFunction::Propose),
             0x02 => Ok(DaoFunction::Vote),
@@ -65,3 +42,47 @@ impl TryFrom<u8> for DaoFunction {
         }
     }
 }
+
+/// Internal contract errors
+pub mod error;
+
+/// Call parameters definitions
+pub mod model;
+
+#[cfg(not(feature = "no-entrypoint"))]
+/// WASM entrypoint functions
+pub mod entrypoint;
+
+#[cfg(feature = "client")]
+/// Client API for interaction with this smart contract
+pub mod client;
+
+// TODO: Delete these and use the proper API
+#[cfg(feature = "client")]
+pub mod money_client;
+#[cfg(feature = "client")]
+pub mod wallet_cache;
+
+// These are the different sled trees that will be created
+pub const DAO_CONTRACT_DB_INFO_TREE: &str = "dao_info";
+pub const DAO_CONTRACT_DB_DAO_BULLAS: &str = "dao_bullas";
+pub const DAO_CONTRACT_DB_DAO_MERKLE_ROOTS: &str = "dao_roots";
+pub const DAO_CONTRACT_DB_PROPOSAL_BULLAS: &str = "dao_proposals";
+pub const DAO_CONTRACT_DB_VOTE_NULLIFIERS: &str = "dao_vote_nullifiers";
+
+// These are keys inside the info tree
+pub const DAO_CONTRACT_KEY_DB_VERSION: &str = "db_version";
+pub const DAO_CONTRACT_KEY_DAO_MERKLE_TREE: &str = "dao_merkle_tree";
+
+/// zkas dao mint circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_MINT_NS: &str = "DaoMint";
+/// zkas dao exec circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_EXEC_NS: &str = "DaoExec";
+/// zkas dao vote input circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS: &str = "DaoVoteInput";
+/// zkas dao vote main circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS: &str = "DaoVoteMain";
+/// zkas dao propose input circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS: &str = "DaoProposeInput";
+/// zkas dao propose main circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";

+ 194 - 0
src/contract/dao/src/model.rs

@@ -0,0 +1,194 @@
+/* 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 darkfi_money_contract::model::Coin;
+use darkfi_sdk::{
+    crypto::{note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, Nullifier, PublicKey},
+    error::ContractError,
+    pasta::pallas,
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+/// A `DaoBulla` represented in the state
+#[derive(Debug, Copy, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
+pub struct DaoBulla(pallas::Base);
+
+impl DaoBulla {
+    /// Reference the raw inner base field element
+    pub fn inner(&self) -> pallas::Base {
+        self.0
+    }
+
+    /// Create a `DaoBulla` object from given bytes, erroring if the
+    /// input bytes are noncanonical.
+    pub fn from_bytes(x: [u8; 32]) -> Result<Self, ContractError> {
+        match pallas::Base::from_repr(x).into() {
+            Some(v) => Ok(Self(v)),
+            None => {
+                Err(ContractError::IoError("Failed to instantiate DaoBulla from bytes".to_string()))
+            }
+        }
+    }
+
+    /// Convert the `DaoBulla` type into 32 raw bytes
+    pub fn to_bytes(&self) -> [u8; 32] {
+        self.0.to_repr()
+    }
+}
+
+use core::str::FromStr;
+darkfi_sdk::fp_from_bs58!(DaoBulla);
+darkfi_sdk::fp_to_bs58!(DaoBulla);
+darkfi_sdk::ty_from_fp!(DaoBulla);
+
+/// Parameters for `Dao::Mint`
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoMintParams {
+    /// The DAO bulla
+    pub dao_bulla: DaoBulla,
+    /// The DAO public key
+    pub dao_pubkey: PublicKey,
+}
+
+/// State update for `Dao::Mint`
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoMintUpdate {
+    /// Revealed DAO bulla
+    pub dao_bulla: DaoBulla,
+}
+
+/// Parameters for `Dao::Propose`
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoProposeParams {
+    /// Merkle root of the DAO in the DAO state
+    pub dao_merkle_root: MerkleNode,
+    /// Token ID commitment for the proposal
+    pub token_commit: pallas::Base,
+    /// Bulla of the DAO proposal
+    pub proposal_bulla: pallas::Base,
+    /// Encrypted note
+    pub note: AeadEncryptedNote,
+    /// Inputs for the proposal
+    pub inputs: Vec<DaoProposeParamsInput>,
+}
+
+/// Input for a DAO proposal
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoProposeParamsInput {
+    /// Value commitment for the input
+    pub value_commit: pallas::Point,
+    /// Merkle root for the input's inclusion proof
+    pub merkle_root: MerkleNode,
+    /// Public key used for signing
+    pub signature_public: PublicKey,
+}
+
+/// State update for `Dao::Propose`
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoProposeUpdate {
+    /// Minted proposal bulla
+    pub proposal_bulla: pallas::Base,
+}
+
+/// Parameters for `Dao::Vote`
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoVoteParams {
+    /// Token commitment for the vote inputs
+    pub token_commit: pallas::Base,
+    /// Proposal bulla being voted on
+    pub proposal_bulla: pallas::Base,
+    /// Commitment for yes votes
+    pub yes_vote_commit: pallas::Point,
+    /// Encrypted note
+    pub note: AeadEncryptedNote,
+    /// Inputs for the vote
+    pub inputs: Vec<DaoVoteParamsInput>,
+}
+
+/// Input for a DAO proposal vote
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoVoteParamsInput {
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+    /// Vote commitment
+    pub vote_commit: pallas::Point,
+    /// Merkle root for the input's inclusion proof
+    pub merkle_root: MerkleNode,
+    /// Public key used for signing
+    pub signature_public: PublicKey,
+}
+
+/// State update for `Dao::Vote`
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoVoteUpdate {
+    /// The proposal bulla being voted on
+    pub proposal_bulla: pallas::Base,
+    /// The proposal votes aggregate
+    pub proposal_votes: DaoBlindAggregateVote,
+    /// Vote nullifiers,
+    pub vote_nullifiers: Vec<Nullifier>,
+}
+
+/// Represents a single or multiple blinded votes.
+/// These can be summed together.
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoBlindAggregateVote {
+    /// Weighted vote commit
+    pub yes_vote_commit: pallas::Point,
+    /// All value staked in the vote
+    pub all_vote_commit: pallas::Point,
+}
+
+impl DaoBlindAggregateVote {
+    /// Aggregate a vote with existing one
+    pub fn aggregate(&mut self, other: Self) {
+        self.yes_vote_commit += other.yes_vote_commit;
+        self.all_vote_commit += other.all_vote_commit;
+    }
+}
+
+impl Default for DaoBlindAggregateVote {
+    fn default() -> Self {
+        Self {
+            yes_vote_commit: pallas::Point::identity(),
+            all_vote_commit: pallas::Point::identity(),
+        }
+    }
+}
+
+/// Parameters for `Dao::Exec`
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoExecParams {
+    /// The proposal bulla
+    pub proposal: pallas::Base,
+    /// The output coin for the proposal recipient
+    pub coin_0: Coin,
+    /// The output coin for the change returned to DAO
+    pub coin_1: Coin,
+    /// Aggregated blinds for the vote commitments
+    pub blind_total_vote: DaoBlindAggregateVote,
+    /// Value commitment for all the inputs
+    pub input_value_commit: pallas::Point,
+}
+
+/// State update for `Dao::Exec`
+#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoExecUpdate {
+    /// The proposal bulla
+    pub proposal: pallas::Base,
+}

+ 21 - 32
src/contract/dao/tests/integration.rs

@@ -21,9 +21,8 @@ use std::time::{Duration, Instant};
 use darkfi::{tx::Transaction, Result};
 use darkfi_sdk::{
     crypto::{
-        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Keypair,
-        MerkleNode, MerkleTree, SecretKey, TokenId, DAO_CONTRACT_ID, DARK_TOKEN_ID,
-        MONEY_CONTRACT_ID,
+        pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode, MerkleTree,
+        SecretKey, TokenId, DAO_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
     },
     pasta::pallas,
     ContractCall,
@@ -32,9 +31,7 @@ use darkfi_serial::{Decodable, Encodable};
 use log::debug;
 use rand::rngs::OsRng;
 
-use darkfi_dao_contract::{
-    dao_client, dao_model, money_client, wallet_cache::WalletCache, DaoFunction,
-};
+use darkfi_dao_contract::{client, model, money_client, wallet_cache::WalletCache, DaoFunction};
 
 use darkfi_money_contract::{
     client::token_mint_v1::TokenMintCallBuilder,
@@ -81,7 +78,7 @@ async fn integration_test() -> Result<()> {
     let gdrk_token_id = TokenId::derive(gdrk_mint_auth.secret);
 
     // DAO parameters
-    let dao = dao_client::DaoInfo {
+    let dao = client::DaoInfo {
         proposer_limit: 110,
         quorum: 110,
         approval_ratio_base: 2,
@@ -101,7 +98,7 @@ async fn integration_test() -> Result<()> {
     // =======================================================
     debug!(target: "dao", "Stage 1. Creating DAO bulla");
 
-    let (params, proofs) = dao_client::make_mint_call(
+    let (params, proofs) = client::make_mint_call(
         &dao,
         &dao_th.dao_kp.secret,
         &dao_th.dao_mint_zkbin,
@@ -400,7 +397,7 @@ async fn integration_test() -> Result<()> {
     // TODO: is it possible for an invalid transfer() to be constructed on exec()?
     //       need to look into this
     let signature_secret = SecretKey::random(&mut OsRng);
-    let input = dao_client::DaoProposeStakeInput {
+    let input = client::DaoProposeStakeInput {
         secret: dao_th.alice_kp.secret,
         note: gov_recv[0].note.clone(),
         leaf_position: money_leaf_position,
@@ -415,14 +412,14 @@ async fn integration_test() -> Result<()> {
         (merkle_path, root)
     };
 
-    let proposal = dao_client::DaoProposalInfo {
+    let proposal = client::DaoProposalInfo {
         dest: receiver_keypair.public,
         amount: 1000,
         token_id: xdrk_token_id,
         blind: pallas::Base::random(&mut OsRng),
     };
 
-    let call = dao_client::DaoProposeCall {
+    let call = client::DaoProposeCall {
         inputs: vec![input],
         proposal,
         dao: dao.clone(),
@@ -461,9 +458,7 @@ async fn integration_test() -> Result<()> {
 
     // Read received proposal
     let (proposal, proposal_bulla) = {
-        let enc_note =
-            AeadEncryptedNote { ciphertext: params.ciphertext, ephem_public: params.ephem_public };
-        let note: dao_client::DaoProposeNote = enc_note.decrypt(&dao_th.dao_kp.secret).unwrap();
+        let note: client::DaoProposeNote = params.note.decrypt(&dao_th.dao_kp.secret).unwrap();
 
         // TODO: check it belongs to DAO bulla
 
@@ -516,7 +511,7 @@ async fn integration_test() -> Result<()> {
     };
 
     let signature_secret = SecretKey::random(&mut OsRng);
-    let input = dao_client::DaoVoteInput {
+    let input = client::DaoVoteInput {
         secret: dao_th.alice_kp.secret,
         note: gov_recv[0].note.clone(),
         leaf_position: money_leaf_position,
@@ -531,7 +526,7 @@ async fn integration_test() -> Result<()> {
     // For the demo MVP, you can just use the dao_keypair secret
     let vote_keypair_1 = Keypair::random(&mut OsRng);
 
-    let call = dao_client::DaoVoteCall {
+    let call = client::DaoVoteCall {
         inputs: vec![input],
         vote_option,
         yes_vote_blind: pallas::Scalar::random(&mut OsRng),
@@ -570,9 +565,7 @@ async fn integration_test() -> Result<()> {
     // TODO: look into verifiable encryption for notes
     // TODO: look into timelock puzzle as a possibility
     let vote_note_1 = {
-        let enc_note =
-            AeadEncryptedNote { ciphertext: params.ciphertext, ephem_public: params.ephem_public };
-        let note: dao_client::DaoVoteNote = enc_note.decrypt(&vote_keypair_1.secret).unwrap();
+        let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_1.secret).unwrap();
         note
     };
     debug!(target: "dao", "User 1 voted!");
@@ -589,7 +582,7 @@ async fn integration_test() -> Result<()> {
     };
 
     let signature_secret = SecretKey::random(&mut OsRng);
-    let input = dao_client::DaoVoteInput {
+    let input = client::DaoVoteInput {
         //secret: gov_keypair_2.secret,
         secret: dao_th.bob_kp.secret,
         note: gov_recv[1].note.clone(),
@@ -604,7 +597,7 @@ async fn integration_test() -> Result<()> {
     // We create a new keypair to encrypt the vote.
     let vote_keypair_2 = Keypair::random(&mut OsRng);
 
-    let call = dao_client::DaoVoteCall {
+    let call = client::DaoVoteCall {
         inputs: vec![input],
         vote_option,
         yes_vote_blind: pallas::Scalar::random(&mut OsRng),
@@ -640,9 +633,7 @@ async fn integration_test() -> Result<()> {
     vote_verify_times.push(timer.elapsed());
 
     let vote_note_2 = {
-        let enc_note =
-            AeadEncryptedNote { ciphertext: params.ciphertext, ephem_public: params.ephem_public };
-        let note: dao_client::DaoVoteNote = enc_note.decrypt(&vote_keypair_2.secret).unwrap();
+        let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_2.secret).unwrap();
         note
     };
     debug!(target: "dao", "User 2 voted!");
@@ -659,7 +650,7 @@ async fn integration_test() -> Result<()> {
     };
 
     let signature_secret = SecretKey::random(&mut OsRng);
-    let input = dao_client::DaoVoteInput {
+    let input = client::DaoVoteInput {
         //secret: gov_keypair_3.secret,
         secret: dao_th.charlie_kp.secret,
         note: gov_recv[2].note.clone(),
@@ -674,7 +665,7 @@ async fn integration_test() -> Result<()> {
     // We create a new keypair to encrypt the vote.
     let vote_keypair_3 = Keypair::random(&mut OsRng);
 
-    let call = dao_client::DaoVoteCall {
+    let call = client::DaoVoteCall {
         inputs: vec![input],
         vote_option,
         yes_vote_blind: pallas::Scalar::random(&mut OsRng),
@@ -713,9 +704,7 @@ async fn integration_test() -> Result<()> {
     // TODO: look into verifiable encryption for notes
     // TODO: look into timelock puzzle as a possibility
     let vote_note_3 = {
-        let enc_note =
-            AeadEncryptedNote { ciphertext: params.ciphertext, ephem_public: params.ephem_public };
-        let note: dao_client::DaoVoteNote = enc_note.decrypt(&vote_keypair_3.secret).unwrap();
+        let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_3.secret).unwrap();
         note
     };
     debug!(target: "dao", "User 3 voted!");
@@ -734,7 +723,7 @@ async fn integration_test() -> Result<()> {
     let mut total_yes_vote_value = 0;
     let mut total_all_vote_value = 0;
 
-    let mut blind_total_vote = dao_model::DaoBlindAggregateVote::default();
+    let mut blind_total_vote = model::DaoBlindAggregateVote::default();
 
     // Just keep track of these for the assert statements after the for loop
     // but they aren't needed otherwise.
@@ -757,7 +746,7 @@ async fn integration_test() -> Result<()> {
         let yes_vote_commit = pedersen_commitment_u64(yes_vote_value, note.yes_vote_blind);
         let all_vote_commit = pedersen_commitment_u64(note.all_vote_value, note.all_vote_blind);
 
-        let blind_vote = dao_model::DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
+        let blind_vote = model::DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
         blind_total_vote.aggregate(blind_vote);
 
         // Just for the debug
@@ -864,7 +853,7 @@ async fn integration_test() -> Result<()> {
     xfer_params.encode(&mut data)?;
     let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
-    let call = dao_client::DaoExecCall {
+    let call = client::DaoExecCall {
         proposal,
         dao,
         yes_vote_value: total_yes_vote_value,

+ 1 - 1
src/contract/money/src/lib.rs

@@ -74,7 +74,7 @@ pub const MONEY_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers";
 pub const MONEY_CONTRACT_TOKEN_FREEZE_TREE: &str = "token_freezes";
 
 // These are keys inside the info tree
-pub const MONEY_CONTRACT_DB_VERSION: &str = env!("CARGO_PKG_VERSION");
+pub const MONEY_CONTRACT_DB_VERSION: &str = "db_version";
 pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &str = "coin_tree";
 pub const MONEY_CONTRACT_FAUCET_PUBKEYS: &str = "faucet_pubkeys";
 

+ 0 - 4
src/sdk/Cargo.toml

@@ -41,7 +41,3 @@ subtle = "2.5.0"
 halo2_proofs = {version = "0.3.0", features = ["dev-graph", "gadget-traces", "sanity-checks"]}
 halo2_gadgets = {version = "0.3.0", features = ["test-dev-graph", "test-dependencies"]}
 rand = "0.8.5"
-
-[patch.crates-io]
-halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v3"}
-halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v3"}