drk2: Dao functionality added

This commit is contained in:
skoupidi
2024-01-25 18:44:38 +02:00
parent c04667a845
commit 2f6bb5748f
3 changed files with 1386 additions and 163 deletions

View File

@@ -120,16 +120,16 @@ pub fn generate_completions(shell: &str) -> Result<()> {
let addresses =
Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
let default_address = Arg::with_name("default_address")
.long("default_address")
let default_address = Arg::with_name("default-address")
.long("default-address")
.takes_value(true)
.help("Set the default address in the wallet");
let secrets =
Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
let import_secrets = Arg::with_name("import_secrets")
.long("import_secrets")
let import_secrets = Arg::with_name("import-secrets")
.long("import-secrets")
.help("Import secret keys from stdin into the wallet, separated by newlines");
let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
@@ -166,15 +166,15 @@ pub fn generate_completions(shell: &str) -> Result<()> {
.args(&vec![amount, token, recipient]);
// Otc
let value_pair = Arg::with_name("value_pair")
let value_pair = Arg::with_name("value-pair")
.short("v")
.long("value_pair")
.long("value-pair")
.takes_value(true)
.help("Value pair to send:recv (11.55:99.42)");
let token_pair = Arg::with_name("token_pair")
let token_pair = Arg::with_name("token-pair")
.short("t")
.long("token_pair")
.long("token-pair")
.takes_value(true)
.help("Token pair to send:recv (f00:b4r)");
@@ -210,7 +210,88 @@ pub fn generate_completions(shell: &str) -> Result<()> {
find coins sent to us and fill our wallet with the necessary metadata.",
);
// TODO: DAO
// DAO
let proposer_limit = Arg::with_name("proposer-limit")
.help("The minimum amount of governance tokens needed to open a proposal for this DAO");
let quorum = Arg::with_name("quorum")
.help("Minimal threshold of participating total tokens needed for a proposal to pass");
let approval_ratio = Arg::with_name("approval-ratio")
.help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
proposer_limit,
quorum,
approval_ratio,
gov_token_id,
]);
let view = SubCommand::with_name("view").about("View DAO data from stdin");
let dao_name = Arg::with_name("dao-name").help("Named identifier for the DAO");
let import =
SubCommand::with_name("import").about("Import DAO data from stdin").args(&vec![dao_name]);
let dao_alias = Arg::with_name("dao-alias").help("Numeric identifier for the DAO (optional)");
let list = SubCommand::with_name("list")
.about("List imported DAOs (or info about a specific one)")
.args(&vec![dao_alias]);
let dao_alias = Arg::with_name("dao-alias").help("Name or numeric identifier for the DAO");
let balance = SubCommand::with_name("balance")
.about("Show the balance of a DAO")
.args(&vec![dao_alias.clone()]);
let mint = SubCommand::with_name("mint")
.about("Mint an imported DAO on-chain")
.args(&vec![dao_alias.clone()]);
let recipient =
Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
let propose = SubCommand::with_name("propose")
.about("Create a proposal for a DAO")
.args(&vec![dao_alias.clone(), recipient, amount, token]);
let proposals = SubCommand::with_name("proposals")
.about("List DAO proposals")
.args(&vec![dao_alias.clone()]);
let proposal_id = Arg::with_name("proposal-id").help("Numeric identifier for the proposal");
let proposal = SubCommand::with_name("proposal")
.about("View a DAO proposal data")
.args(&vec![dao_alias.clone(), proposal_id.clone()]);
let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
let vote_weight =
Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
dao_alias.clone(),
proposal_id.clone(),
vote,
vote_weight,
]);
let exec = SubCommand::with_name("exec")
.about("Execute a DAO proposal")
.args(&vec![dao_alias, proposal_id]);
let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
]);
// Scan
let reset = Arg::with_name("reset")
@@ -354,6 +435,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
inspect,
broadcast,
subscribe,
dao,
scan,
explorer,
alias,

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,11 @@ use darkfi::{
async_daemonize, cli_desc,
rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
tx::Transaction,
util::{parse::encode_base10, path::expand_path},
util::{
parse::{decode_base10, encode_base10},
path::expand_path,
},
zk::halo2::Field,
Result,
};
use darkfi_money_contract::model::Coin;
@@ -71,6 +75,7 @@ use money::BALANCE_BASE10_DECIMALS;
/// Wallet functionality related to Dao
mod dao;
use dao::DaoParams;
/// Wallet functionality related to transactions history
mod txs_history;
@@ -82,6 +87,8 @@ use walletdb::{WalletDb, WalletPtr};
const CONFIG_FILE: &str = "drk_config.toml";
const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
// Dev Note: when adding/modifying args here,
// don't forget to update cli_util::generate_completions()
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
#[serde(default)]
#[structopt(name = "drk", about = cli_desc!())]
@@ -115,6 +122,8 @@ struct Args {
verbose: u8,
}
// Dev Note: when adding/modifying commands here,
// don't forget to update cli_util::generate_completions()
#[derive(Clone, Debug, Deserialize, StructOpt)]
enum Subcmd {
/// Fun
@@ -209,7 +218,13 @@ enum Subcmd {
/// find coins sent to us and fill our wallet with the necessary metadata.
Subscribe,
// TODO: DAO
/// DAO functionalities
Dao {
#[structopt(subcommand)]
/// Sub command to execute
command: DaoSubcmd,
},
/// Scan the blockchain and parse relevant transactions
Scan {
#[structopt(long)]
@@ -270,6 +285,102 @@ enum OtcSubcmd {
Sign,
}
#[derive(Clone, Debug, Deserialize, StructOpt)]
enum DaoSubcmd {
/// Create DAO parameters
Create {
/// The minimum amount of governance tokens needed to open a proposal for this DAO
proposer_limit: String,
/// Minimal threshold of participating total tokens needed for a proposal to pass
quorum: String,
/// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
approval_ratio: f64,
/// DAO's governance token ID
gov_token_id: String,
},
/// View DAO data from stdin
View,
/// Import DAO data from stdin
Import {
/// Named identifier for the DAO
dao_name: String,
},
/// List imported DAOs (or info about a specific one)
List {
/// Numeric identifier for the DAO (optional)
dao_alias: Option<String>,
},
/// Show the balance of a DAO
Balance {
/// Name or numeric identifier for the DAO
dao_alias: String,
},
/// Mint an imported DAO on-chain
Mint {
/// Name or numeric identifier for the DAO
dao_alias: String,
},
/// Create a proposal for a DAO
Propose {
/// Name or numeric identifier for the DAO
dao_alias: String,
/// Pubkey to send tokens to with proposal success
recipient: String,
/// Amount to send from DAO with proposal success
amount: String,
/// Token ID to send from DAO with proposal success
token: String,
},
/// List DAO proposals
Proposals {
/// Name or numeric identifier for the DAO
dao_alias: String,
},
/// View a DAO proposal data
Proposal {
/// Name or numeric identifier for the DAO
dao_alias: String,
/// Numeric identifier for the proposal
proposal_id: u64,
},
/// Vote on a given proposal
Vote {
/// Name or numeric identifier for the DAO
dao_alias: String,
/// Numeric identifier for the proposal
proposal_id: u64,
/// Vote (0 for NO, 1 for YES)
vote: u8,
/// Vote weight (amount of governance tokens)
vote_weight: String,
},
/// Execute a DAO proposal
Exec {
/// Name or numeric identifier for the DAO
dao_alias: String,
/// Numeric identifier for the proposal
proposal_id: u64,
},
}
#[derive(Clone, Debug, Deserialize, StructOpt)]
enum ExplorerSubcmd {
/// Fetch a blockchain transaction by hash
@@ -800,6 +911,280 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
}
}
Subcmd::Dao { command } => match command {
DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
if let Err(e) = f64::from_str(&proposer_limit) {
eprintln!("Invalid proposer limit: {e:?}");
exit(2);
}
if let Err(e) = f64::from_str(&quorum) {
eprintln!("Invalid quorum: {e:?}");
exit(2);
}
let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
if approval_ratio > 1.0 {
eprintln!("Error: Approval ratio cannot be >1.0");
exit(2);
}
let approval_ratio_base = 100_u64;
let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let gov_token_id = match drk.get_token(gov_token_id).await {
Ok(g) => g,
Err(e) => {
eprintln!("Invalid Token ID: {e:?}");
exit(2);
}
};
let secret_key = SecretKey::random(&mut OsRng);
let bulla_blind = pallas::Base::random(&mut OsRng);
let dao_params = DaoParams {
proposer_limit,
quorum,
approval_ratio_base,
approval_ratio_quot,
gov_token_id,
secret_key,
bulla_blind,
};
let encoded = bs58::encode(&serialize(&dao_params)).into_string();
eprintln!("{encoded}");
Ok(())
}
DaoSubcmd::View => {
let mut buf = String::new();
stdin().read_to_string(&mut buf)?;
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let dao_params: DaoParams = deserialize(&bytes)?;
eprintln!("{dao_params}");
Ok(())
}
DaoSubcmd::Import { dao_name } => {
let mut buf = String::new();
stdin().read_to_string(&mut buf)?;
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let dao_params: DaoParams = deserialize(&bytes)?;
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
if let Err(e) = drk.import_dao(dao_name, dao_params).await {
eprintln!("Failed to import DAO: {e:?}");
exit(2);
}
Ok(())
}
DaoSubcmd::List { dao_alias } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
// We cannot use .map() since get_dao_id() uses ?
let dao_id = match dao_alias {
Some(alias) => Some(drk.get_dao_id(&alias).await?),
None => None,
};
if let Err(e) = drk.dao_list(dao_id).await {
eprintln!("Failed to list DAO: {e:?}");
exit(2);
}
Ok(())
}
DaoSubcmd::Balance { dao_alias } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let balmap = match drk.dao_balance(dao_id).await {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to fetch DAO balance: {e:?}");
exit(2);
}
};
let aliases_map = match drk.get_aliases_mapped_by_token().await {
Ok(a) => a,
Err(e) => {
eprintln!("Failed to fetch wallet aliases: {e:?}");
exit(2);
}
};
// Create a prettytable with the new data:
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row!["Token ID", "Aliases", "Balance"]);
for (token_id, balance) in balmap.iter() {
let aliases = match aliases_map.get(token_id) {
Some(a) => a,
None => "-",
};
table.add_row(row![
token_id,
aliases,
encode_base10(*balance, BALANCE_BASE10_DECIMALS)
]);
}
if table.is_empty() {
eprintln!("No unspent balances found");
} else {
eprintln!("{table}");
}
Ok(())
}
DaoSubcmd::Mint { dao_alias } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let tx = match drk.dao_mint(dao_id).await {
Ok(tx) => tx,
Err(e) => {
eprintln!("Failed to mint DAO: {e:?}");
exit(2);
}
};
eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Propose { dao_alias, recipient, amount, token } => {
if let Err(e) = f64::from_str(&amount) {
eprintln!("Invalid amount: {e:?}");
exit(2);
}
let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
let rcpt = match PublicKey::from_str(&recipient) {
Ok(r) => r,
Err(e) => {
eprintln!("Invalid recipient: {e:?}");
exit(2);
}
};
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let token_id = match drk.get_token(token).await {
Ok(t) => t,
Err(e) => {
eprintln!("Invalid token alias: {e:?}");
exit(2);
}
};
let tx = match drk.dao_propose(dao_id, rcpt, amount, token_id).await {
Ok(tx) => tx,
Err(e) => {
eprintln!("Failed to create DAO proposal: {e:?}");
exit(2);
}
};
eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Proposals { dao_alias } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let proposals = drk.get_dao_proposals(dao_id).await?;
for proposal in proposals {
eprintln!("[{}] {:?}", proposal.id, proposal.bulla());
}
Ok(())
}
DaoSubcmd::Proposal { dao_alias, proposal_id } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let proposals = drk.get_dao_proposals(dao_id).await?;
let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
eprintln!("No such DAO proposal found");
exit(2);
};
eprintln!("{proposal}");
let votes = drk.get_dao_proposal_votes(proposal_id).await?;
eprintln!("votes:");
for vote in votes {
let option = if vote.vote_option { "yes" } else { "no " };
eprintln!(" {option} {}", vote.all_vote_value);
}
Ok(())
}
DaoSubcmd::Vote { dao_alias, proposal_id, vote, vote_weight } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
if let Err(e) = f64::from_str(&vote_weight) {
eprintln!("Invalid vote weight: {e:?}");
exit(2);
}
let weight = decode_base10(&vote_weight, BALANCE_BASE10_DECIMALS, true)?;
if vote > 1 {
eprintln!("Vote can be either 0 (NO) or 1 (YES)");
exit(2);
}
let vote = vote != 0;
let tx = match drk.dao_vote(dao_id, proposal_id, vote, weight).await {
Ok(tx) => tx,
Err(e) => {
eprintln!("Failed to create DAO Vote transaction: {e:?}");
exit(2);
}
};
// TODO: Write our_vote in the proposal sql.
eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Exec { dao_alias, proposal_id } => {
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let dao = drk.get_dao_by_id(dao_id).await?;
let proposal = drk.get_dao_proposal_by_id(proposal_id).await?;
assert!(proposal.dao_bulla == dao.bulla());
let tx = match drk.dao_exec(dao, proposal).await {
Ok(tx) => tx,
Err(e) => {
eprintln!("Failed to execute DAO proposal: {e:?}");
exit(2);
}
};
eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
},
Subcmd::Inspect => {
let mut buf = String::new();
stdin().read_to_string(&mut buf)?;
@@ -863,15 +1248,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
if list {
eprintln!("List requested.");
// TODO: implement
return Ok(())
unimplemented!()
}
if let Some(c) = checkpoint {
eprintln!("Checkpoint requested: {c}");
// TODO: implement
return Ok(())
unimplemented!()
}
if let Err(e) = drk.scan_blocks(false).await {