Răsfoiți Sursa

dao_demo: create a DAO proposal from command-line.

lunar-mining 3 ani în urmă
părinte
comite
3fa92c5443

+ 32 - 9
bin/dao/dao-cli/src/main.rs

@@ -29,10 +29,12 @@ pub enum CliDaoSubCommands {
 
         /// Public key of the DAO treasury.
         dao_addr: String,
-
-        /// DAO public identifier.
-        dao_bulla: String,
     },
+    UserBalance {
+        nym: String,
+    },
+    DaoBalance {},
+    DaoBulla {},
     Keygen {
         nym: String,
     },
@@ -43,7 +45,13 @@ pub enum CliDaoSubCommands {
         value: u64,
     },
     /// Propose
-    Propose {},
+    Propose {
+        sender: String,
+
+        recipient: String,
+
+        amount: u64,
+    },
     /// Vote
     Vote {},
     /// Execute
@@ -92,8 +100,8 @@ async fn start(options: CliDao) -> Result<()> {
             println!("DAO public address: {}", &reply.to_string());
             return Ok(())
         }
-        Some(CliDaoSubCommands::Mint { token_supply, dao_addr, dao_bulla }) => {
-            let reply = client.mint(token_supply, dao_addr, dao_bulla).await?;
+        Some(CliDaoSubCommands::Mint { token_supply, dao_addr }) => {
+            let reply = client.mint(token_supply, dao_addr).await?;
             println!("New DAO balance: {}", &reply.to_string());
             return Ok(())
         }
@@ -107,9 +115,24 @@ async fn start(options: CliDao) -> Result<()> {
             println!("New user balance: {}", &reply.to_string());
             return Ok(())
         }
-        Some(CliDaoSubCommands::Propose {}) => {
-            let reply = client.propose().await?;
-            println!("Server replied: {}", &reply.to_string());
+        Some(CliDaoSubCommands::DaoBalance {}) => {
+            let reply = client.dao_balance().await?;
+            println!("DAO balance: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::DaoBulla {}) => {
+            let reply = client.dao_bulla().await?;
+            println!("DAO bulla: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::UserBalance { nym }) => {
+            let reply = client.user_balance(nym).await?;
+            println!("User balance: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Propose { sender, recipient, amount }) => {
+            let reply = client.propose(sender, recipient, amount).await?;
+            println!("Proposal bulla: {}", &reply.to_string());
             return Ok(())
         }
         Some(CliDaoSubCommands::Vote {}) => {

+ 25 - 9
bin/dao/dao-cli/src/rpc.rs

@@ -35,13 +35,8 @@ impl Rpc {
 
     // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42}
     // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42}
-    pub async fn mint(
-        &self,
-        token_supply: u64,
-        dao_addr: String,
-        dao_bulla: String,
-    ) -> Result<Value> {
-        let req = JsonRequest::new("mint", json!([token_supply, dao_addr, dao_bulla]));
+    pub async fn mint(&self, token_supply: u64, dao_addr: String) -> Result<Value> {
+        let req = JsonRequest::new("mint", json!([token_supply, dao_addr]));
         self.client.request(req).await
     }
 
@@ -59,10 +54,31 @@ impl Rpc {
         self.client.request(req).await
     }
 
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn dao_balance(&self) -> Result<Value> {
+        let req = JsonRequest::new("dao_balance", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn dao_bulla(&self) -> Result<Value> {
+        let req = JsonRequest::new("dao_bulla", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn user_balance(&self, nym: String) -> Result<Value> {
+        let req = JsonRequest::new("user_balance", json!([nym]));
+        self.client.request(req).await
+    }
+
     // --> {"jsonrpc": "2.0", "method": "propose", "params": [], "id": 42}
     // <-- {"jsonrpc": "2.0", "result": "creating proposal...", "id": 42}
-    pub async fn propose(&self) -> Result<Value> {
-        let req = JsonRequest::new("propose", json!([]));
+    pub async fn propose(&self, sender: String, recipient: String, amount: u64) -> Result<Value> {
+        let req = JsonRequest::new("propose", json!([sender, recipient, amount]));
         self.client.request(req).await
     }
 

+ 203 - 147
bin/dao/daod/src/main.rs

@@ -125,8 +125,10 @@ impl Client {
     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 };
+        let own_coins: Vec<(OwnCoin, bool)> = Vec::new();
+        let money_wallet = MoneyWallet { keypair, signature_secret, own_coins };
+        money_wallet.track(&mut self.states);
+
         self.money_wallets.insert(key.clone(), money_wallet);
         debug!(target: "dao-demo::client::new_money_wallet()", "created wallet with key {}", &key);
     }
@@ -141,6 +143,7 @@ impl Client {
         dao_approval_ratio_base: u64,
         token_id: pallas::Base,
     ) -> Result<pallas::Base> {
+        debug!(target: "dao-demo::client::create_dao()", "START");
         let tx = self.dao_wallet.mint_tx(
             dao_proposer_limit,
             dao_quorum,
@@ -152,10 +155,8 @@ impl Client {
 
         // TODO: Proper error handling.
         // Only witness the value once the transaction is confirmed.
-        match self.validate(&tx) {
-            Ok(v) => self.dao_wallet.update_witness(&mut self.states)?,
-            Err(e) => {}
-        }
+        self.validate(&tx).unwrap();
+        self.dao_wallet.update_witness(&mut self.states).unwrap();
 
         // Retrieve DAO bulla from the state.
         let dao_bulla = {
@@ -180,54 +181,45 @@ impl Client {
             bulla_blind: self.dao_wallet.bulla_blind,
         };
 
-        self.dao_wallet.params.insert(HashableBase(dao_bulla.0), dao_params);
+        self.dao_wallet.params.push(dao_params);
+        self.dao_wallet.bullas.push(dao_bulla.clone());
 
         Ok(dao_bulla.0)
     }
 
-    pub fn mint_treasury(
+    fn mint_treasury(
         &mut self,
         token_id: pallas::Base,
         token_supply: u64,
-        dao_bulla: pallas::Base,
         recipient: PublicKey,
-    ) -> Result<u64> {
+    ) -> Result<()> {
         self.dao_wallet.track(&mut self.states);
 
-        let tx =
-            self.cashier.mint(*XDRK_ID, token_supply, dao_bulla, recipient, &self.zk_bins).unwrap();
+        let tx = self
+            .cashier
+            .mint(*XDRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins)
+            .unwrap();
 
-        self.validate(&tx)?;
-
-        let own_coin = self.dao_wallet.balances(&mut self.states)?;
-
-        let balance = own_coin.note.value;
+        self.validate(&tx).unwrap();
+        self.update_wallets().unwrap();
 
-        Ok(balance)
+        Ok(())
     }
 
     fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, nym: String) -> Result<()> {
         let wallet = self.money_wallets.get(&nym).unwrap();
-        wallet.track(&mut self.states);
-
         let addr = wallet.get_public_key();
 
         let tx = self.cashier.airdrop(value, token_id, addr, &self.zk_bins)?;
         self.validate(&tx)?;
+        self.update_wallets().unwrap();
 
         Ok(())
     }
 
-    fn query_balance(&mut self, nym: String) -> Result<u64> {
-        let wallet = self.money_wallets.get(&nym).unwrap();
-        let own_coin = wallet.balances(&mut self.states)?;
-        let balance = own_coin.note.value;
-
-        Ok(balance)
-    }
-
     // TODO: Change these into errors instead of expects.
     fn validate(&mut self, tx: &Transaction) -> Result<()> {
+        debug!(target: "dao_demo::client::validate()", "commencing validate sequence");
         let mut updates = vec![];
 
         // Validate all function calls in the tx
@@ -276,21 +268,80 @@ impl Client {
         Ok(())
     }
 
+    fn update_wallets(&mut self) -> Result<()> {
+        let state =
+            self.states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+
+        let mut dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret);
+        for coin in dao_coins {
+            let note = coin.note.clone();
+            let coords = self.dao_wallet.keypair.public.0.to_affine().coordinates().unwrap();
+
+            let coin_hash = poseidon_hash::<8>([
+                *coords.x(),
+                *coords.y(),
+                DrkValue::from(note.value),
+                note.token_id,
+                note.serial,
+                note.spend_hook,
+                note.user_data,
+                note.coin_blind,
+            ]);
+
+            assert_eq!(coin_hash, coin.coin.0);
+            assert_eq!(note.spend_hook, *dao_contract::exec::FUNC_ID);
+            assert_eq!(note.user_data, self.dao_wallet.bullas[0].0);
+
+            self.dao_wallet.own_coins.push((coin, false));
+            debug!("DAO received a coin worth {} xDRK", note.value);
+        }
+
+        for (key, wallet) in &mut self.money_wallets {
+            let mut coins = state.wallet_cache.get_received(&wallet.keypair.secret);
+            for coin in coins {
+                let note = coin.note.clone();
+                let coords = wallet.keypair.public.0.to_affine().coordinates().unwrap();
+
+                let coin_hash = poseidon_hash::<8>([
+                    *coords.x(),
+                    *coords.y(),
+                    DrkValue::from(note.value),
+                    note.token_id,
+                    note.serial,
+                    note.spend_hook,
+                    note.user_data,
+                    note.coin_blind,
+                ]);
+
+                assert_eq!(coin_hash, coin.coin.0);
+                wallet.own_coins.push((coin, false));
+            }
+        }
+
+        Ok(())
+    }
+
     // TODO: error handling
     fn propose(
         &mut self,
-        params: DaoParams,
         recipient: PublicKey,
         token_id: pallas::Base,
         amount: u64,
-        key: String,
-    ) -> Result<()> {
+        sender: String,
+    ) -> Result<(Proposal, pallas::Base)> {
+        let params = self.dao_wallet.params[0].clone();
+
         let dao_leaf_position = self.dao_wallet.leaf_position;
 
-        let mut money_wallet = self.money_wallets.get_mut(&key).unwrap();
+        // 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 mut sender_wallet = self.money_wallets.get_mut(&sender).unwrap();
+        //let own_coin = sender_wallet.balances()?;
+        //let (money_leaf_position, money_merkle_path) =
+        //    sender_wallet.get_path(&self.states, &own_coin)?;
 
-        let tx = money_wallet.propose_tx(
-            params,
+        let tx = sender_wallet.propose_tx(
+            params.clone(),
             recipient,
             token_id,
             amount,
@@ -299,11 +350,18 @@ impl Client {
             &mut self.states,
         )?;
 
+        // bang!
         self.validate(&tx)?;
+        self.update_wallets().unwrap();
 
-        self.dao_wallet.read_proposal(&tx)?;
+        let (proposal, proposal_bulla) = self.dao_wallet.read_proposal(&tx)?;
 
-        Ok(())
+        Ok((proposal, proposal_bulla))
+    }
+
+    fn get_addr_from_nym(&self, nym: String) -> Result<PublicKey> {
+        let wallet = self.money_wallets.get(&nym).unwrap();
+        Ok(wallet.get_public_key())
     }
 }
 
@@ -312,7 +370,9 @@ struct DaoWallet {
     signature_secret: SecretKey,
     bulla_blind: pallas::Base,
     leaf_position: Position,
-    params: HashMap<HashableBase, DaoParams>,
+    bullas: Vec<DaoBulla>,
+    params: Vec<DaoParams>,
+    own_coins: Vec<(OwnCoin, bool)>,
     vote_notes: Vec<dao_contract::vote::wallet::Note>,
 }
 impl DaoWallet {
@@ -321,10 +381,21 @@ impl DaoWallet {
         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 bullas = Vec::new();
+        let params = Vec::new();
+        let own_coins: Vec<(OwnCoin, bool)> = Vec::new();
         let vote_notes = Vec::new();
 
-        Self { keypair, signature_secret, bulla_blind, leaf_position, params, vote_notes }
+        Self {
+            keypair,
+            signature_secret,
+            bulla_blind,
+            leaf_position,
+            bullas,
+            params,
+            own_coins,
+            vote_notes,
+        }
     }
 
     fn get_public_key(&self) -> PublicKey {
@@ -348,6 +419,7 @@ impl DaoWallet {
         token_id: pallas::Base,
         zk_bins: &ZkContractTable,
     ) -> Transaction {
+        debug!(target: "dao-demo::dao::mint_tx()", "START");
         let builder = dao_contract::mint::wallet::Builder {
             dao_proposer_limit,
             dao_quorum,
@@ -378,43 +450,18 @@ impl DaoWallet {
         Ok(())
     }
 
-    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)
+    fn balances(&self) -> Result<u64> {
+        let mut balances = 0;
+        for (coin, is_spent) in &self.own_coins {
+            if *is_spent {
+                continue
+            }
+            balances += coin.note.value;
         }
-        // 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)
+        Ok(balances)
     }
 
-    fn read_proposal(&self, tx: &Transaction) -> Result<()> {
+    fn read_proposal(&self, tx: &Transaction) -> Result<(Proposal, pallas::Base)> {
         let (proposal, proposal_bulla) = {
             let func_call = &tx.func_calls[0];
             let call_data = func_call.call_data.as_any();
@@ -428,17 +475,15 @@ impl DaoWallet {
             // Return the proposal info
             (note.proposal, call_data.header.proposal_bulla)
         };
-        // TODO: this should print from the CLI rather than use debug statements.
         debug!(target: "demo", "Proposal now active!");
         debug!(target: "demo", "  destination: {:?}", proposal.dest);
         debug!(target: "demo", "  amount: {}", proposal.amount);
         debug!(target: "demo", "  token_id: {:?}", proposal.token_id);
         debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla);
 
-        Ok(())
-
-        // TODO: encode Proposal as base58 and return to cli
+        Ok((proposal, proposal_bulla))
     }
+
     // We decrypt the votes in a transaction and add it to the wallet.
     fn read_vote(&mut self, tx: &Transaction) -> Result<()> {
         let vote_note = {
@@ -491,18 +536,12 @@ impl DaoWallet {
         zk_bins: &ZkContractTable,
         states: &mut StateRegistry,
     ) -> Result<Transaction> {
+        let mut inputs = Vec::new();
+        let mut total_input_value = 0;
         // 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.get_treasury_path(&own_coin, states)?;
-
-        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);
@@ -512,23 +551,37 @@ impl DaoWallet {
         let dao_serial = pallas::Base::random(&mut OsRng);
         let dao_coin_blind = pallas::Base::random(&mut OsRng);
 
-        let input = {
-            money_contract::transfer::wallet::BuilderInputInfo {
-                leaf_position: treasury_leaf_position,
-                merkle_path: treasury_merkle_path,
-                secret: self.keypair.secret,
-                note: own_coin.note.clone(),
-                user_data_blind,
-                value_blind: input_value_blind,
-                // TODO: in schema, we create random signatures here. why?
-                signature_secret: tx_signature_secret,
+        // We must prove we have sufficient governance tokens to execute this.
+        for (coin, is_spent) in &self.own_coins {
+            let is_spent = is_spent.clone();
+            if is_spent {
+                continue
             }
-        };
+            let (treasury_leaf_position, treasury_merkle_path) =
+                self.get_treasury_path(&coin, states)?;
+
+            let input_value = coin.note.value;
+
+            let input = {
+                money_contract::transfer::wallet::BuilderInputInfo {
+                    leaf_position: treasury_leaf_position,
+                    merkle_path: treasury_merkle_path,
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    user_data_blind,
+                    value_blind: input_value_blind,
+                    // TODO: in schema, we create random signatures here. why?
+                    signature_secret: tx_signature_secret,
+                }
+            };
+            total_input_value += input_value;
+            inputs.push(input);
+        }
 
         let builder = {
             money_contract::transfer::wallet::Builder {
                 clear_inputs: vec![],
-                inputs: vec![input],
+                inputs,
                 outputs: vec![
                     // Sending money
                     money_contract::transfer::wallet::BuilderOutputInfo {
@@ -542,8 +595,11 @@ impl DaoWallet {
                     },
                     // Change back to DAO
                     money_contract::transfer::wallet::BuilderOutputInfo {
-                        value: own_coin.note.value - proposal.amount,
-                        token_id: own_coin.note.token_id,
+                        value: total_input_value - proposal.amount,
+                        // TODO: this should be the token id of the treasury,
+                        // rather than the token id of the proposal.
+                        // total_id: own_coin.token_id,
+                        token_id: proposal.token_id,
                         public: self.keypair.public,
                         // ?
                         serial: dao_serial,
@@ -601,11 +657,11 @@ impl DaoWallet {
 }
 
 // Stores governance tokens and related secret values.
-#[derive(Clone)]
 struct MoneyWallet {
     keypair: Keypair,
     signature_secret: SecretKey,
-    leaf_position: Position,
+    own_coins: Vec<(OwnCoin, bool)>,
+    //leaf_position: Position,
 }
 
 impl MoneyWallet {
@@ -624,20 +680,13 @@ impl MoneyWallet {
         Ok(())
     }
 
-    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 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)
+    fn balances(&self) -> Result<u64> {
+        let mut balances = 0;
+        for (coin, is_spent) in &self.own_coins {
+            if *is_spent {}
+            balances += coin.note.value;
+        }
+        Ok(balances)
     }
 
     fn propose_tx(
@@ -650,23 +699,26 @@ impl MoneyWallet {
         zk_bins: &ZkContractTable,
         states: &mut StateRegistry,
     ) -> 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.get_path(&states, &own_coin)?;
-
-        let signature_secret = SecretKey::random(&mut OsRng);
+        let mut inputs = Vec::new();
 
-        let input = {
-            dao_contract::propose::wallet::BuilderInput {
-                secret: self.keypair.secret,
-                note: own_coin.note.clone(),
-                leaf_position: money_leaf_position,
-                merkle_path: money_merkle_path,
-                signature_secret,
+        for (coin, is_spent) in &self.own_coins {
+            let is_spent = is_spent.clone();
+            if is_spent {
+                continue
             }
-        };
+            let (money_leaf_position, money_merkle_path) = self.get_path(&states, &coin)?;
+
+            let input = {
+                dao_contract::propose::wallet::BuilderInput {
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    leaf_position: money_leaf_position,
+                    merkle_path: money_merkle_path,
+                    signature_secret: self.signature_secret,
+                }
+            };
+            inputs.push(input);
+        }
 
         let (dao_merkle_path, dao_merkle_root) = {
             let state = states.lookup::<dao_contract::State>(*dao_contract::CONTRACT_ID).unwrap();
@@ -687,7 +739,7 @@ impl MoneyWallet {
         };
 
         let builder = dao_contract::propose::wallet::Builder {
-            inputs: vec![input],
+            inputs,
             proposal,
             dao: params.clone(),
             dao_leaf_position,
@@ -698,7 +750,8 @@ impl MoneyWallet {
         let func_call = builder.build(zk_bins);
         let func_calls = vec![func_call];
 
-        let signatures = sign(vec![signature_secret], &func_calls);
+        debug!(target: "dao-demo::money_wallet::propose", "signature_public {:?}", self.signature_public());
+        let signatures = sign(vec![self.signature_secret], &func_calls);
         Ok(Transaction { func_calls, signatures })
     }
 
@@ -733,24 +786,27 @@ impl MoneyWallet {
         zk_bins: &ZkContractTable,
         states: &mut StateRegistry,
     ) -> 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.get_path(states, &own_coin)?;
-
-        let input = {
-            dao_contract::vote::wallet::BuilderInput {
-                secret: self.keypair.secret,
-                note: own_coin.note.clone(),
-                leaf_position: money_leaf_position,
-                merkle_path: money_merkle_path,
-                signature_secret: self.signature_secret,
-            }
-        };
+        let mut inputs = Vec::new();
+
+        // We must prove we have sufficient governance tokens in order to vote.
+        for (coin, is_spent) in &self.own_coins {
+            let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin)?;
+
+            let input = {
+                dao_contract::vote::wallet::BuilderInput {
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    leaf_position: money_leaf_position,
+                    merkle_path: money_merkle_path,
+                    signature_secret: self.signature_secret,
+                }
+            };
+            inputs.push(input);
+        }
 
         let builder = {
             dao_contract::vote::wallet::Builder {
-                inputs: vec![input],
+                inputs,
                 vote: dao_contract::vote::wallet::Vote {
                     vote_option,
                     vote_option_blind: pallas::Scalar::random(&mut OsRng),

+ 59 - 24
bin/dao/daod/src/rpc.rs

@@ -39,6 +39,9 @@ impl RequestHandler for JsonRpcInterface {
         match req.method.as_str() {
             Some("create") => return self.create_dao(req.id, params).await,
             Some("get_dao_addr") => return self.get_dao_addr(req.id, params).await,
+            Some("dao_balance") => return self.dao_balance(req.id, params).await,
+            Some("dao_bulla") => return self.dao_bulla(req.id, params).await,
+            Some("user_balance") => return self.user_balance(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,
@@ -59,7 +62,6 @@ impl JsonRpcInterface {
     // --> {"method": "create", "params": []}
     // <-- {"result": "creating dao..."}
     async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult {
-        // TODO: error handling
         let dao_proposer_limit = params[0].as_u64().unwrap();
         let dao_quorum = params[1].as_u64().unwrap();
         let dao_approval_ratio_quot = params[2].as_u64().unwrap();
@@ -87,35 +89,49 @@ impl JsonRpcInterface {
         let mut client = self.client.lock().await;
         let pubkey = client.dao_wallet.get_public_key();
         let addr: String = bs58::encode(pubkey.to_bytes()).into_string();
-
         JsonResponse::new(json!(addr), id).into()
     }
 
+    async fn dao_balance(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        let balance = client.dao_wallet.balances().unwrap();
+        JsonResponse::new(json!(balance), id).into()
+    }
+
+    async fn dao_bulla(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        let dao_bullas = client.dao_wallet.bullas.clone();
+        let mut bulla_vec = Vec::new();
+
+        for bulla in dao_bullas {
+            let dao_bulla: String = bs58::encode(bulla.0.to_repr()).into_string();
+            bulla_vec.push(dao_bulla);
+        }
+
+        JsonResponse::new(json!(bulla_vec), id).into()
+    }
+
+    async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        let nym = params[0].as_str().unwrap().to_string();
+
+        let wallet = client.money_wallets.get(&nym).unwrap();
+        let balance = wallet.balances().unwrap();
+        JsonResponse::new(json!(balance), id).into()
+    }
     // --> {"method": "mint_treasury", "params": []}
     // <-- {"result": "minting treasury..."}
     async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult {
-        // TODO: error handling
         let mut client = self.client.lock().await;
 
         let token_supply = params[0].as_u64().unwrap();
         let addr = params[1].as_str().unwrap();
-        let bulla = params[2].as_str().unwrap();
-
-        let dao_bulla = parse_b58(bulla).unwrap();
         let dao_addr = PublicKey::from_str(addr).unwrap();
 
-        //match PublicKey::from_str(addr) {
-        //    Ok(addr) => {
-        //        debug!(target: "daod::rpc", "Decoded correctly: {:?}", addr)
-        //    }
-        //    Err(e) => {
-        //        debug!(target: "daod::rpc", "Decoded incorrectly: {}", e)
-        //    }
-        //}
-
-        let balance = client.mint_treasury(*XDRK_ID, token_supply, dao_bulla, dao_addr).unwrap();
+        client.mint_treasury(*XDRK_ID, token_supply, dao_addr).unwrap();
+        //let balance = client.query_dao_balance().unwrap();
 
-        JsonResponse::new(json!(balance), id).into()
+        JsonResponse::new(json!("minted treasury"), id).into()
     }
 
     // Create a new wallet for governance tokens.
@@ -132,6 +148,7 @@ impl JsonRpcInterface {
         let addr: String = bs58::encode(pubkey.to_bytes()).into_string();
         JsonResponse::new(json!(addr), id).into()
     }
+
     // --> {"method": "airdrop_tokens", "params": []}
     // <-- {"result": "airdropping tokens..."}
     async fn airdrop_tokens(&self, id: Value, params: &[Value]) -> JsonResult {
@@ -142,19 +159,37 @@ impl JsonRpcInterface {
         let value = params[1].as_u64().unwrap();
 
         client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap();
-        let balance = client.query_balance(nym.clone()).unwrap();
+        //let balance = client.query_balance(nym.clone()).unwrap();
 
-        JsonResponse::new(json!(balance), id).into()
+        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 {
+    async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult {
         let mut client = self.client.lock().await;
-        // let dao_params = self.client.client.dao_wallet.params.get(bulla);
-        //self.client.client.dao_wallet.propose(dao_params).unwrap();
-        // TODO: return proposal data and Proposal to CLI
-        JsonResponse::new(json!("proposal created"), id).into()
+
+        let sender = params[0].as_str().unwrap().to_string();
+        let recipient = params[1].as_str().unwrap();
+        let amount = params[2].as_u64().unwrap();
+
+        let recv_addr = PublicKey::from_str(recipient).unwrap();
+
+        let (proposal, proposal_bulla) =
+            client.propose(recv_addr, *XDRK_ID, amount, sender).unwrap();
+        let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string();
+        let token_id: String = bs58::encode(proposal.token_id.to_repr()).into_string();
+        let addr: String = bs58::encode(proposal.dest.to_bytes()).into_string();
+
+        let mut proposal_vec = Vec::new();
+
+        proposal_vec.push("Proposal now active!".to_string());
+        proposal_vec.push(format!("destination: {:?}", addr.to_string()));
+        proposal_vec.push(format!("amount: {:?}", proposal.amount.to_string()));
+        proposal_vec.push(format!("token_id: {:?}", token_id));
+        proposal_vec.push(format!("bulla: {:?}", bulla));
+
+        JsonResponse::new(json!(proposal_vec), id).into()
     }
     // --> {"method": "vote", "params": []}
     // <-- {"result": "voting..."}

+ 2 - 1
bin/dao/daod/src/util.rs

@@ -44,7 +44,7 @@ lazy_static! {
     pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng);
 }
 
-#[derive(Eq, PartialEq)]
+#[derive(Eq, PartialEq, Debug)]
 pub struct HashableBase(pub pallas::Base);
 
 impl std::hash::Hash for HashableBase {
@@ -164,6 +164,7 @@ impl Transaction {
             func_call.encode(&mut unsigned_tx_data).expect("failed to encode data");
             let signature_pub_keys = func_call.call_data.signature_public_keys();
             for signature_pub_key in signature_pub_keys {
+                debug!(target: "dao-demo::util::verify_sigs()", "{:?}", signature_pub_key);
                 let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature);
                 assert!(verify_result, "verify sigs[{}] failed", i);
             }