/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2022 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::process::exit;
use clap::{IntoApp, Parser, Subcommand};
use prettytable::{format, row, Table};
use url::Url;
use darkfi::{rpc::client::RpcClient, Result};
mod rpc;
#[derive(Subcommand)]
pub enum CliDaoSubCommands {
/// Create DAO
Create {
/// Minium number of governance tokens a user must have to propose a vote.
dao_proposer_limit: u64,
/// Minimum number of governance tokens staked on a proposal for it to pass.
dao_quorum: u64,
/// Quotient value of minimum vote ratio of yes:no votes required for a proposal to pass.
dao_approval_ratio_quot: u64,
/// Base value of minimum vote ratio of yes:no votes required for a proposal to pass.
dao_approval_ratio_base: u64,
},
/// Get DAO public address.
Addr {},
/// Get votes on current proposal in the form of [[true/false, user's GOV_tokens],[...]].
GetVotes {},
/// Get proposals in the form of [[destination, amount, token_id], [...]].
GetProposals {},
/// Mint tokens.
Mint {
/// Number of treasury tokens to mint.
token_supply: u64,
/// Public key of the DAO treasury.
dao_addr: String,
},
/// Get user balance.
UserBalance {
/// User public address.
addr: String,
},
/// Get DAO treasury balance.
DaoBalance {},
/// Get DAO bulla.
DaoBulla {},
/// Generate a new PublicKey.
Keygen {},
/// Airdrop tokens given recipient address and value.
Airdrop {
/// Airdrop recipient address.
addr: String,
/// Value to be airdropped.
value: u64,
},
/// Create a Proposal.
Propose {
/// Sender PublicKey.
sender: String,
/// Recipient PublicKey.
recipient: String,
/// Amount of tokens to be sent.
amount: u64,
},
/// Vote
Vote {
/// Voter's public address.
addr: String,
/// Vote value [yes/no].
vote: String,
},
/// Execute proposal bulla.
Exec {
/// Bulla.
bulla: String,
},
}
/// DAO cli
#[derive(Parser)]
#[clap(name = "dao")]
#[clap(arg_required_else_help(true))]
pub struct CliDao {
/// Increase verbosity
#[clap(short, parse(from_occurrences))]
pub verbose: u8,
#[clap(subcommand)]
pub command: Option,
}
pub struct Rpc {
client: RpcClient,
}
async fn start(options: CliDao) -> Result<()> {
let rpc_addr = "tcp://127.0.0.1:7777";
let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? };
match options.command {
Some(CliDaoSubCommands::Create {
dao_proposer_limit,
dao_quorum,
dao_approval_ratio_base,
dao_approval_ratio_quot,
}) => {
let reply = client
.create(
dao_proposer_limit,
dao_quorum,
dao_approval_ratio_quot,
dao_approval_ratio_base,
)
.await?;
println!("Created DAO bulla: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::Addr {}) => {
let reply = client.addr().await?;
println!("DAO public address: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::GetVotes {}) => {
let reply = client.get_votes().await?;
println!("Votes on current proposals: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::GetProposals {}) => {
let reply = client.get_proposals().await?;
println!("Current proposals: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::Mint { token_supply, dao_addr }) => {
let reply = client.mint(token_supply, dao_addr).await?;
println!("{}", &reply.as_str().unwrap().to_string());
return Ok(())
}
Some(CliDaoSubCommands::Keygen {}) => {
let reply = client.keygen().await?;
println!("User public key: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::Airdrop { addr, value }) => {
println!("Requesting airdrop of {} GOV", value);
let reply = client.airdrop(addr, value).await?;
println!("{}", &reply.as_str().unwrap().to_string());
return Ok(())
}
Some(CliDaoSubCommands::DaoBalance {}) => {
let rep = client.dao_balance().await?;
if !rep.is_object() {
eprintln!("Invalid balance data received from darkfid RPC endpoint.");
exit(1);
}
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row!["Token", "Balance"]);
for i in rep.as_object().unwrap().keys() {
if let Some(balance) = rep[i].as_u64() {
table.add_row(row![i, balance]);
continue
}
eprintln!("Found invalid balance data for key \"{}\"", i);
}
if table.is_empty() {
println!("No balances.");
} else {
println!("{}", table);
}
// 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 { addr }) => {
let rep = client.user_balance(addr).await?;
if !rep.is_object() {
eprintln!("Invalid balance data received from darkfid RPC endpoint.");
exit(1);
}
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row!["Token", "Balance"]);
for i in rep.as_object().unwrap().keys() {
if let Some(balance) = rep[i].as_u64() {
table.add_row(row![i, balance]);
continue
}
eprintln!("Found invalid balance data for key \"{}\"", i);
}
if table.is_empty() {
println!("No balances.");
} else {
println!("{}", table);
}
// println!("User balance: {}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::Propose { sender, recipient, amount }) => {
let reply = client.propose(sender.clone(), recipient.clone(), amount).await?;
println!(
"Proposal bulla: {}\nSender: {}\nRecipient: {}\nAmount: {} DRK",
&reply.to_string(),
sender,
recipient,
amount
);
return Ok(())
}
Some(CliDaoSubCommands::Vote { addr, vote }) => {
let reply = client.vote(addr, vote).await?;
println!("{}", &reply.to_string());
return Ok(())
}
Some(CliDaoSubCommands::Exec { bulla }) => {
let reply = client.exec(bulla).await?;
println!("{}", &reply.to_string());
return Ok(())
}
None => {}
}
Ok(())
}
#[async_std::main]
async fn main() -> Result<()> {
let args = CliDao::parse();
let _matches = CliDao::command().get_matches();
//let config_path = if args.config.is_some() {
// expand_path(&args.config.clone().unwrap())?
//} else {
// join_config_path(&PathBuf::from("drk.toml"))?
//};
// Spawn config file if it's not in place already.
//spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
//let (lvl, conf) = log_config(matches)?;
//TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
//let config = Config::::load(config_path)?;
start(args).await
}