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

drk/dao: manage dao configuration using toml format

skoupidi 1 год назад
Родитель
Сommit
764cd1a096
3 измененных файлов с 438 добавлено и 14 удалено
  1. 3 0
      bin/drk/src/cli_util.rs
  2. 402 6
      bin/drk/src/dao.rs
  3. 33 8
      bin/drk/src/main.rs

+ 3 - 0
bin/drk/src/cli_util.rs

@@ -285,6 +285,8 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         .about("Import DAO data from stdin")
         .about("Import DAO data from stdin")
         .args(&vec![name.clone()]);
         .args(&vec![name.clone()]);
 
 
+    let update_keys = SubCommand::with_name("update-keys").about("Update DAO keys from stdin");
+
     let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
     let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
 
 
     let list = SubCommand::with_name("list")
     let list = SubCommand::with_name("list")
@@ -358,6 +360,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         create,
         create,
         view,
         view,
         import,
         import,
+        update_keys,
         list,
         list,
         balance,
         balance,
         mint,
         mint,

+ 402 - 6
bin/drk/src/dao.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::{collections::HashMap, fmt};
+use std::{collections::HashMap, fmt, str::FromStr};
 
 
 use lazy_static::lazy_static;
 use lazy_static::lazy_static;
 use num_bigint::BigUint;
 use num_bigint::BigUint;
@@ -219,6 +219,373 @@ impl DaoParams {
             early_exec_secret_key,
             early_exec_secret_key,
         }
         }
     }
     }
+
+    /// Parse provided toml string into `DaoParams`.
+    /// If a specific secret key is provided, the corresponding public key
+    /// will be derived from it and ignore the provided one.
+    pub fn from_toml_str(toml: &str) -> Result<Self> {
+        // Parse TOML file contents
+        let Ok(contents) = toml::from_str::<toml::Value>(toml) else {
+            return Err(Error::ParseFailed("Failed parsing TOML config"))
+        };
+        let Some(table) = contents.as_table() else {
+            return Err(Error::ParseFailed("TOML not a map"))
+        };
+
+        // Grab configuration parameters
+        let Some(proposer_limit) = table.get("proposer_limit") else {
+            return Err(Error::ParseFailed("TOML does not contain proposer limit"))
+        };
+        let Some(proposer_limit) = proposer_limit.as_str() else {
+            return Err(Error::ParseFailed("Invalid proposer limit: Not a string"))
+        };
+        if f64::from_str(proposer_limit).is_err() {
+            return Err(Error::ParseFailed("Invalid proposer limit: Cannot be parsed to float"))
+        }
+        let proposer_limit = decode_base10(proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
+
+        let Some(quorum) = table.get("quorum") else {
+            return Err(Error::ParseFailed("TOML does not contain quorum"))
+        };
+        let Some(quorum) = quorum.as_str() else {
+            return Err(Error::ParseFailed("Invalid quorum: Not a string"))
+        };
+        if f64::from_str(quorum).is_err() {
+            return Err(Error::ParseFailed("Invalid quorum: Cannot be parsed to float"))
+        }
+        let quorum = decode_base10(quorum, BALANCE_BASE10_DECIMALS, true)?;
+
+        let Some(early_exec_quorum) = table.get("early_exec_quorum") else {
+            return Err(Error::ParseFailed("TOML does not contain early exec quorum"))
+        };
+        let Some(early_exec_quorum) = early_exec_quorum.as_str() else {
+            return Err(Error::ParseFailed("Invalid early exec quorum: Not a string"))
+        };
+        if f64::from_str(early_exec_quorum).is_err() {
+            return Err(Error::ParseFailed("Invalid early exec quorum: Cannot be parsed to float"))
+        }
+        let early_exec_quorum = decode_base10(early_exec_quorum, BALANCE_BASE10_DECIMALS, true)?;
+
+        let Some(approval_ratio) = table.get("approval_ratio") else {
+            return Err(Error::ParseFailed("TOML does not contain approval ratio"))
+        };
+        let Some(approval_ratio) = approval_ratio.as_float() else {
+            return Err(Error::ParseFailed("Invalid approval ratio: Not a float"))
+        };
+        if approval_ratio > 1.0 {
+            return Err(Error::ParseFailed("Approval ratio cannot be >1.0"))
+        }
+        let approval_ratio_base = 100_u64;
+        let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
+
+        let Some(gov_token_id) = table.get("gov_token_id") else {
+            return Err(Error::ParseFailed("TOML does not contain gov token id"))
+        };
+        let Some(gov_token_id) = gov_token_id.as_str() else {
+            return Err(Error::ParseFailed("Invalid gov token id: Not a string"))
+        };
+        let gov_token_id = TokenId::from_str(gov_token_id)?;
+
+        let Some(bulla_blind) = table.get("bulla_blind") else {
+            return Err(Error::ParseFailed("TOML does not contain bulla blind"))
+        };
+        let Some(bulla_blind) = bulla_blind.as_str() else {
+            return Err(Error::ParseFailed("Invalid bulla blind: Not a string"))
+        };
+        let bulla_blind = BaseBlind::from_str(bulla_blind)?;
+
+        // Grab DAO actions keypairs
+        let notes_secret_key = match table.get("notes_secret_key") {
+            Some(notes_secret_key) => {
+                let Some(notes_secret_key) = notes_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid notes secret key: Not a string"))
+                };
+                let Ok(notes_secret_key) = SecretKey::from_str(notes_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid notes secret key: Decoding failed"))
+                };
+                Some(notes_secret_key)
+            }
+            None => None,
+        };
+        let notes_public_key = match notes_secret_key {
+            Some(notes_secret_key) => PublicKey::from_secret(notes_secret_key),
+            None => {
+                let Some(notes_public_key) = table.get("notes_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain notes public key"))
+                };
+                let Some(notes_public_key) = notes_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid notes public key: Not a string"))
+                };
+                let Ok(notes_public_key) = PublicKey::from_str(notes_public_key) else {
+                    return Err(Error::ParseFailed("Invalid notes public key: Decoding failed"))
+                };
+                notes_public_key
+            }
+        };
+
+        let proposer_secret_key = match table.get("proposer_secret_key") {
+            Some(proposer_secret_key) => {
+                let Some(proposer_secret_key) = proposer_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid proposer secret key: Not a string"))
+                };
+                let Ok(proposer_secret_key) = SecretKey::from_str(proposer_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid proposer secret key: Decoding failed"))
+                };
+                Some(proposer_secret_key)
+            }
+            None => None,
+        };
+        let proposer_public_key = match proposer_secret_key {
+            Some(proposer_secret_key) => PublicKey::from_secret(proposer_secret_key),
+            None => {
+                let Some(proposer_public_key) = table.get("proposer_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain proposer public key"))
+                };
+                let Some(proposer_public_key) = proposer_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid proposer public key: Not a string"))
+                };
+                let Ok(proposer_public_key) = PublicKey::from_str(proposer_public_key) else {
+                    return Err(Error::ParseFailed("Invalid proposer public key: Decoding failed"))
+                };
+                proposer_public_key
+            }
+        };
+
+        let proposals_secret_key = match table.get("proposals_secret_key") {
+            Some(proposals_secret_key) => {
+                let Some(proposals_secret_key) = proposals_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid proposals secret key: Not a string"))
+                };
+                let Ok(proposals_secret_key) = SecretKey::from_str(proposals_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid proposals secret key: Decoding failed"))
+                };
+                Some(proposals_secret_key)
+            }
+            None => None,
+        };
+        let proposals_public_key = match proposals_secret_key {
+            Some(proposals_secret_key) => PublicKey::from_secret(proposals_secret_key),
+            None => {
+                let Some(proposals_public_key) = table.get("proposals_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain proposals public key"))
+                };
+                let Some(proposals_public_key) = proposals_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid proposals public key: Not a string"))
+                };
+                let Ok(proposals_public_key) = PublicKey::from_str(proposals_public_key) else {
+                    return Err(Error::ParseFailed("Invalid proposals public key: Decoding failed"))
+                };
+                proposals_public_key
+            }
+        };
+
+        let votes_secret_key = match table.get("votes_secret_key") {
+            Some(votes_secret_key) => {
+                let Some(votes_secret_key) = votes_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid votes secret key: Not a string"))
+                };
+                let Ok(votes_secret_key) = SecretKey::from_str(votes_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid votes secret key: Decoding failed"))
+                };
+                Some(votes_secret_key)
+            }
+            None => None,
+        };
+        let votes_public_key = match votes_secret_key {
+            Some(votes_secret_key) => PublicKey::from_secret(votes_secret_key),
+            None => {
+                let Some(votes_public_key) = table.get("votes_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain votes public key"))
+                };
+                let Some(votes_public_key) = votes_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid votes public key: Not a string"))
+                };
+                let Ok(votes_public_key) = PublicKey::from_str(votes_public_key) else {
+                    return Err(Error::ParseFailed("Invalid votes public key: Decoding failed"))
+                };
+                votes_public_key
+            }
+        };
+
+        let exec_secret_key = match table.get("exec_secret_key") {
+            Some(exec_secret_key) => {
+                let Some(exec_secret_key) = exec_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid exec secret key: Not a string"))
+                };
+                let Ok(exec_secret_key) = SecretKey::from_str(exec_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid exec secret key: Decoding failed"))
+                };
+                Some(exec_secret_key)
+            }
+            None => None,
+        };
+        let exec_public_key = match exec_secret_key {
+            Some(exec_secret_key) => PublicKey::from_secret(exec_secret_key),
+            None => {
+                let Some(exec_public_key) = table.get("exec_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain exec public key"))
+                };
+                let Some(exec_public_key) = exec_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid exec public key: Not a string"))
+                };
+                let Ok(exec_public_key) = PublicKey::from_str(exec_public_key) else {
+                    return Err(Error::ParseFailed("Invalid exec public key: Decoding failed"))
+                };
+                exec_public_key
+            }
+        };
+
+        let early_exec_secret_key = match table.get("early_exec_secret_key") {
+            Some(early_exec_secret_key) => {
+                let Some(early_exec_secret_key) = early_exec_secret_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid early exec secret key: Not a string"))
+                };
+                let Ok(early_exec_secret_key) = SecretKey::from_str(early_exec_secret_key) else {
+                    return Err(Error::ParseFailed("Invalid early exec secret key: Decoding failed"))
+                };
+                Some(early_exec_secret_key)
+            }
+            None => None,
+        };
+        let early_exec_public_key = match early_exec_secret_key {
+            Some(early_exec_secret_key) => PublicKey::from_secret(early_exec_secret_key),
+            None => {
+                let Some(early_exec_public_key) = table.get("early_exec_public_key") else {
+                    return Err(Error::ParseFailed("TOML does not contain early exec public key"))
+                };
+                let Some(early_exec_public_key) = early_exec_public_key.as_str() else {
+                    return Err(Error::ParseFailed("Invalid early exec public key: Not a string"))
+                };
+                let Ok(early_exec_public_key) = PublicKey::from_str(early_exec_public_key) else {
+                    return Err(Error::ParseFailed("Invalid early exec public key: Decoding failed"))
+                };
+                early_exec_public_key
+            }
+        };
+
+        Ok(Self::new(
+            proposer_limit,
+            quorum,
+            early_exec_quorum,
+            approval_ratio_base,
+            approval_ratio_quot,
+            gov_token_id,
+            notes_secret_key,
+            notes_public_key,
+            proposer_secret_key,
+            proposer_public_key,
+            proposals_secret_key,
+            proposals_public_key,
+            votes_secret_key,
+            votes_public_key,
+            exec_secret_key,
+            exec_public_key,
+            early_exec_secret_key,
+            early_exec_public_key,
+            bulla_blind,
+        ))
+    }
+
+    /// Generate a toml string containing the DAO configuration.
+    pub fn toml_str(&self) -> String {
+        // Header comments
+        let mut toml = String::from(
+            "## DAO configuration file\n\
+            ##\n\
+            ## Please make sure you go through all the settings so you can configure\n\
+            ## your DAO properly.\n\
+            ##\n\
+            ## If you want to restrict access to certain actions, the corresponding\n\
+            ## secret key can be ommited. All public keys, along with the DAO configuration\n\
+            ## parameters must be shared.\n\
+            ##\n\
+            ## If you want to combine access to certain actions, you can use the same\n\
+            ## secret and public key combination for them.\n\n",
+        );
+
+        // Configuration parameters
+        toml += &format!(
+            "## ====== DAO configuration parameters =====\n\n\
+            ## The minimum amount of governance tokens needed to open a proposal for this DAO\n\
+            proposer_limit = \"{}\"\n\n\
+            ## Minimal threshold of participating total tokens needed for a proposal to pass\n\
+            quorum = \"{}\"\n\n\
+            ## Minimal threshold of participating total tokens needed for a proposal to\n\
+            ## be considered as strongly supported, enabling early execution.\n\
+            ## Must be greater or equal to normal quorum.\n\
+            early_exec_quorum = \"{}\"\n\n\
+            ## The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)\n\
+            approval_ratio = {}\n\n\
+            ## DAO's governance token ID\n\
+            gov_token_id = \"{}\"\n\n\
+            ## Bulla blind\n\
+            bulla_blind = \"{}\"\n\n",
+            encode_base10(self.dao.proposer_limit, BALANCE_BASE10_DECIMALS),
+            encode_base10(self.dao.quorum, BALANCE_BASE10_DECIMALS),
+            encode_base10(self.dao.early_exec_quorum, BALANCE_BASE10_DECIMALS),
+            self.dao.approval_ratio_quot as f64 / self.dao.approval_ratio_base as f64,
+            self.dao.gov_token_id,
+            self.dao.bulla_blind,
+        );
+
+        // DAO actions keypairs
+        toml += &format!(
+            "## ====== DAO actions keypairs =====\n\n\
+            ## DAO notes decryption keypair\n\
+            notes_public_key = \"{}\"\n",
+            self.dao.notes_public_key,
+        );
+        match self.notes_secret_key {
+            Some(secret_key) => toml += &format!("notes_secret_key = \"{}\"\n\n", secret_key),
+            None => toml += "\n",
+        }
+        toml += &format!(
+            "## DAO proposals creator keypair\n\
+            proposer_public_key = \"{}\"\n",
+            self.dao.proposer_public_key,
+        );
+        match self.proposer_secret_key {
+            Some(secret_key) => toml += &format!("proposer_secret_key = \"{}\"\n\n", secret_key),
+            None => toml += "\n",
+        }
+        toml += &format!(
+            "## DAO proposals viewer keypair\n\
+            proposals_public_key = \"{}\"\n",
+            self.dao.proposals_public_key,
+        );
+        match self.proposals_secret_key {
+            Some(secret_key) => toml += &format!("proposals_secret_key = \"{}\"\n\n", secret_key),
+            None => toml += "\n",
+        }
+        toml += &format!(
+            "## DAO votes viewer keypair\n\
+            votes_public_key = \"{}\"\n",
+            self.dao.votes_public_key,
+        );
+        match self.votes_secret_key {
+            Some(secret_key) => toml += &format!("votes_secret_key = \"{}\"\n\n", secret_key),
+            None => toml += "\n",
+        }
+        toml += &format!(
+            "## DAO proposals executor keypair\n\
+            exec_public_key = \"{}\"\n",
+            self.dao.exec_public_key,
+        );
+        match self.exec_secret_key {
+            Some(secret_key) => toml += &format!("exec_secret_key = \"{}\"\n\n", secret_key),
+            None => toml += "\n",
+        }
+        toml += &format!(
+            "## DAO strongly supported proposals executor keypair\n\
+            early_exec_public_key = \"{}\"",
+            self.dao.early_exec_public_key,
+        );
+        if let Some(secret_key) = self.early_exec_secret_key {
+            toml += &format!("\nearly_exec_secret_key = \"{}\"", secret_key)
+        }
+
+        toml
+    }
 }
 }
 
 
 impl fmt::Display for DaoParams {
 impl fmt::Display for DaoParams {
@@ -250,7 +617,7 @@ impl fmt::Display for DaoParams {
         };
         };
 
 
         let s = format!(
         let s = format!(
-            "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}",
+            "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
             "DAO Parameters",
             "DAO Parameters",
             "==============",
             "==============",
             "Proposer limit",
             "Proposer limit",
@@ -259,6 +626,9 @@ impl fmt::Display for DaoParams {
             "Quorum",
             "Quorum",
             encode_base10(self.dao.quorum, BALANCE_BASE10_DECIMALS),
             encode_base10(self.dao.quorum, BALANCE_BASE10_DECIMALS),
             self.dao.quorum,
             self.dao.quorum,
+            "Early Exec Quorum",
+            encode_base10(self.dao.early_exec_quorum, BALANCE_BASE10_DECIMALS),
+            self.dao.early_exec_quorum,
             "Approval ratio",
             "Approval ratio",
             self.dao.approval_ratio_quot as f64 / self.dao.approval_ratio_base as f64,
             self.dao.approval_ratio_quot as f64 / self.dao.approval_ratio_base as f64,
             "Governance Token ID",
             "Governance Token ID",
@@ -369,7 +739,7 @@ impl fmt::Display for DaoRecord {
         };
         };
 
 
         let s = format!(
         let s = format!(
-            "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
+            "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
             "DAO Parameters",
             "DAO Parameters",
             "==============",
             "==============",
             "Name",
             "Name",
@@ -382,6 +752,9 @@ impl fmt::Display for DaoRecord {
             "Quorum",
             "Quorum",
             encode_base10(self.params.dao.quorum, BALANCE_BASE10_DECIMALS),
             encode_base10(self.params.dao.quorum, BALANCE_BASE10_DECIMALS),
             self.params.dao.quorum,
             self.params.dao.quorum,
+            "Early Exec Quorum",
+            encode_base10(self.params.dao.early_exec_quorum, BALANCE_BASE10_DECIMALS),
+            self.params.dao.early_exec_quorum,
             "Approval ratio",
             "Approval ratio",
             self.params.dao.approval_ratio_quot as f64 / self.params.dao.approval_ratio_base as f64,
             self.params.dao.approval_ratio_quot as f64 / self.params.dao.approval_ratio_base as f64,
             "Governance Token ID",
             "Governance Token ID",
@@ -1391,10 +1764,9 @@ impl Drk {
     }
     }
 
 
     /// Import given DAO params into the wallet with a given name.
     /// Import given DAO params into the wallet with a given name.
-    pub async fn import_dao(&self, name: &str, params: DaoParams) -> Result<()> {
+    pub async fn import_dao(&self, name: &str, params: &DaoParams) -> Result<()> {
         // First let's check if we've imported this DAO with the given name before.
         // First let's check if we've imported this DAO with the given name before.
         if self.get_dao_by_name(name).await.is_ok() {
         if self.get_dao_by_name(name).await.is_ok() {
-            // TODO: update `DaoParams` if the dao already exists
             return Err(Error::DatabaseError(
             return Err(Error::DatabaseError(
                 "[import_dao] This DAO has already been imported".to_string(),
                 "[import_dao] This DAO has already been imported".to_string(),
             ))
             ))
@@ -1411,7 +1783,7 @@ impl Drk {
             rusqlite::params![
             rusqlite::params![
                 serialize_async(&params.dao.to_bulla()).await,
                 serialize_async(&params.dao.to_bulla()).await,
                 name,
                 name,
-                serialize_async(&params).await,
+                serialize_async(params).await,
             ],
             ],
         ) {
         ) {
             return Err(Error::DatabaseError(format!("[import_dao] DAO insert failed: {e:?}")))
             return Err(Error::DatabaseError(format!("[import_dao] DAO insert failed: {e:?}")))
@@ -1420,6 +1792,30 @@ impl Drk {
         Ok(())
         Ok(())
     }
     }
 
 
+    /// Update given DAO params into the wallet, if the corresponding DAO exists.
+    pub async fn update_dao_keys(&self, params: &DaoParams) -> Result<()> {
+        // Grab the params DAO
+        let bulla = params.dao.to_bulla();
+        let Ok(dao) = self.get_dao_by_bulla(&bulla).await else {
+            return Err(Error::DatabaseError(format!("[import_dao] DAO {bulla} was not found")))
+        };
+
+        println!("Updating \"{}\" DAO keys into the wallet", dao.name);
+
+        let query = format!(
+            "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
+            *DAO_DAOS_TABLE, DAO_DAOS_COL_PARAMS, DAO_DAOS_COL_BULLA,
+        );
+        if let Err(e) = self.wallet.exec_sql(
+            &query,
+            rusqlite::params![serialize_async(params).await, serialize_async(&bulla).await,],
+        ) {
+            return Err(Error::DatabaseError(format!("[update_dao_keys] DAO update failed: {e:?}")))
+        };
+
+        Ok(())
+    }
+
     /// Fetch a DAO given its bulla.
     /// Fetch a DAO given its bulla.
     pub async fn get_dao_by_bulla(&self, bulla: &DaoBulla) -> Result<DaoRecord> {
     pub async fn get_dao_by_bulla(&self, bulla: &DaoBulla) -> Result<DaoRecord> {
         let row = match self.wallet.query_single(
         let row = match self.wallet.query_single(

+ 33 - 8
bin/drk/src/main.rs

@@ -300,6 +300,9 @@ enum DaoSubcmd {
         name: String,
         name: String,
     },
     },
 
 
+    /// Update DAO keys from stdin
+    UpdateKeys,
+
     /// List imported DAOs (or info about a specific one)
     /// List imported DAOs (or info about a specific one)
     List {
     List {
         /// Name identifier for the DAO (optional)
         /// Name identifier for the DAO (optional)
@@ -1168,6 +1171,10 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     eprintln!("Invalid quorum: {e:?}");
                     eprintln!("Invalid quorum: {e:?}");
                     exit(2);
                     exit(2);
                 }
                 }
+                if let Err(e) = f64::from_str(&early_exec_quorum) {
+                    eprintln!("Invalid early exec quorum: {e:?}");
+                    exit(2);
+                }
 
 
                 let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
                 let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
                 let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
                 let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
@@ -1228,8 +1235,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     bulla_blind,
                     bulla_blind,
                 );
                 );
 
 
-                let encoded = bs58::encode(&serialize_async(&params).await).into_string();
-                println!("{encoded}");
+                println!("{}", params.toml_str());
 
 
                 Ok(())
                 Ok(())
             }
             }
@@ -1237,9 +1243,8 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             DaoSubcmd::View => {
             DaoSubcmd::View => {
                 let mut buf = String::new();
                 let mut buf = String::new();
                 stdin().read_to_string(&mut buf)?;
                 stdin().read_to_string(&mut buf)?;
-                let bytes = bs58::decode(&buf.trim()).into_vec()?;
-                let dao_params: DaoParams = deserialize_async(&bytes).await?;
-                println!("{dao_params}");
+                let params = DaoParams::from_toml_str(&buf)?;
+                println!("{params}");
 
 
                 Ok(())
                 Ok(())
             }
             }
@@ -1247,8 +1252,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             DaoSubcmd::Import { name } => {
             DaoSubcmd::Import { name } => {
                 let mut buf = String::new();
                 let mut buf = String::new();
                 stdin().read_to_string(&mut buf)?;
                 stdin().read_to_string(&mut buf)?;
-                let bytes = bs58::decode(&buf.trim()).into_vec()?;
-                let params: DaoParams = deserialize_async(&bytes).await?;
+                let params = DaoParams::from_toml_str(&buf)?;
 
 
                 let drk = new_wallet(
                 let drk = new_wallet(
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_path,
@@ -1258,7 +1262,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     args.fun,
                     args.fun,
                 )
                 )
                 .await;
                 .await;
-                if let Err(e) = drk.import_dao(&name, params).await {
+                if let Err(e) = drk.import_dao(&name, &params).await {
                     eprintln!("Failed to import DAO: {e:?}");
                     eprintln!("Failed to import DAO: {e:?}");
                     exit(2);
                     exit(2);
                 }
                 }
@@ -1266,6 +1270,27 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 Ok(())
                 Ok(())
             }
             }
 
 
+            DaoSubcmd::UpdateKeys => {
+                let mut buf = String::new();
+                stdin().read_to_string(&mut buf)?;
+                let params = DaoParams::from_toml_str(&buf)?;
+
+                let drk = new_wallet(
+                    blockchain_config.wallet_path,
+                    blockchain_config.wallet_pass,
+                    None,
+                    ex,
+                    args.fun,
+                )
+                .await;
+                if let Err(e) = drk.update_dao_keys(&params).await {
+                    eprintln!("Failed to update DAO keys: {e:?}");
+                    exit(2);
+                }
+
+                Ok(())
+            }
+
             DaoSubcmd::List { name } => {
             DaoSubcmd::List { name } => {
                 let drk = new_wallet(
                 let drk = new_wallet(
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_path,