Przeglądaj źródła

contract/dao: Initial implementation.

parazyd 3 lat temu
rodzic
commit
0b6f6f847d

+ 10 - 33
Cargo.lock

@@ -1147,39 +1147,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "dao-example"
-version = "0.3.0"
-dependencies = [
- "async-channel",
- "async-executor",
- "async-std",
- "async-trait",
- "bs58",
- "chacha20poly1305",
- "darkfi",
- "darkfi-sdk",
- "darkfi-serial",
- "easy-parallel",
- "env_logger",
- "futures",
- "fxhash",
- "group",
- "halo2_gadgets",
- "halo2_proofs",
- "incrementalmerkletree",
- "lazy_static",
- "log",
- "num_cpus",
- "pasta_curves",
- "rand",
- "serde_json",
- "simplelog",
- "smol",
- "thiserror",
- "url",
-]
-
 [[package]]
 name = "daod"
 version = "0.3.0"
@@ -1279,6 +1246,16 @@ dependencies = [
  "wasmer-middlewares",
 ]
 
+[[package]]
+name = "darkfi-dao-contract"
+version = "0.3.0"
+dependencies = [
+ "darkfi-money-contract",
+ "darkfi-sdk",
+ "darkfi-serial",
+ "getrandom 0.2.8",
+]
+
 [[package]]
 name = "darkfi-derive"
 version = "0.3.0"

+ 1 - 1
Cargo.toml

@@ -45,9 +45,9 @@ members = [
     "src/serial/derive-internal",
 
     "src/contract/money",
+    "src/contract/dao",
 
     "example/dchat",
-    "example/dao",
 ]
 
 [dependencies]

+ 2 - 0
src/contract/dao/.gitignore

@@ -0,0 +1,2 @@
+dao_contract.wasm
+proof/*.zk.bin

+ 24 - 0
src/contract/dao/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "darkfi-dao-contract"
+version = "0.3.0"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+darkfi-sdk = { path = "../../sdk" }
+darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
+darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
+
+# We need to disable random using "custom" which makes the crate a noop
+# so the wasm32-unknown-unknown target is enabled.
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+getrandom = { version = "0.2.8", features = ["custom"] }
+
+[features]
+default = []
+no-entrypoint = []
+client = []

+ 37 - 0
src/contract/dao/Makefile

@@ -0,0 +1,37 @@
+# Cargo binary
+CARGO ?= cargo
+
+# Zkas binary
+ZKAS ?= ../../../zkas
+
+# zkas circuit source files
+ZKAS_SRC = $(shell find proof -type f -name '*.zk')
+
+# wasm source files
+WASM_SRC = \
+	$(shell find src -type f) \
+	$(shell find ../../sdk -type f) \
+	$(shell find ../../serial -type f)
+
+# zkas circuit bin files
+ZKAS_BIN = $(ZKAS_SRC:=.bin)
+
+# Contract WASM binaries
+WASM_BIN = dao_contract.wasm
+
+all: $(WASM_BIN)
+
+$(ZKAS_BIN): $(ZKAS_SRC)
+	$(ZKAS) $(basename $@) -o $@
+
+dao_contract.wasm: $(ZKAS_BIN) $(WASM_SRC)
+	$(CARGO) build --release --package darkfi-dao-contract --target wasm32-unknown-unknown
+	cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_dao_contract.wasm $@
+
+test: all
+	$(CARGO) test --release --features=no-entrypoint,client --package darkfi-dao-contract
+
+clean:
+	rm -f $(ZKAS_BIN) $(WASM_BIN)
+
+.PHONY: all test clean

+ 150 - 0
src/contract/dao/proof/dao-exec.zk

@@ -0,0 +1,150 @@
+constant "DaoExec" {
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+}
+
+contract "DaoExec" {
+	# Proposal parameters
+	Base proposal_dest_x,
+	Base proposal_dest_y,
+	Base proposal_amount,
+	Base proposal_serial,
+	Base proposal_token_id,
+	Base proposal_blind,
+
+	# DAO parameters
+	Base dao_proposer_limit,
+	Base dao_quorum,
+	Base dao_approval_ratio_quot,
+	Base dao_approval_ratio_base,
+	Base gov_token_id,
+	Base dao_public_x,
+	Base dao_public_y,
+	Base dao_bulla_blind,
+
+	# Votes
+	Base yes_votes_value,
+	Base all_votes_value,
+	Scalar yes_votes_blind,
+	Scalar all_votes_blind,
+
+	# Outputs + Inputs
+	Base user_serial,
+	Base user_coin_blind,
+	Base dao_serial,
+	Base dao_coin_blind,
+	Base input_value,
+	Scalar input_value_blind,
+
+	# Miscellaneous
+	Base dao_spend_hook,
+	Base user_spend_hook,
+	Base user_data,
+}
+
+circuit "DaoExec" {
+	dao_bulla = poseidon_hash(
+		dao_proposer_limit,
+		dao_quorum,
+		dao_approval_ratio_quot,
+		dao_approval_ratio_base,
+		gov_token_id,
+		dao_public_x,
+		dao_public_y,
+		dao_bulla_blind,
+	);
+
+	# Proposal bulla being valid means DAO bulla is also valid because
+	# dao-propose-main.zk already checks that when we first create the
+	# proposal - so it is redundant here.
+	proposal_bulla = poseidon_hash(
+		proposal_dest_x,
+		proposal_dest_y,
+		proposal_amount,
+		proposal_serial,
+		proposal_token_id,
+		dao_bulla,
+		proposal_blind,
+		# Blind twice to workaround odd-n poseidon bug
+		proposal_blind,
+	);
+	constrain_instance(proposal_bulla);
+
+	coin_0 = poseidon_hash(
+		proposal_dest_x,
+		proposal_dest_y,
+		proposal_amount,
+		proposal_token_id,
+		proposal_serial,
+		user_spend_hook,
+		user_data,
+		proposal_blind,
+	);
+	constrain_instance(coin_0);
+
+	change = base_sub(input_value, proposal_amount);
+
+	coin_1 = poseidon_hash(
+		dao_public_x,
+		dao_public_y,
+		change,
+		proposal_token_id,
+		dao_serial,
+		dao_spend_hook,
+		dao_bulla,
+		dao_coin_blind,
+	);
+	constrain_instance(coin_1);
+
+	# Create Pedersen commitments for win_votes and total_votes, and
+	# constrain the commitments' coordinates.
+	yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
+	yes_votes_blind_c = ec_mul(yes_votes_blind, VALUE_COMMIT_RANDOM);
+	yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
+	constrain_instance(ec_get_x(yes_votes_commit));
+	constrain_instance(ec_get_y(yes_votes_commit));
+
+	all_votes_value_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
+	all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
+	all_votes_commit = ec_add(all_votes_value_c, all_votes_blind_c);
+	constrain_instance(ec_get_x(all_votes_commit));
+	constrain_instance(ec_get_y(all_votes_commit));
+
+	# Create Pedersen commitment for input_value and make public
+	input_value_v = ec_mul_short(input_value, VALUE_COMMIT_VALUE);
+	input_value_r = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM);
+	input_value_commit = ec_add(input_value_v, input_value_r);
+	constrain_instance(ec_get_x(input_value_commit));
+	constrain_instance(ec_get_y(input_value_commit));
+
+	constrain_instance(dao_spend_hook);
+	constrain_instance(user_spend_hook);
+	constrain_instance(user_data);
+
+	# Check that dao_quorum is less than or equal to all_votes_value
+	one = witness_base(1);
+	all_votes_value_1 = base_add(all_votes_value, one);
+	less_than_strict(dao_quorum, all_votes_value_1);
+
+	# approval_ratio_quot / approval_ratio_base <= yes_votes / all_votes
+	#
+	# The above is also equivalent to this:
+	#
+	# all_votes * approval_ratio_quot <= yes_votes * approval_ratio_base
+	lhs = base_mul(all_votes_value, dao_approval_ratio_quot);
+	rhs = base_mul(yes_votes_value, dao_approval_ratio_base);
+	rhs_1 = base_add(rhs, one);
+	less_than_strict(lhs, rhs_1);
+
+	# Create coin 0
+	# Create coin 1
+	# Check values of coin 0 + coin 1 == input_value
+	# Check value of coin 0 == proposal_amount
+	# Check public key matches too
+	# Create the input value commit
+	# Create the value commits
+
+	# NOTE: There is a vulnerability here where someone can create the exec
+	# transaction with a bad note so it cannot be decrypted by the receiver
+	# TODO: Research verifiable encryption inside ZK
+}

+ 31 - 0
src/contract/dao/proof/dao-mint.zk

@@ -0,0 +1,31 @@
+constant "DaoMint" {
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+}
+
+contract "DaoMint" {
+	Base dao_proposer_limit,
+	Base dao_quorum,
+	Base dao_approval_ratio_quot,
+	Base dao_approval_ratio_base,
+	Base gov_token_id,
+	Base dao_public_x,
+	Base dao_public_y,
+	Base dao_bulla_blind,
+}
+
+circuit "DaoMint" {
+	# This circuit states that the bulla is a hash of 8 values
+
+	bulla = poseidon_hash(
+		dao_proposer_limit,
+		dao_quorum,
+		dao_approval_ratio_quot,
+		dao_approval_ratio_base,
+		gov_token_id,
+		dao_public_x,
+		dao_public_y,
+		dao_bulla_blind,
+	);
+
+	constrain_instance(bulla);
+}

+ 61 - 0
src/contract/dao/proof/dao-propose-burn.zk

@@ -0,0 +1,61 @@
+constant "DaoProposeInput" {
+	EcFixedPointBase NULLIFIER_K,
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+}
+
+contract "DaoProposeInput" {
+	Base secret,
+	Base serial,
+	Base spend_hook,
+	Base user_data,
+	Base value,
+	Base token,
+	Base coin_blind,
+	Scalar value_blind,
+	Base token_blind,
+	Uint32 leaf_pos,
+	MerklePath path,
+	Base signature_secret,
+}
+
+circuit "DaoProposeInput" {
+	nullifier = poseidon_hash(secret, serial);
+	constrain_instance(nullifier);
+
+	# Pedersen commitment for coin's value
+	vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+	vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+	value_commit = ec_add(vcv, vcr);
+	constrain_instance(ec_get_x(value_commit));
+	constrain_instance(ec_get_y(value_commit));
+
+	# Commitment for coin's token ID
+	token_commit = poseidon_hash(token, token_blind);
+	constrain_instance(token_commit);
+
+	# Coin hash
+	pub = ec_mul_base(secret, NULLIFIER_K);
+	pub_x = ec_get_x(pub);
+	pub_y = ec_get_y(pub);
+	C = poseidon_hash(
+		pub_x,
+		pub_y,
+		value,
+		token,
+		serial,
+		spend_hook,
+		user_data,
+		coin_blind
+	);
+
+	# Merkle root
+	root = merkle_root(leaf_pos, path, C);
+	constrain_instance(root);
+
+	# Finally we derive a public key for the signature and constrain
+	# its coordinates:
+	signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
+	constrain_instance(ec_get_x(signature_public));
+	constrain_instance(ec_get_y(signature_public));
+}

+ 83 - 0
src/contract/dao/proof/dao-propose-main.zk

@@ -0,0 +1,83 @@
+constant "DaoProposeMain" {
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+}
+
+contract "DaoProposeMain" {
+	# Proposers total number of governance tokens
+	Base total_funds,
+	Scalar total_funds_blind,
+
+	# Check the inputs and this proof are for the same token
+	Base gov_token_blind,
+
+	# Proposal parameters
+	Base proposal_dest_x,
+	Base proposal_dest_y,
+	Base proposal_amount,
+	Base proposal_serial,
+	Base proposal_token_id,
+	Base proposal_blind,
+
+	# DAO params
+	Base dao_proposer_limit,
+	Base dao_quorum,
+	Base dao_approval_ratio_quot,
+	Base dao_approval_ratio_base,
+	Base gov_token_id,
+	Base dao_public_x,
+	Base dao_public_y,
+	Base dao_bulla_blind,
+
+	Uint32 dao_leaf_pos,
+	MerklePath dao_path,
+}
+
+circuit "DaoProposeMain" {
+	token_commit = poseidon_hash(gov_token_id, gov_token_blind);
+	constrain_instance(token_commit);
+
+	dao_bulla = poseidon_hash(
+		dao_proposer_limit,
+		dao_quorum,
+		dao_approval_ratio_quot,
+		dao_approval_ratio_base,
+		gov_token_id,
+		dao_public_x,
+		dao_public_y,
+		dao_bulla_blind,
+	);
+
+	dao_root = merkle_root(dao_leaf_pos, dao_path, dao_bulla);
+	constrain_instance(dao_root);
+	# Proves this DAO is valid
+
+	proposal_bulla = poseidon_hash(
+		proposal_dest_x,
+		proposal_dest_y,
+		proposal_amount,
+		proposal_serial,
+		proposal_token_id,
+		dao_bulla,
+		proposal_blind,
+		proposal_blind,
+	);
+	constrain_instance(proposal_bulla);
+
+	# Rangeproof check for proposal amount
+	zero = witness_base(0);
+	less_than_strict(zero, proposal_amount);
+
+	# This is the main check
+	# We check that dao_proposer_limit <= total_funds
+	one = witness_base(1);
+	total_funds_1 = base_add(total_funds, one);
+	less_than_strict(dao_proposer_limit, total_funds_1);
+
+	# Pedersen commitment for coin's value
+	vcv = ec_mul_short(total_funds, VALUE_COMMIT_VALUE);
+	vcr = ec_mul(total_funds_blind, VALUE_COMMIT_RANDOM);
+	total_funds_commit = ec_add(vcv, vcr);
+	constrain_instance(ec_get_x(total_funds_commit));
+	constrain_instance(ec_get_y(total_funds_commit));
+}

+ 56 - 0
src/contract/dao/proof/dao-vote-burn.zk

@@ -0,0 +1,56 @@
+constant "DaoVoteInput" {
+	EcFixedPointBase NULLIFIER_K,
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+}
+
+contract "DaoVoteInput" {
+	Base secret,
+	Base serial,
+	Base spend_hook,
+	Base user_data,
+	Base value,
+	Base gov_token_id,
+	Base coin_blind,
+	Scalar value_blind,
+	Base gov_token_blind,
+	Uint32 leaf_pos,
+	MerklePath path,
+	Base signature_secret,
+}
+
+circuit "DaoVoteInput" {
+	nullifier = poseidon_hash(secret, serial);
+	constrain_instance(nullifier);
+
+	vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+	vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+	value_commit = ec_add(vcv, vcr);
+	constrain_instance(ec_get_x(value_commit));
+	constrain_instance(ec_get_y(value_commit));
+
+	token_commit = poseidon_hash(gov_token_id, gov_token_blind);
+	constrain_instance(token_commit);
+
+	pub = ec_mul_base(secret, NULLIFIER_K);
+	pub_x = ec_get_x(pub);
+	pub_y = ec_get_y(pub);
+	C = poseidon_hash(
+		pub_x,
+		pub_y,
+		value,
+		gov_token_id,
+		serial,
+		spend_hook,
+		user_data,
+		coin_blind,
+	);
+
+	# Merkle root
+	root = merkle_root(leaf_pos, path, C);
+	constrain_instance(root);
+
+	signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
+	constrain_instance(ec_get_x(signature_public));
+	constrain_instance(ec_get_y(signature_public));
+}

+ 84 - 0
src/contract/dao/proof/dao-vote-main.zk

@@ -0,0 +1,84 @@
+constant "DaoVoteMain" {
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+}
+
+contract "DaoVoteMain" {
+	# Proposal parameters
+	Base proposal_dest_x,
+	Base proposal_dest_y,
+	Base proposal_amount,
+	Base proposal_serial,
+	Base proposal_token_id,
+	Base proposal_blind,
+
+	# DAO parameters
+	Base dao_proposer_limit,
+	Base dao_quorum,
+	Base dao_approval_ratio_quot,
+	Base dao_approval_ratio_base,
+	Base gov_token_id,
+	Base dao_public_x,
+	Base dao_public_y,
+	Base dao_bulla_blind,
+
+	# Is the vote yes or no
+	Base vote_option,
+	Scalar yes_vote_blind,
+
+	# Total amount of capital allocated to vote
+	Base all_votes_value,
+	Scalar all_votes_blind,
+
+	# Check the inputs and this proof are for the same token
+	Base gov_token_blind,
+}
+
+circuit "DaoVoteMain" {
+	token_commit = poseidon_hash(gov_token_id, gov_token_blind);
+	constrain_instance(token_commit);
+
+	dao_bulla = poseidon_hash(
+		dao_proposer_limit,
+		dao_quorum,
+		dao_approval_ratio_quot,
+		dao_approval_ratio_base,
+		gov_token_id,
+		dao_public_x,
+		dao_public_y,
+		dao_bulla_blind,
+	);
+
+	proposal_bulla = poseidon_hash(
+		proposal_dest_x,
+		proposal_dest_y,
+		proposal_amount,
+		proposal_serial,
+		proposal_token_id,
+		dao_bulla,
+		proposal_blind,
+		proposal_blind,
+	);
+	constrain_instance(proposal_bulla);
+	# TODO: We need to check the proposal isn't invalidated
+	# that is expired or already executed.
+
+	# Normally we call this yes vote
+	# Pedersen commitment for vote option
+	yes_votes_value = base_mul(vote_option, all_votes_value);
+	yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
+	yes_votes_blind_c = ec_mul(yes_vote_blind, VALUE_COMMIT_RANDOM);
+	yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
+	constrain_instance(ec_get_x(yes_votes_commit));
+	constrain_instance(ec_get_y(yes_votes_commit));
+
+	# Pedersen commitment for vote value
+	all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
+	all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
+	all_votes_commit = ec_add(all_votes_c, all_votes_blind_c);
+	constrain_instance(ec_get_x(all_votes_commit));
+	constrain_instance(ec_get_y(all_votes_commit));
+
+	# Vote option should be 0 or 1
+	bool_check(vote_option);
+}

+ 553 - 0
src/contract/dao/src/entrypoint.rs

@@ -0,0 +1,553 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 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, MerkleTree, PublicKey},
+    db::{db_contains_key, db_get, db_init, db_lookup, db_set, ZKAS_DB_NAME},
+    error::{ContractError, ContractResult},
+    merkle::merkle_add,
+    msg,
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{Curve, Group},
+        pallas,
+    },
+    tx::ContractCall,
+    util::set_return_data,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use darkfi_money_contract::{
+    state::MoneyTransferParams, MoneyFunction, COIN_ROOTS_TREE, NULLIFIERS_TREE,
+};
+
+use crate::{
+    state::{
+        DaoExecParams, DaoExecUpdate, DaoMintParams, DaoMintUpdate, DaoProposeParams,
+        DaoProposeUpdate, DaoVoteParams, DaoVoteUpdate, ProposalVotes,
+    },
+    DaoFunction,
+};
+
+darkfi_sdk::define_contract!(
+    init: init_contract,
+    exec: process_instruction,
+    apply: process_update,
+    metadata: get_metadata
+);
+
+// These are the different sled trees that will be created
+pub const DAO_BULLA_TREE: &str = "dao_info";
+pub const DAO_ROOTS_TREE: &str = "dao_roots";
+pub const DAO_PROPOSAL_TREE: &str = "dao_proposals";
+pub const DAO_PROPOSAL_ROOTS_TREE: &str = "dao_proposal_roots";
+pub const DAO_PROPOSAL_VOTES_TREE: &str = "dao_proposal_votes";
+
+// These are keys inside the some db trees
+pub const DAO_MERKLE_TREE: &str = "dao_merkle_tree";
+pub const DAO_PROPOSAL_MERKLE_TREE: &str = "dao_proposals_merkle_tree";
+
+// These are zkas circuit namespaces
+pub const ZKAS_DAO_EXEC_NS: &str = "DaoExec";
+pub const ZKAS_DAO_MINT_NS: &str = "DaoMint";
+pub const ZKAS_DAO_VOTE_BURN_NS: &str = "DaoVoteInput";
+pub const ZKAS_DAO_VOTE_MAIN_NS: &str = "DaoVoteMain";
+pub const ZKAS_DAO_PROPOSE_BURN_NS: &str = "DaoProposeInput";
+pub const ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";
+
+fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
+    // The zkas circuits can simply be embedded in the wasm and set up by
+    // the initialization. Note that the tree should then be called "zkas".
+    // The lookups can then be done by `contract_id+_zkas+namespace`.
+    let zkas_db = match db_lookup(cid, ZKAS_DB_NAME) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, ZKAS_DB_NAME)?,
+    };
+    let dao_exec_bin = include_bytes!("../proof/dao-exec.zk.bin");
+    let dao_mint_bin = include_bytes!("../proof/dao-mint.zk.bin");
+    let dao_vote_burn_bin = include_bytes!("../proof/dao-vote-burn.zk.bin");
+    let dao_vote_main_bin = include_bytes!("../proof/dao-vote-main.zk.bin");
+    let dao_propose_burn_bin = include_bytes!("../proof/dao-propose-burn.zk.bin");
+    let dao_propose_main_bin = include_bytes!("../proof/dao-propose-main.zk.bin");
+
+    db_set(zkas_db, &serialize(&ZKAS_DAO_EXEC_NS.to_string()), &dao_exec_bin[..])?;
+    db_set(zkas_db, &serialize(&ZKAS_DAO_MINT_NS.to_string()), &dao_mint_bin[..])?;
+    db_set(zkas_db, &serialize(&ZKAS_DAO_VOTE_BURN_NS.to_string()), &dao_vote_burn_bin[..])?;
+    db_set(zkas_db, &serialize(&ZKAS_DAO_VOTE_MAIN_NS.to_string()), &dao_vote_main_bin[..])?;
+    db_set(zkas_db, &serialize(&ZKAS_DAO_PROPOSE_BURN_NS.to_string()), &dao_propose_burn_bin[..])?;
+    db_set(zkas_db, &serialize(&ZKAS_DAO_PROPOSE_MAIN_NS.to_string()), &dao_propose_main_bin[..])?;
+
+    // Set up a database tree to hold the Merkle tree for DAO bullas
+    let dao_bulla_db = match db_lookup(cid, DAO_BULLA_TREE) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, DAO_BULLA_TREE)?,
+    };
+
+    match db_get(dao_bulla_db, &serialize(&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 _: MerkleTree = deserialize(&bytes)?;
+        }
+        None => {
+            // We didn't find a tree, so just make a new one.
+            let tree = MerkleTree::new(100);
+            db_set(dao_bulla_db, &serialize(&DAO_MERKLE_TREE), &serialize(&tree))?;
+        }
+    };
+
+    // Set up a database tree to hold Merkle roots for the DAO bullas Merkle tree
+    let _ = match db_lookup(cid, DAO_ROOTS_TREE) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, DAO_ROOTS_TREE)?,
+    };
+
+    // Set up a database tree to hold the Merkle tree for proposal bullas
+    let dao_proposal_db = match db_lookup(cid, DAO_PROPOSAL_TREE) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, DAO_PROPOSAL_TREE)?,
+    };
+
+    match db_get(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE))? {
+        Some(bytes) => {
+            // We found some bytes, try to deserialize into a tree.
+            // For now, if this doesn't work, we bail.
+            let _: MerkleTree = deserialize(&bytes)?;
+        }
+        None => {
+            // We didn't find a tree, so just make a new one.
+            let tree = MerkleTree::new(100);
+            db_set(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE), &serialize(&tree))?;
+        }
+    };
+
+    // Set up a database tree to hold Merkle roots for the proposal bullas Merkle tree
+    let _ = match db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, DAO_PROPOSAL_ROOTS_TREE)?,
+    };
+
+    // Set up a database tree to hold proposal votes (k: proposalbulla, v: ProposalVotes)
+    let _ = match db_lookup(cid, DAO_PROPOSAL_VOTES_TREE) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, DAO_PROPOSAL_VOTES_TREE)?,
+    };
+
+    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];
+
+    match DaoFunction::try_from(self_.data[0])? {
+        DaoFunction::Mint => {
+            let params: DaoMintParams = deserialize(&self_.data[1..])?;
+
+            // No checks in Mint, just return the 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)?;
+            set_return_data(&update_data)?;
+            msg!("[DAO Mint] State update set!");
+
+            Ok(())
+        }
+
+        DaoFunction::Propose => {
+            let params: DaoProposeParams = deserialize(&self_.data[1..])?;
+
+            // Check the Merkle roots for the input coins are valid
+            let money_cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
+            let coin_roots_db = db_lookup(money_cid, 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, DAO_ROOTS_TREE)?;
+            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))
+            }
+
+            // TODO: Look at gov tokens avoid using already spent ones
+            // Need to spend original coin and generate 2 nullifiers?
+
+            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(())
+        }
+
+        DaoFunction::Vote => {
+            let params: DaoVoteParams = deserialize(&self_.data[1..])?;
+
+            let money_cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+            // Check proposal bulla exists
+            let proposal_votes_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
+            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 proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
+
+            // Check the Merkle roots and nullifiers for the input coins are valid
+            let mut vote_nullifiers = vec![];
+            let mut all_vote_commit = pallas::Point::identity();
+            let money_roots_db = db_lookup(money_cid, COIN_ROOTS_TREE)?;
+            let money_nullifier_db = db_lookup(money_cid, NULLIFIERS_TREE)?;
+
+            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))
+                }
+
+                if vote_nullifiers.contains(&input.nullifier) ||
+                    proposal_votes.vote_nullifiers.contains(&input.nullifier)
+                {
+                    msg!("Attempted double vote");
+                    return Err(ContractError::Custom(7))
+                }
+
+                all_vote_commit += input.vote_commit;
+                vote_nullifiers.push(input.nullifier);
+            }
+
+            let update = DaoVoteUpdate {
+                proposal_bulla: params.proposal_bulla,
+                vote_nullifiers,
+                yes_vote_commit: params.yes_vote_commit,
+                all_vote_commit,
+            };
+            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(())
+        }
+
+        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
+            // FIXME: No hardcode of contract like this
+            assert!(call[0].contract_id == ContractId::from(pallas::Base::from(u64::MAX - 420)));
+            assert!(call[0].data[0] == MoneyFunction::Transfer 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 == params.coin_0);
+            assert!(mt_params.outputs[1].coin == 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, DAO_PROPOSAL_TREE)?;
+            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: ProposalVotes = deserialize(&proposal_votes)?;
+
+            // 4. Check yes_votes_commit and all_votes_commit are the same as in ProposalVotes
+            assert!(proposal_votes.yes_votes_commit == params.yes_votes_commit);
+            assert!(proposal_votes.all_votes_commit == params.all_votes_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(())
+        }
+    }
+}
+
+fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
+    match DaoFunction::try_from(ix[0])? {
+        DaoFunction::Mint => {
+            let update: DaoMintUpdate = deserialize(&ix[1..])?;
+
+            let bulla_db = db_lookup(cid, DAO_BULLA_TREE)?;
+            let roots_db = db_lookup(cid, DAO_ROOTS_TREE)?;
+
+            let node = MerkleNode::from(update.dao_bulla.inner());
+            merkle_add(bulla_db, roots_db, &serialize(&DAO_MERKLE_TREE), &node)?;
+
+            Ok(())
+        }
+
+        DaoFunction::Propose => {
+            let update: DaoProposeUpdate = deserialize(&ix[1..])?;
+
+            let proposal_tree_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
+            let proposal_root_db = db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE)?;
+            let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
+
+            let node = MerkleNode::from(update.proposal_bulla);
+            merkle_add(
+                proposal_tree_db,
+                proposal_root_db,
+                &serialize(&DAO_PROPOSAL_MERKLE_TREE),
+                &node,
+            )?;
+
+            let pv = ProposalVotes::default();
+            db_set(proposal_vote_db, &serialize(&update.proposal_bulla), &serialize(&pv))?;
+
+            Ok(())
+        }
+
+        DaoFunction::Vote => {
+            let mut update: DaoVoteUpdate = deserialize(&ix[1..])?;
+
+            let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
+
+            let Some(proposal_votes) = db_get(proposal_vote_db, &serialize(&update.proposal_bulla))? else {
+                msg!("Proposal {:?} not found in db", update.proposal_bulla);
+                return Err(ContractError::Custom(1));
+            };
+
+            let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
+
+            proposal_votes.yes_votes_commit += update.yes_vote_commit;
+            proposal_votes.all_votes_commit += update.all_vote_commit;
+            proposal_votes.vote_nullifiers.append(&mut update.vote_nullifiers);
+
+            Ok(())
+        }
+
+        DaoFunction::Exec => {
+            let update: DaoExecUpdate = deserialize(&ix[1..])?;
+
+            // TODO: Implement db_del
+            // Remove proposal from db
+
+            Ok(())
+        }
+    }
+}
+
+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])? {
+        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![];
+
+            zk_public_values.push((ZKAS_DAO_MINT_NS.to_string(), vec![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(())
+        }
+
+        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((
+                    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((
+                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(())
+        }
+
+        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_votes_commit = pallas::Point::identity();
+
+            for input in &params.inputs {
+                signature_pubkeys.push(input.signature_public);
+                all_votes_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((
+                    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_votes_commit.to_affine().coordinates().unwrap();
+
+            zk_public_values.push((
+                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(())
+        }
+
+        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 yes_votes_coords = params.yes_votes_commit.to_affine().coordinates().unwrap();
+            let all_votes_coords = params.all_votes_commit.to_affine().coordinates().unwrap();
+            let input_value_coords = params.input_value_commit.to_affine().coordinates().unwrap();
+
+            zk_public_values.push((
+                ZKAS_DAO_EXEC_NS.to_string(),
+                vec![
+                    params.proposal,
+                    params.coin_0,
+                    params.coin_1,
+                    *yes_votes_coords.x(),
+                    *yes_votes_coords.y(),
+                    *all_votes_coords.x(),
+                    *all_votes_coords.y(),
+                    *input_value_coords.x(),
+                    *input_value_coords.y(),
+                    pallas::Base::from(u64::MAX - 420), // <-- TODO: Should be money contract id?
+                    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(())
+        }
+    }
+}

+ 46 - 0
src/contract/dao/src/lib.rs

@@ -0,0 +1,46 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 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;
+
+#[cfg(not(feature = "no-entrypoint"))]
+pub mod entrypoint;
+
+pub mod state;
+
+#[repr(u8)]
+pub enum DaoFunction {
+    Mint = 0x00,
+    Propose = 0x01,
+    Vote = 0x02,
+    Exec = 0x03,
+}
+
+impl TryFrom<u8> for DaoFunction {
+    type Error = ContractError;
+
+    fn try_from(x: u8) -> core::result::Result<DaoFunction, Self::Error> {
+        match x {
+            0x00 => Ok(DaoFunction::Mint),
+            0x01 => Ok(DaoFunction::Propose),
+            0x02 => Ok(DaoFunction::Vote),
+            0x03 => Ok(DaoFunction::Exec),
+            _ => Err(ContractError::InvalidFunction),
+        }
+    }
+}

+ 138 - 0
src/contract/dao/src/state.rs

@@ -0,0 +1,138 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 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::{MerkleNode, Nullifier, PublicKey},
+    pasta::{group::Group, pallas},
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+#[derive(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)
+    }
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct ProposalVotes {
+    // TODO: Might be more logical to have `yes_votes_commit` and `no_votes_commit`
+    /// Weighted vote commit
+    pub yes_votes_commit: pallas::Point,
+    /// All value staked in the vote
+    pub all_votes_commit: pallas::Point,
+    /// Vote nullifiers
+    pub vote_nullifiers: Vec<Nullifier>,
+}
+
+impl ProposalVotes {
+    pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
+        self.vote_nullifiers.iter().any(|n| n == nullifier)
+    }
+}
+
+impl Default for ProposalVotes {
+    fn default() -> Self {
+        Self {
+            yes_votes_commit: pallas::Point::identity(),
+            all_votes_commit: pallas::Point::identity(),
+            vote_nullifiers: vec![],
+        }
+    }
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoMintParams {
+    pub dao_bulla: DaoBulla,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoMintUpdate {
+    pub dao_bulla: DaoBulla,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct ProposeInput {
+    pub value_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+#[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<ProposeInput>,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoProposeUpdate {
+    pub proposal_bulla: pallas::Base,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct VoteInput {
+    pub nullifier: Nullifier,
+    pub vote_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+#[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<VoteInput>,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoVoteUpdate {
+    pub proposal_bulla: pallas::Base,
+    pub vote_nullifiers: Vec<Nullifier>,
+    pub yes_vote_commit: pallas::Point,
+    pub all_vote_commit: pallas::Point,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoExecParams {
+    pub proposal: pallas::Base,
+    pub coin_0: pallas::Base,
+    pub coin_1: pallas::Base,
+    pub yes_votes_commit: pallas::Point,
+    pub all_votes_commit: pallas::Point,
+    pub input_value_commit: pallas::Point,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoExecUpdate {
+    pub proposal: pallas::Base,
+}

+ 0 - 1
src/contract/money/Makefile

@@ -34,5 +34,4 @@ test: all
 clean:
 	rm -f $(ZKAS_BIN) $(WASM_BIN)
 
-# We always rebuild the wasm no matter what
 .PHONY: all test clean

+ 6 - 0
src/sdk/src/error.rs

@@ -68,6 +68,9 @@ pub enum ContractError {
 
     #[error("Db contains_key failed")]
     DbContainsKeyFailed,
+
+    #[error("Invalid function call")]
+    InvalidFunction,
 }
 
 /// Builtin return values occupy the upper 32 bits
@@ -91,6 +94,7 @@ pub const DB_SET_FAILED: i64 = to_builtin!(11);
 pub const DB_LOOKUP_FAILED: i64 = to_builtin!(12);
 pub const DB_GET_FAILED: i64 = to_builtin!(13);
 pub const DB_CONTAINS_KEY_FAILED: i64 = to_builtin!(14);
+pub const INVALID_FUNCTION: i64 = to_builtin!(15);
 
 impl From<ContractError> for i64 {
     fn from(err: ContractError) -> Self {
@@ -108,6 +112,7 @@ impl From<ContractError> for i64 {
             ContractError::DbLookupFailed => DB_LOOKUP_FAILED,
             ContractError::DbGetFailed => DB_GET_FAILED,
             ContractError::DbContainsKeyFailed => DB_CONTAINS_KEY_FAILED,
+            ContractError::InvalidFunction => INVALID_FUNCTION,
             ContractError::Custom(error) => {
                 if error == 0 {
                     CUSTOM_ZERO
@@ -136,6 +141,7 @@ impl From<i64> for ContractError {
             DB_LOOKUP_FAILED => Self::DbLookupFailed,
             DB_GET_FAILED => Self::DbGetFailed,
             DB_CONTAINS_KEY_FAILED => Self::DbContainsKeyFailed,
+            INVALID_FUNCTION => Self::InvalidFunction,
             _ => Self::Custom(error as u32),
         }
     }