Просмотр исходного кода

dao_demo: further restructure and simplify architecture. mark CLI TODOs

lunar-mining 3 лет назад
Родитель
Сommit
197e770afa
2 измененных файлов с 430 добавлено и 418 удалено
  1. 369 404
      bin/dao/daod/src/main.rs
  2. 61 14
      bin/dao/daod/src/rpc.rs

+ 369 - 404
bin/dao/daod/src/main.rs

@@ -1,4 +1,4 @@
-use std::{any::TypeId, sync::Arc, time::Instant};
+use std::{any::TypeId, collections::HashMap, sync::Arc, time::Instant};
 
 use incrementalmerkletree::{Position, Tree};
 use log::debug;
@@ -36,54 +36,33 @@ use crate::{
         money_contract::{self, state::OwnCoin, transfer::Note},
     },
     rpc::JsonRpcInterface,
-    util::{sign, FuncCall, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID},
+    util::{
+        sign, FuncCall, HashableBase, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID,
+    },
 };
 
-/////////////////////////////////////////////////////////////////////////////////////////
-// TODO: restructure to this architecture.
-// Note: to make a Proposal, you need the dao_leaf_position
-// to make a Vote, you need the dao decryption key
-// Everyone has a unique money_wallet and a copy of the dao_wallet in their Client.
-//
-// pub struct Cashier {
-//      cashier_wallet: CashierWallet,
-//      zk_bins, ...
-//      states ...
-// }
-//
-// impl Cashier {
-//      init() ...
-//      mint_treasury()...
-//      airdrop() ...
-// }
-//
-// pub struct Dao {
-//      dao_params: DaoParams,
-//      dao_wallet: DaoWallet,
-// }
-//
-// pub struct Client {
-//      dao: Dao,
-//      money_wallet: MoneyWallet,
-// }
-//
-// fn start() {
-//      cashier::init();
-//
-//      match input {
-//          dao_create() => Dao::new()
-//          wallet_create() => Client::new(money_wallet::new(), dao)
-//      }
-// }
-
 pub struct Client {
     dao_wallet: DaoWallet,
-    money_wallet: MoneyWallet,
-    states: StateRegistry,
-    zk_bins: ZkContractTable,
+    money_wallets: HashMap<String, MoneyWallet>,
 }
 
 impl Client {
+    fn new() -> Self {
+        let dao_wallet = DaoWallet::new();
+
+        let money_wallets = HashMap::default();
+
+        Self { dao_wallet, money_wallets }
+    }
+
+    fn new_money_wallet(&mut self, key: String) {
+        let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let leaf_position = Position::zero();
+        let money_wallet = MoneyWallet { keypair, signature_secret, leaf_position };
+        self.money_wallets.insert(key, money_wallet);
+    }
+
     // TODO: user passes DAO approval ratio: 1/2
     // we parse that into dao_approval_ratio_base and dao_approval_ratio_quot
     fn create_dao(
@@ -93,156 +72,62 @@ impl Client {
         dao_approval_ratio_quot: u64,
         dao_approval_ratio_base: u64,
         token_id: pallas::Base,
-    ) -> pallas::Base {
-        let tx = self.dao_wallet.build_mint_tx(
+        zk_bins: &ZkContractTable,
+        states: &mut StateRegistry,
+    ) -> Result<pallas::Base> {
+        let tx = self.dao_wallet.mint_tx(
             dao_proposer_limit,
             dao_quorum,
             dao_approval_ratio_quot,
             dao_approval_ratio_base,
             token_id,
-            &self.zk_bins,
+            zk_bins,
         );
 
-        self.validate(&tx);
-
-        self.dao_wallet.balances(&mut self.states);
+        // TODO: Proper error handling.
+        // Only witness the value once the transaction is confirmed.
+        match self.validate(&tx, states, zk_bins) {
+            Ok(v) => self.dao_wallet.update_witness(states)?,
+            Err(e) => {}
+        }
 
+        // Retrieve DAO bulla from the state.
         let dao_bulla = {
-            assert_eq!(tx.func_calls.len(), 1);
             let func_call = &tx.func_calls[0];
             let call_data = func_call.call_data.as_any();
-            assert_eq!(
-                (&*call_data).type_id(),
-                TypeId::of::<dao_contract::mint::validate::CallData>()
-            );
             let call_data =
                 call_data.downcast_ref::<dao_contract::mint::validate::CallData>().unwrap();
             call_data.dao_bulla.clone()
         };
 
+        // TODO: instead of this print statement, return DAO bulla to CLI
         debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0);
 
-        dao_bulla.0
-    }
-
-    fn init(&mut self) -> Result<()> {
-        debug!(target: "demo", "Loading dao-mint.zk");
-        let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin");
-        let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?;
-        self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13);
-
-        debug!(target: "demo", "Loading money-transfer contracts");
-        let start = Instant::now();
-        let mint_pk = ProvingKey::build(11, &MintContract::default());
-        debug!("Mint PK: [{:?}]", start.elapsed());
-        let start = Instant::now();
-        let burn_pk = ProvingKey::build(11, &BurnContract::default());
-        debug!("Burn PK: [{:?}]", start.elapsed());
-        let start = Instant::now();
-        let mint_vk = VerifyingKey::build(11, &MintContract::default());
-        debug!("Mint VK: [{:?}]", start.elapsed());
-        let start = Instant::now();
-        let burn_vk = VerifyingKey::build(11, &BurnContract::default());
-        debug!("Burn VK: [{:?}]", start.elapsed());
-
-        self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk);
-        self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk);
-        debug!(target: "demo", "Loading dao-propose-main.zk");
-        let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin");
-        let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?;
-        self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13);
-        debug!(target: "demo", "Loading dao-propose-burn.zk");
-        let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin");
-        let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?;
-        self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13);
-        debug!(target: "demo", "Loading dao-vote-main.zk");
-        let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin");
-        let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?;
-        self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13);
-        debug!(target: "demo", "Loading dao-vote-burn.zk");
-        let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin");
-        let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?;
-        self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13);
-        let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin");
-        let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?;
-        self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13);
-
-        // State for money contracts
-        // TODO: we need the cashier value elsewhere.
-        let cashier_signature_secret = SecretKey::random(&mut OsRng);
-        let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret);
-        let faucet_signature_secret = SecretKey::random(&mut OsRng);
-        let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret);
+        // We create a hashmap so we can easily retrieve DAO values for the demo.
+        let dao_params = DaoParams {
+            proposer_limit: dao_proposer_limit,
+            quorum: dao_quorum,
+            approval_ratio_quot: dao_approval_ratio_quot,
+            approval_ratio_base: dao_approval_ratio_base,
+            gov_token_id: token_id,
+            public_key: self.dao_wallet.keypair.public,
+            bulla_blind: self.dao_wallet.bulla_blind,
+        };
 
-        ///////////////////////////////////////////////////
-        let money_state =
-            money_contract::state::State::new(cashier_signature_public, faucet_signature_public);
-        self.states.register(*money_contract::CONTRACT_ID, money_state);
-        /////////////////////////////////////////////////////
-        let dao_state = dao_contract::State::new();
-        self.states.register(*dao_contract::CONTRACT_ID, dao_state);
-        /////////////////////////////////////////////////////
+        self.dao_wallet.params.insert(HashableBase(dao_bulla.0), dao_params);
 
-        Ok(())
+        Ok(dao_bulla.0)
     }
 
-    // TODO: user passes "gDRK", we match with gDRK tokenID
-    fn mint_treasury(
+    // TODO: Change these into errors instead of expects.
+    fn validate(
         &mut self,
-        token_id: pallas::Base,
-        token_supply: u64,
-        dao_bulla: pallas::Base,
-        recipient: PublicKey,
+        tx: &Transaction,
+        states: &mut StateRegistry,
+        zk_bins: &ZkContractTable,
     ) -> Result<()> {
-        let spend_hook = *dao_contract::exec::FUNC_ID;
-
-        let user_data = dao_bulla;
-        let value = token_supply;
-        let tx = self.money_wallet.build_transfer_tx(
-            value,
-            token_id,
-            spend_hook,
-            user_data,
-            recipient,
-            &self.zk_bins,
-        )?;
-
-        self.validate(&tx);
-
-        let own_coin = self.dao_wallet.balances(&mut self.states)?;
-
-        // TODO: return own_coin.note.value to CLI
-
-        Ok(())
-    }
-
-    fn airdrop(&mut self, value: u64, token_id: pallas::Base, recipient: PublicKey) -> Result<()> {
-        // Spend hook and user data disabled
-        let spend_hook = DrkSpendHook::from(0);
-        let user_data = DrkUserData::from(0);
-
-        let tx = self.money_wallet.build_transfer_tx(
-            value,
-            token_id,
-            spend_hook,
-            user_data,
-            recipient,
-            &self.zk_bins,
-        )?;
-
-        self.validate(&tx);
-
-        let own_coin = self.money_wallet.balances(&mut self.states)?;
-
-        // TODO: return own_coin.note.value to CLI
-
-        Ok(())
-    }
-
-    fn validate(&mut self, tx: &Transaction) -> Result<()> {
         let mut updates = vec![];
 
-        let states = &self.states;
         // Validate all function calls in the tx
         for (idx, func_call) in tx.func_calls.iter().enumerate() {
             // So then the verifier will lookup the corresponding state_transition and apply
@@ -278,27 +163,33 @@ impl Client {
 
         // Atomically apply all changes
         for update in updates {
-            update.apply(&mut self.states);
+            update.apply(states);
         }
 
-        tx.zk_verify(&self.zk_bins);
+        tx.zk_verify(zk_bins);
         tx.verify_sigs();
 
         Ok(())
     }
 
+    // TODO: error handling
     fn propose(
         &mut self,
         params: DaoParams,
         recipient: PublicKey,
         token_id: pallas::Base,
         amount: u64,
+        key: String,
+        states: &mut StateRegistry,
+        zk_bins: &ZkContractTable,
     ) -> Result<()> {
-        let dao_leaf_position = self.dao_wallet.witness(&mut self.states)?;
+        let dao_leaf_position = self.dao_wallet.leaf_position;
+
+        let mut money_wallet = self.money_wallets.get_mut(&key).unwrap();
 
-        let tx = self.money_wallet.build_propose_tx(
-            &mut self.states,
-            &self.zk_bins,
+        let tx = money_wallet.propose_tx(
+            states,
+            &zk_bins,
             params,
             recipient,
             token_id,
@@ -306,65 +197,110 @@ impl Client {
             dao_leaf_position,
         )?;
 
-        self.validate(&tx)?;
+        self.validate(&tx, states, zk_bins)?;
 
         self.dao_wallet.read_proposal(&tx)?;
 
         Ok(())
     }
+}
 
-    // TODO: User must have the values Proposal and DaoParams in order to cast a vote.
-    // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams)
-    // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request.
-    fn vote(&mut self, vote_option: bool, proposal: Proposal, dao_params: DaoParams) -> Result<()> {
-        let dao_keypair = self.dao_wallet.get_vote_decryption_key();
-
-        let tx = self.money_wallet.build_vote_tx(
-            vote_option,
-            &mut self.states,
-            &self.zk_bins,
-            dao_keypair,
-            proposal,
-            dao_params,
-        )?;
+struct DaoWallet {
+    keypair: Keypair,
+    signature_secret: SecretKey,
+    bulla_blind: pallas::Base,
+    leaf_position: Position,
+    params: HashMap<HashableBase, DaoParams>,
+    vote_notes: Vec<dao_contract::vote::wallet::Note>,
+}
+impl DaoWallet {
+    fn new() -> Self {
+        let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let bulla_blind = pallas::Base::random(&mut OsRng);
+        let leaf_position = Position::zero();
+        let params: HashMap<HashableBase, DaoParams> = HashMap::default();
+        let vote_notes = Vec::new();
 
-        self.validate(&tx)?;
+        Self { keypair, signature_secret, bulla_blind, leaf_position, params, vote_notes }
+    }
 
-        self.dao_wallet.read_vote(&tx)?;
+    // Mint the DAO bulla.
+    fn mint_tx(
+        &mut self,
+        dao_proposer_limit: u64,
+        dao_quorum: u64,
+        dao_approval_ratio_quot: u64,
+        dao_approval_ratio_base: u64,
+        token_id: pallas::Base,
+        zk_bins: &ZkContractTable,
+    ) -> Transaction {
+        let builder = dao_contract::mint::wallet::Builder {
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            gov_token_id: *GDRK_ID,
+            dao_pubkey: self.keypair.public,
+            dao_bulla_blind: self.bulla_blind,
+            _signature_secret: self.signature_secret,
+        };
+        let func_call = builder.build(zk_bins);
+        let func_calls = vec![func_call];
 
-        Ok(())
+        let signatures = sign(vec![self.signature_secret], &func_calls);
+        Transaction { func_calls, signatures }
     }
 
-    // TODO: user must pass in a base58 encoded string of the Proposal, proposal_bulla and
-    // DaoParams
-    fn exec(
-        &mut self,
-        proposal: Proposal,
-        proposal_bulla: pallas::Base,
-        dao_params: DaoParams,
-    ) -> Result<()> {
-        self.dao_wallet.build_exec_tx(
-            &mut self.states,
-            &self.zk_bins,
-            proposal,
-            proposal_bulla,
-            dao_params,
-        )?;
+    // TODO: error handling
+    fn update_witness(&mut self, states: &mut StateRegistry) -> Result<()> {
+        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::CONTRACT_ID).unwrap();
+        let path = state.dao_tree.witness();
+        match path {
+            Some(path) => {
+                self.leaf_position = path;
+            }
+            None => {}
+        }
         Ok(())
     }
-}
 
-// DAO private values. This class is purely concerned with the DAO treasury.
-// TODO: we must call track() for keypairs before we can query the balance.
-pub struct DaoWallet {
-    keypair: Keypair,
-    vote_keypair: Keypair,
-    signature: SecretKey,
-    params: DaoParams,
-    vote_notes: Vec<dao_contract::vote::wallet::Note>,
-}
+    fn balances(&self, states: &mut StateRegistry) -> Result<OwnCoin> {
+        let state =
+            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+
+        let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret);
+
+        let dao_recv_coin = recv_coins.pop().unwrap();
+        let treasury_note = dao_recv_coin.note.clone();
+
+        let coords = self.keypair.public.0.to_affine().coordinates().unwrap();
+        let coin = poseidon_hash::<8>([
+            *coords.x(),
+            *coords.y(),
+            DrkValue::from(treasury_note.value),
+            treasury_note.token_id,
+            treasury_note.serial,
+            treasury_note.spend_hook,
+            treasury_note.user_data,
+            treasury_note.coin_blind,
+        ]);
+
+        // TODO: Error handling
+        if coin == dao_recv_coin.coin.0 {
+            // return Ok(dao_recv_coin)
+        }
+        // else {
+        //  return Err::InvalidCoin
+        // }
+
+        // TODO: this is the CLI output.
+        debug!("DAO received a coin worth {} xDRK", treasury_note.value);
+
+        // TODO: just return the value of the coin, not OwnCoin.
+        Ok(dao_recv_coin)
+    }
 
-impl DaoWallet {
     fn read_proposal(&self, tx: &Transaction) -> Result<()> {
         let (proposal, proposal_bulla) = {
             let func_call = &tx.func_calls[0];
@@ -390,7 +326,6 @@ impl DaoWallet {
 
         // TODO: encode Proposal as base58 and return to cli
     }
-
     // We decrypt the votes in a transaction and add it to the wallet.
     fn read_vote(&mut self, tx: &Transaction) -> Result<()> {
         let vote_note = {
@@ -401,7 +336,7 @@ impl DaoWallet {
 
             let header = &call_data.header;
             let note: dao_contract::vote::wallet::Note =
-                header.enc_note.decrypt(&self.vote_keypair.secret).unwrap();
+                header.enc_note.decrypt(&self.keypair.secret).unwrap();
             note
         };
 
@@ -416,84 +351,8 @@ impl DaoWallet {
         Ok(())
     }
 
-    // We need to encrypt votes to the DAO secret key for this demo, which requires users
-    // to have access to a secret key operated by the DAO. We create a specific key for decrypting
-    // votes which is different to the key that operates the treasury.
-    fn get_vote_decryption_key(&self) -> Keypair {
-        self.vote_keypair
-    }
-
-    fn track(&self, states: &mut StateRegistry) -> Result<()> {
-        let state =
-            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
-        state.wallet_cache.track(self.keypair.secret);
-
-        Ok(())
-    }
-
-    fn witness(&self, states: &mut StateRegistry) -> Result<Position> {
-        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::CONTRACT_ID).unwrap();
-        let path = state.dao_tree.witness();
-        // TODO: error handling
-        //if path.is_some() {
-        return Ok(path.unwrap())
-        //}
-    }
-
-    fn balances(&self, states: &mut StateRegistry) -> Result<OwnCoin> {
-        let state =
-            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
-
-        let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret);
-
-        let dao_recv_coin = recv_coins.pop().unwrap();
-        let treasury_note = dao_recv_coin.note.clone();
-
-        debug!("DAO received a coin worth {} xDRK", treasury_note.value);
-
-        Ok(dao_recv_coin)
-    }
-
-    // TODO: encode this to base58 and display in cli
-    fn params(&self) -> Result<&DaoParams> {
-        Ok(&self.params)
-    }
-
-    fn build_mint_tx(
-        &self,
-        dao_proposer_limit: u64,
-        dao_quorum: u64,
-        dao_approval_ratio_quot: u64,
-        dao_approval_ratio_base: u64,
-        token_id: pallas::Base,
-        zk_bins: &ZkContractTable,
-    ) -> Transaction {
-        // TODO: store this?
-        let dao_bulla_blind = pallas::Base::random(&mut OsRng);
-
-        let builder = dao_contract::mint::wallet::Builder {
-            dao_proposer_limit,
-            dao_quorum,
-            dao_approval_ratio_quot,
-            dao_approval_ratio_base,
-            gov_token_id: *GDRK_ID,
-            dao_pubkey: self.keypair.public,
-            dao_bulla_blind,
-            _signature_secret: self.signature,
-        };
-        let func_call = builder.build(zk_bins);
-        let func_calls = vec![func_call];
-
-        // TODO: this should be a cashier key?
-        let signatures = sign(vec![self.signature], &func_calls);
-        let tx = Transaction { func_calls, signatures };
-        tx
-    }
-
-    // We use this to prove ownership of treasury tokens.
-    // Right now this method is duplicated on both wallets but doesn't need to be.
-    // TODO: clean up the architecture.
-    fn coin_path(
+    // TODO: Explicit error handling.
+    fn get_treasury_path(
         &self,
         states: &StateRegistry,
         own_coin: &OwnCoin,
@@ -519,13 +378,20 @@ impl DaoWallet {
         proposal_bulla: pallas::Base,
         dao_params: DaoParams,
     ) -> Result<Transaction> {
+        // TODO: move these to DAO struct?
+        let tx_signature_secret = SecretKey::random(&mut OsRng);
+        let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+        // We must prove we have sufficient governance tokens to execute this.
         let own_coin = self.balances(states)?;
 
-        let (treasury_leaf_position, treasury_merkle_path) = self.coin_path(states, &own_coin)?;
+        let (treasury_leaf_position, treasury_merkle_path) =
+            self.get_treasury_path(states, &own_coin)?;
 
         let input_value = own_coin.note.value;
 
         // TODO: not sure what this is doing
+        // Should this be moved into a different struct?
         let user_serial = pallas::Base::random(&mut OsRng);
         let user_coin_blind = pallas::Base::random(&mut OsRng);
         let user_data_blind = pallas::Base::random(&mut OsRng);
@@ -542,7 +408,7 @@ impl DaoWallet {
                 user_data_blind,
                 value_blind: input_value_blind,
                 // TODO: in schema, we create random signatures here. why?
-                signature_secret: self.signature,
+                signature_secret: tx_signature_secret,
             }
         };
 
@@ -609,31 +475,29 @@ impl DaoWallet {
                 input_value: proposal.amount,
                 input_value_blind,
                 hook_dao_exec: *dao_contract::exec::FUNC_ID,
-                signature_secret: self.signature,
+                signature_secret: exec_signature_secret,
             }
         };
 
         let exec_func_call = builder.build(zk_bins);
         let func_calls = vec![transfer_func_call, exec_func_call];
 
-        // TODO: we sign both transactions with the same sig, is this wrong?
-        let signatures = sign(vec![self.signature, self.signature], &func_calls);
+        let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls);
         Ok(Transaction { func_calls, signatures })
     }
 }
-// Money private values.
-pub struct MoneyWallet {
+
+// Stores governance tokens and related secret values.
+#[derive(Clone)]
+struct MoneyWallet {
     keypair: Keypair,
-    signature: SecretKey,
+    signature_secret: SecretKey,
+    leaf_position: Position,
 }
 
 impl MoneyWallet {
-    fn track(&self, states: &mut StateRegistry) -> Result<()> {
-        let state =
-            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
-        state.wallet_cache.track(self.keypair.secret);
-
-        Ok(())
+    fn signature_public(&self) -> PublicKey {
+        PublicKey::from_secret(self.signature_secret)
     }
 
     fn balances(&self, states: &mut StateRegistry) -> Result<OwnCoin> {
@@ -645,66 +509,14 @@ impl MoneyWallet {
         let recv_coin = recv_coins.pop().unwrap();
         let note = recv_coin.note.clone();
 
+        // TODO: this should output to command line
         debug!("User received a coin worth {} gDRK", note.value);
 
+        // TODO: don't return the coin, just return the value
         Ok(recv_coin)
     }
 
-    // We use this to prove ownership of governance tokens.
-    fn coin_path(
-        &self,
-        states: &StateRegistry,
-        own_coin: &OwnCoin,
-    ) -> Result<(Position, Vec<MerkleNode>)> {
-        let (money_leaf_position, money_merkle_path) = {
-            let state =
-                states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
-            let tree = &state.tree;
-            let leaf_position = own_coin.leaf_position.clone();
-            let root = tree.root(0).unwrap();
-            let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
-            (leaf_position, merkle_path)
-        };
-
-        Ok((money_leaf_position, money_merkle_path))
-    }
-
-    fn build_transfer_tx(
-        &self,
-        value: u64,
-        token_id: pallas::Base,
-        spend_hook: pallas::Base,
-        user_data: pallas::Base,
-        recipient: PublicKey,
-        zk_bins: &ZkContractTable,
-    ) -> Result<Transaction> {
-        let builder = {
-            money_contract::transfer::wallet::Builder {
-                clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo {
-                    value,
-                    token_id,
-                    signature_secret: self.signature,
-                }],
-                inputs: vec![],
-                outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo {
-                    value,
-                    token_id,
-                    public: recipient,
-                    serial: pallas::Base::random(&mut OsRng),
-                    coin_blind: pallas::Base::random(&mut OsRng),
-                    spend_hook,
-                    user_data,
-                }],
-            }
-        };
-        let func_call = builder.build(zk_bins)?;
-        let func_calls = vec![func_call];
-
-        let signatures = sign(vec![self.signature], &func_calls);
-        Ok(Transaction { func_calls, signatures })
-    }
-
-    fn build_propose_tx(
+    fn propose_tx(
         &mut self,
         states: &mut StateRegistry,
         zk_bins: &ZkContractTable,
@@ -714,9 +526,11 @@ impl MoneyWallet {
         amount: u64,
         dao_leaf_position: Position,
     ) -> Result<Transaction> {
+        // To be able to make a proposal, we must prove we have ownership of governance tokens,
+        // and that the quantity of governance tokens is within the accepted proposal limit.
         let own_coin = self.balances(states)?;
 
-        let (money_leaf_position, money_merkle_path) = self.coin_path(&states, &own_coin)?;
+        let (money_leaf_position, money_merkle_path) = self.get_path(&states, &own_coin)?;
 
         let signature_secret = SecretKey::random(&mut OsRng);
 
@@ -764,7 +578,29 @@ impl MoneyWallet {
         Ok(Transaction { func_calls, signatures })
     }
 
-    fn build_vote_tx(
+    // TODO: Explicit error handling.
+    fn get_path(
+        &self,
+        states: &StateRegistry,
+        own_coin: &OwnCoin,
+    ) -> Result<(Position, Vec<MerkleNode>)> {
+        let (money_leaf_position, money_merkle_path) = {
+            let state =
+                states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+            let tree = &state.tree;
+            let leaf_position = own_coin.leaf_position.clone();
+            let root = tree.root(0).unwrap();
+            let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+            (leaf_position, merkle_path)
+        };
+
+        Ok((money_leaf_position, money_merkle_path))
+    }
+
+    // TODO: User must have the values Proposal and DaoParams in order to cast a vote.
+    // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams)
+    // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request.
+    fn vote_tx(
         &mut self,
         vote_option: bool,
         states: &mut StateRegistry,
@@ -773,9 +609,10 @@ impl MoneyWallet {
         proposal: Proposal,
         dao_params: DaoParams,
     ) -> Result<Transaction> {
+        // We must prove we have governance tokens in order to vote.
         let own_coin = self.balances(states)?;
 
-        let (money_leaf_position, money_merkle_path) = self.coin_path(states, &own_coin)?;
+        let (money_leaf_position, money_merkle_path) = self.get_path(states, &own_coin)?;
 
         let input = {
             dao_contract::vote::wallet::BuilderInput {
@@ -783,7 +620,7 @@ impl MoneyWallet {
                 note: own_coin.note.clone(),
                 leaf_position: money_leaf_position,
                 merkle_path: money_merkle_path,
-                signature_secret: self.signature,
+                signature_secret: self.signature_secret,
             }
         };
 
@@ -802,62 +639,187 @@ impl MoneyWallet {
         let func_call = builder.build(zk_bins);
         let func_calls = vec![func_call];
 
-        let signatures = sign(vec![self.signature], &func_calls);
+        let signatures = sign(vec![self.signature_secret], &func_calls);
         Ok(Transaction { func_calls, signatures })
     }
 }
 
-pub struct DaoDemo {}
+async fn start_rpc(demo: Demo) -> Result<()> {
+    let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?;
+
+    let client = JsonRpcInterface::new(demo);
+
+    let rpc_interface = Arc::new(client);
+
+    listen_and_serve(rpc_addr, rpc_interface).await?;
+    Ok(())
+}
+
+pub struct Demo {
+    cashier: Cashier,
+    client: Client,
+    states: StateRegistry,
+    zk_bins: ZkContractTable,
+}
+
+impl Demo {
+    fn new() -> Self {
+        let cashier = Cashier::new();
+        let client = Client::new();
+
+        // Lookup table for smart contract states
+        let mut states = StateRegistry::new();
+
+        // Initialize ZK binary table
+        let mut zk_bins = ZkContractTable::new();
 
-impl DaoDemo {
-    pub fn new() -> Self {
-        Self {}
+        Self { cashier, client, states, zk_bins }
     }
 
     fn init(&mut self) -> Result<()> {
-        Ok(())
-    }
+        // We use these to initialize the money state.
+        let faucet_signature_secret = SecretKey::random(&mut OsRng);
+        let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret);
 
-    fn create(&mut self) -> Result<()> {
-        Ok(())
-    }
+        debug!(target: "demo", "Loading dao-mint.zk");
+        let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin");
+        let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?;
+        self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13);
+
+        debug!(target: "demo", "Loading money-transfer contracts");
+        let start = Instant::now();
+        let mint_pk = ProvingKey::build(11, &MintContract::default());
+        debug!("Mint PK: [{:?}]", start.elapsed());
+        let start = Instant::now();
+        let burn_pk = ProvingKey::build(11, &BurnContract::default());
+        debug!("Burn PK: [{:?}]", start.elapsed());
+        let start = Instant::now();
+        let mint_vk = VerifyingKey::build(11, &MintContract::default());
+        debug!("Mint VK: [{:?}]", start.elapsed());
+        let start = Instant::now();
+        let burn_vk = VerifyingKey::build(11, &BurnContract::default());
+        debug!("Burn VK: [{:?}]", start.elapsed());
+
+        self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk);
+        self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk);
+        debug!(target: "demo", "Loading dao-propose-main.zk");
+        let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin");
+        let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?;
+        self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13);
+        debug!(target: "demo", "Loading dao-propose-burn.zk");
+        let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin");
+        let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?;
+        self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13);
+        debug!(target: "demo", "Loading dao-vote-main.zk");
+        let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin");
+        let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?;
+        self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13);
+        debug!(target: "demo", "Loading dao-vote-burn.zk");
+        let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin");
+        let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?;
+        self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13);
+        let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin");
+        let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?;
+        self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13);
+
+        let cashier_signature_public = self.cashier.signature_public();
+
+        let money_state =
+            money_contract::state::State::new(cashier_signature_public, faucet_signature_public);
+        self.states.register(*money_contract::CONTRACT_ID, money_state);
+
+        let dao_state = dao_contract::State::new();
+        self.states.register(*dao_contract::CONTRACT_ID, dao_state);
 
-    fn mint(&mut self) -> Result<()> {
         Ok(())
     }
+}
 
-    fn airdrop(&mut self) -> Result<()> {
-        Ok(())
+// Mint authority that mints the DAO treasury and airdrops governance tokens.
+#[derive(Clone)]
+struct Cashier {
+    keypair: Keypair,
+    signature_secret: SecretKey,
+}
+
+impl Cashier {
+    fn new() -> Self {
+        let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        Self { keypair, signature_secret }
     }
 
-    fn propose(&mut self) -> Result<()> {
-        Ok(())
+    fn signature_public(&self) -> PublicKey {
+        PublicKey::from_secret(self.signature_secret)
     }
 
-    fn vote(&mut self) -> Result<()> {
-        Ok(())
+    fn mint_treasury(
+        &mut self,
+        token_id: pallas::Base,
+        token_supply: u64,
+        dao_bulla: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        let spend_hook = *dao_contract::exec::FUNC_ID;
+        let user_data = dao_bulla;
+        let value = token_supply;
+
+        let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?;
+
+        Ok(tx)
     }
 
-    fn exec(&mut self) -> Result<()> {
-        Ok(())
+    fn transfer_tx(
+        &self,
+        value: u64,
+        token_id: pallas::Base,
+        spend_hook: pallas::Base,
+        user_data: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        let builder = {
+            money_contract::transfer::wallet::Builder {
+                clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo {
+                    value,
+                    token_id,
+                    signature_secret: self.signature_secret,
+                }],
+                inputs: vec![],
+                outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo {
+                    value,
+                    token_id,
+                    public: recipient,
+                    serial: pallas::Base::random(&mut OsRng),
+                    coin_blind: pallas::Base::random(&mut OsRng),
+                    spend_hook,
+                    user_data,
+                }],
+            }
+        };
+        let func_call = builder.build(zk_bins)?;
+        let func_calls = vec![func_call];
+
+        let signatures = sign(vec![self.signature_secret], &func_calls);
+        Ok(Transaction { func_calls, signatures })
     }
-}
 
-async fn start() -> Result<()> {
-    // daod
-    //
-    let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?;
-    let mut demo = DaoDemo::new();
-    /////////////////////////////////////////////////
-    //// init()
-    /////////////////////////////////////////////////
-    demo.init()?;
-    let client = JsonRpcInterface::new(demo);
+    fn airdrop(
+        &mut self,
+        value: u64,
+        token_id: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        // Spend hook and user data disabled
+        let spend_hook = DrkSpendHook::from(0);
+        let user_data = DrkUserData::from(0);
 
-    let rpc_interface = Arc::new(client);
+        let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?;
 
-    listen_and_serve(rpc_addr, rpc_interface).await?;
-    Ok(())
+        Ok(tx)
+    }
 }
 
 #[async_std::main]
@@ -869,7 +831,10 @@ async fn main() -> Result<()> {
         ColorChoice::Auto,
     )?;
 
-    start().await?;
-    // demo().await?;
+    let mut demo = Demo::new();
+    demo.init();
+
+    start_rpc(demo).await?;
+
     Ok(())
 }

+ 61 - 14
bin/dao/daod/src/rpc.rs

@@ -11,10 +11,10 @@ use darkfi::rpc::{
     server::RequestHandler,
 };
 
-use crate::DaoDemo;
+use crate::Demo;
 
 pub struct JsonRpcInterface {
-    demo: Arc<Mutex<DaoDemo>>,
+    demo: Arc<Mutex<Demo>>,
 }
 
 #[async_trait]
@@ -30,7 +30,8 @@ impl RequestHandler for JsonRpcInterface {
 
         match req.method.as_str() {
             Some("create") => return self.create_dao(req.id, params).await,
-            Some("mint") => return self.mint_tokens(req.id, params).await,
+            Some("mint") => return self.mint_treasury(req.id, params).await,
+            Some("keygen") => return self.keygen(req.id, params).await,
             Some("airdrop") => return self.airdrop_tokens(req.id, params).await,
             Some("propose") => return self.create_proposal(req.id, params).await,
             Some("vote") => return self.vote(req.id, params).await,
@@ -41,53 +42,99 @@ impl RequestHandler for JsonRpcInterface {
 }
 
 impl JsonRpcInterface {
-    pub fn new(demo: DaoDemo) -> Self {
+    pub fn new(demo: Demo) -> Self {
         let demo = Arc::new(Mutex::new(demo));
         Self { demo }
     }
 
     // TODO: add 3 params: dao_proposer_limit, dao_quorum, dao_approval_ratio
-    //
     // --> {"method": "create", "params": []}
     // <-- {"result": "creating dao..."}
     async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.create().unwrap();
+        // TODO: pass in DaoParams from CLI
+        //let dao_bulla = demo.client.create_dao();
+        // TODO: return dao_bulla to command line
         JsonResponse::new(json!("dao created"), id).into()
     }
-    // --> {"method": "mint_tokens", "params": []}
-    // <-- {"result": "minting tokens..."}
-    async fn mint_tokens(&self, id: Value, _params: &[Value]) -> JsonResult {
+    // --> {"method": "mint_treasury", "params": []}
+    // <-- {"result": "minting treasury..."}
+    async fn mint_treasury(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.mint().unwrap();
+        let zk_bins = &demo.zk_bins;
+        // TODO: pass DAO params + zk_bins into mint_treasury
+        // let tx = demo.cashier.mint_treasury();
+        // demo.client.validate(tx);
+        // demo.client.wallet.balances();
         JsonResponse::new(json!("tokens minted"), id).into()
     }
+
+    // Create a new wallet for governance tokens.
+    // TODO: must pass a string identifier like alice, bob, charlie
+    async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let mut demo = self.demo.lock().await;
+        // TODO: pass string id
+        //demo.client.new_money_wallet(alice);
+        //let wallet = demo.client.money_wallets.get(alice) {
+        //      Some(wallet) => wallet.keypair.public
+        //}
+        // TODO: return 'Alice: public key' to CLI
+        JsonResponse::new(json!("created new keys"), id).into()
+    }
     // --> {"method": "airdrop_tokens", "params": []}
     // <-- {"result": "airdropping tokens..."}
+    // TODO: pass a string 'alice'
     async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.airdrop().unwrap();
+        let zk_bins = &demo.zk_bins;
+        //let keypair_public = demo.client.money_wallets.get(alice) {
+        //      Some(wallet) => wallet.keypair.public
+        //};
+        //let transaction = demo.cashier.airdrop(keypair_public, zk_bins);
+        // demo.client.validate(tx);
+        //
+        // demo.client.money_wallets.get(alice) {
+        //      Some(wallet) => wallet.balances()
+        // }
+        // TODO: return wallet balance to command line
         JsonResponse::new(json!("tokens airdropped"), id).into()
     }
     // --> {"method": "create_proposal", "params": []}
     // <-- {"result": "creating proposal..."}
+    // TODO: pass string 'alice' and dao bulla
     async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.propose().unwrap();
+        // let dao_params = self.demo.client.dao_wallet.params.get(bulla);
+        //self.demo.client.dao_wallet.propose(dao_params).unwrap();
+        // TODO: return proposal data and Proposal to CLI
         JsonResponse::new(json!("proposal created"), id).into()
     }
     // --> {"method": "vote", "params": []}
     // <-- {"result": "voting..."}
+    // TODO: pass string 'alice', dao bulla, and Proposal
+    // TODO: must pass yes or no, convert to bool
     async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.vote().unwrap();
+        // let dao_params = self.demo.client.dao_wallet.params.get(bulla);
+        // let dao_key = self.demo.client.dao_wallet.keypair.private;
+        //
+        // demo.client.money_wallets.get(alice) {
+        //      Some(wallet) => {
+        //      wallet.vote(dao_params)
+        //      let tx = wallet.vote(dao_params, vote_option, proposal)
+        //      demo.client.validate(tx);
+        //      demo.client.dao_wallet.read_vote(tx);
+        //      }
+        // }
+        //
         JsonResponse::new(json!("voted"), id).into()
     }
     // --> {"method": "execute", "params": []}
     // <-- {"result": "executing..."}
     async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult {
         let mut demo = self.demo.lock().await;
-        demo.exec().unwrap();
+        // demo.client.dao_wallet.build_exec_tx(proposal, proposal_bulla)
+        //demo.exec().unwrap();
         JsonResponse::new(json!("executed"), id).into()
     }
 }