cli_util.rs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. collections::{HashMap, HashSet},
  20. io::{stdin, Cursor, Read},
  21. slice,
  22. str::FromStr,
  23. };
  24. use rodio::{Decoder, OutputStreamBuilder, Sink};
  25. use smol::{channel::Sender, fs::read_to_string};
  26. use structopt_toml::{
  27. clap::{App, Arg, Shell, SubCommand},
  28. structopt::StructOpt,
  29. StructOptToml,
  30. };
  31. use url::Url;
  32. use darkfi::{
  33. cli_desc,
  34. tx::{ContractCallLeaf, Transaction, TransactionBuilder},
  35. util::{encoding::base64, parse::decode_base10, path::get_config_path},
  36. zk::Proof,
  37. Error, Result,
  38. };
  39. use darkfi_money_contract::model::TokenId;
  40. use darkfi_sdk::{
  41. crypto::{
  42. keypair::{Address, Network},
  43. pasta_prelude::PrimeField,
  44. FuncId, SecretKey,
  45. },
  46. dark_tree::DarkTree,
  47. pasta::pallas,
  48. ContractCallImport,
  49. };
  50. use darkfi_serial::deserialize_async;
  51. use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
  52. /// Defines a blockchain network configuration.
  53. /// Default values correspond to a local network.
  54. #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
  55. #[structopt()]
  56. pub struct BlockchainNetwork {
  57. #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/cache")]
  58. /// Path to blockchain cache database
  59. pub cache_path: String,
  60. #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/wallet.db")]
  61. /// Path to wallet database
  62. pub wallet_path: String,
  63. #[structopt(long, default_value = "changeme")]
  64. /// Password for the wallet database
  65. pub wallet_pass: String,
  66. #[structopt(short, long, default_value = "tcp://127.0.0.1:28345")]
  67. /// darkfid JSON-RPC endpoint
  68. pub endpoint: Url,
  69. #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/history.txt")]
  70. /// Path to interactive shell history file
  71. pub history_path: String,
  72. }
  73. /// Auxiliary function to parse darkfid configuration file and extract requested
  74. /// blockchain network config.
  75. pub async fn parse_blockchain_config(
  76. config: Option<String>,
  77. network: &str,
  78. fallback: &str,
  79. ) -> Result<(Network, BlockchainNetwork)> {
  80. // Grab network
  81. let used_net = match network {
  82. "mainnet" | "localnet" => Network::Mainnet,
  83. "testnet" => Network::Testnet,
  84. _ => return Err(Error::ParseFailed("Invalid blockchain network")),
  85. };
  86. // Grab config path
  87. let config_path = get_config_path(config, fallback)?;
  88. // Parse TOML file contents
  89. let contents = read_to_string(&config_path).await?;
  90. let contents: toml::Value = match toml::from_str(&contents) {
  91. Ok(v) => v,
  92. Err(e) => {
  93. eprintln!("Failed parsing TOML config: {e}");
  94. return Err(Error::ParseFailed("Failed parsing TOML config"))
  95. }
  96. };
  97. // Grab requested network config
  98. let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
  99. let Some(network_configs) = table.get("network_config") else {
  100. return Err(Error::ParseFailed("TOML does not contain network configurations"))
  101. };
  102. let Some(network_configs) = network_configs.as_table() else {
  103. return Err(Error::ParseFailed("`network_config` not a map"))
  104. };
  105. let Some(network_config) = network_configs.get(network) else {
  106. return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
  107. };
  108. let network_config = toml::to_string(&network_config).unwrap();
  109. let network_config =
  110. match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
  111. Ok(v) => v,
  112. Err(e) => {
  113. eprintln!("Failed parsing requested network configuration: {e}");
  114. return Err(Error::ParseFailed("Failed parsing requested network configuration"))
  115. }
  116. };
  117. Ok((used_net, network_config))
  118. }
  119. /// Auxiliary function to parse a base64 encoded transaction from stdin.
  120. pub async fn parse_tx_from_stdin() -> Result<Transaction> {
  121. let mut buf = String::new();
  122. stdin().read_to_string(&mut buf)?;
  123. match base64::decode(buf.trim()) {
  124. Some(bytes) => Ok(deserialize_async(&bytes).await?),
  125. None => Err(Error::ParseFailed("Failed to decode transaction")),
  126. }
  127. }
  128. /// Auxiliary function to parse a base64 encoded transaction from
  129. /// provided input or fallback to stdin if its empty.
  130. pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
  131. match input.len() {
  132. 0 => parse_tx_from_stdin().await,
  133. 1 => match base64::decode(input[0].trim()) {
  134. Some(bytes) => Ok(deserialize_async(&bytes).await?),
  135. None => Err(Error::ParseFailed("Failed to decode transaction")),
  136. },
  137. _ => Err(Error::ParseFailed("Multiline input provided")),
  138. }
  139. }
  140. /// Auxiliary function to parse base64 encoded contract calls from stdin.
  141. pub async fn parse_calls_from_stdin() -> Result<Vec<ContractCallImport>> {
  142. let lines = stdin().lines();
  143. let mut calls = vec![];
  144. for line in lines {
  145. let Some(line) = base64::decode(&line?) else {
  146. return Err(Error::ParseFailed("Failed to decode base64"))
  147. };
  148. calls.push(deserialize_async(&line).await?);
  149. }
  150. Ok(calls)
  151. }
  152. /// Auxiliary function to parse base64 encoded contract calls from
  153. /// provided input or fallback to stdin if its empty.
  154. pub async fn parse_calls_from_input(input: &[String]) -> Result<Vec<ContractCallImport>> {
  155. if input.is_empty() {
  156. return parse_calls_from_stdin().await
  157. }
  158. let mut calls = vec![];
  159. for line in input {
  160. let Some(line) = base64::decode(line) else {
  161. return Err(Error::ParseFailed("Failed to decode base64"))
  162. };
  163. calls.push(deserialize_async(&line).await?);
  164. }
  165. Ok(calls)
  166. }
  167. /// Auxiliary function to parse provided string into a values pair.
  168. pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
  169. let v: Vec<&str> = s.split(':').collect();
  170. if v.len() != 2 {
  171. return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
  172. }
  173. let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
  174. let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
  175. if val0.is_err() || val1.is_err() {
  176. return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
  177. }
  178. Ok((val0.unwrap(), val1.unwrap()))
  179. }
  180. /// Auxiliary function to parse provided string into a tokens pair.
  181. pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
  182. let v: Vec<&str> = s.split(':').collect();
  183. if v.len() != 2 {
  184. return Err(Error::ParseFailed(
  185. "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
  186. A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
  187. ))
  188. }
  189. let tok0 = drk.get_token(v[0].to_string()).await;
  190. let tok1 = drk.get_token(v[1].to_string()).await;
  191. if tok0.is_err() || tok1.is_err() {
  192. return Err(Error::ParseFailed(
  193. "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
  194. A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
  195. ))
  196. }
  197. Ok((tok0.unwrap(), tok1.unwrap()))
  198. }
  199. /// Fun police go away
  200. pub async fn kaching() {
  201. const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
  202. let cursor = Cursor::new(WALLET_MP3);
  203. let Ok(stream_handle) = OutputStreamBuilder::open_default_stream() else { return };
  204. let sink = Sink::connect_new(stream_handle.mixer());
  205. let Ok(source) = Decoder::new(cursor) else { return };
  206. sink.append(source);
  207. sink.detach();
  208. }
  209. /// Auxiliary function to generate provided shell completions.
  210. pub fn generate_completions(shell: &str) -> Result<String> {
  211. // Sub-commands
  212. // Interactive
  213. let interactive = SubCommand::with_name("interactive").about("Enter Drk interactive shell");
  214. // Kaching
  215. let kaching = SubCommand::with_name("kaching").about("Fun");
  216. // Ping
  217. let ping =
  218. SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
  219. // Completions
  220. let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
  221. let completions = SubCommand::with_name("completions")
  222. .about("Generate a SHELL completion script and print to stdout")
  223. .arg(shell_arg);
  224. // Wallet
  225. let initialize = SubCommand::with_name("initialize").about("Initialize wallet database");
  226. let keygen = SubCommand::with_name("keygen").about("Generate a new keypair in the wallet");
  227. let balance = SubCommand::with_name("balance").about("Query the wallet for known balances");
  228. let address = SubCommand::with_name("address").about("Get the default address in the wallet");
  229. let addresses =
  230. SubCommand::with_name("addresses").about("Print all the addresses in the wallet");
  231. let index = Arg::with_name("index").help("Identifier of the address");
  232. let default_address = SubCommand::with_name("default-address")
  233. .about("Set the default address in the wallet")
  234. .arg(index.clone());
  235. let secrets =
  236. SubCommand::with_name("secrets").about("Print all the secret keys from the wallet");
  237. let import_secrets = SubCommand::with_name("import-secrets")
  238. .about("Import secret keys from stdin into the wallet, separated by newlines");
  239. let tree = SubCommand::with_name("tree").about("Print the Merkle tree in the wallet");
  240. let coins = SubCommand::with_name("coins").about("Print all the coins in the wallet");
  241. let spend_hook = Arg::with_name("spend-hook").help("Optional contract spend hook to use");
  242. let user_data = Arg::with_name("user-data").help("Optional user data to use");
  243. let mining_config = SubCommand::with_name("mining-config")
  244. .about("Print a wallet address mining configuration")
  245. .args(&[index, spend_hook.clone(), user_data.clone()]);
  246. let wallet = SubCommand::with_name("wallet").about("Wallet operations").subcommands(vec![
  247. initialize,
  248. keygen,
  249. balance,
  250. address,
  251. addresses,
  252. default_address,
  253. secrets,
  254. import_secrets,
  255. tree,
  256. coins,
  257. mining_config,
  258. ]);
  259. // Spend
  260. let spend = SubCommand::with_name("spend")
  261. .about("Read a transaction from stdin and mark its input coins as spent");
  262. // Unspend
  263. let coin = Arg::with_name("coin").help("base64-encoded coin to mark as unspent");
  264. let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
  265. // Transfer
  266. let amount = Arg::with_name("amount").help("Amount to send");
  267. let token = Arg::with_name("token").help("Token ID to send");
  268. let recipient = Arg::with_name("recipient").help("Recipient address");
  269. let half_split = Arg::with_name("half-split")
  270. .long("half-split")
  271. .help("Split the output coin into two equal halves");
  272. let transfer = SubCommand::with_name("transfer").about("Create a payment transaction").args(&[
  273. amount.clone(),
  274. token.clone(),
  275. recipient.clone(),
  276. spend_hook.clone(),
  277. user_data.clone(),
  278. half_split,
  279. ]);
  280. // Otc
  281. let value_pair = Arg::with_name("value-pair")
  282. .short("v")
  283. .long("value-pair")
  284. .takes_value(true)
  285. .help("Value pair to send:recv (11.55:99.42)");
  286. let token_pair = Arg::with_name("token-pair")
  287. .short("t")
  288. .long("token-pair")
  289. .takes_value(true)
  290. .help("Token pair to send:recv (f00:b4r)");
  291. let init = SubCommand::with_name("init")
  292. .about("Initialize the first half of the atomic swap")
  293. .args(&[value_pair, token_pair]);
  294. let join =
  295. SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
  296. let inspect = SubCommand::with_name("inspect")
  297. .about("Inspect a swap half or the full swap tx from stdin");
  298. let sign = SubCommand::with_name("sign").about("Sign a swap transaction given from stdin");
  299. let otc = SubCommand::with_name("otc")
  300. .about("OTC atomic swap")
  301. .subcommands(vec![init, join, inspect, sign]);
  302. // DAO
  303. let proposer_limit = Arg::with_name("proposer-limit")
  304. .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
  305. let quorum = Arg::with_name("quorum")
  306. .help("Minimal threshold of participating total tokens needed for a proposal to pass");
  307. let early_exec_quorum = Arg::with_name("early-exec-quorum")
  308. .help("Minimal threshold of participating total tokens needed for a proposal to be considered strongly supported, enabling early execution. Must be greater than or equal to normal quorum.");
  309. let approval_ratio = Arg::with_name("approval-ratio")
  310. .help("The ratio of yes votes/total votes needed for a proposal to pass (2 decimals)");
  311. let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
  312. let create = SubCommand::with_name("create").about("Create DAO parameters").args(&[
  313. proposer_limit,
  314. quorum,
  315. early_exec_quorum,
  316. approval_ratio,
  317. gov_token_id,
  318. ]);
  319. let view = SubCommand::with_name("view").about("View DAO data from stdin");
  320. let name = Arg::with_name("name").help("Name identifier for the DAO");
  321. let import = SubCommand::with_name("import")
  322. .about("Import DAO data from stdin")
  323. .args(slice::from_ref(&name));
  324. let remove = SubCommand::with_name("remove")
  325. .about("Remove a DAO and all its data")
  326. .args(slice::from_ref(&name));
  327. let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
  328. let list = SubCommand::with_name("list")
  329. .about("List imported DAOs (or info about a specific one)")
  330. .args(&[opt_name]);
  331. let balance = SubCommand::with_name("balance")
  332. .about("Show the balance of a DAO")
  333. .args(slice::from_ref(&name));
  334. let mint = SubCommand::with_name("mint")
  335. .about("Mint an imported DAO on-chain")
  336. .args(slice::from_ref(&name));
  337. let duration = Arg::with_name("duration").help("Duration of the proposal, in block windows");
  338. let propose_transfer = SubCommand::with_name("propose-transfer")
  339. .about("Create a transfer proposal for a DAO")
  340. .args(&[
  341. name.clone(),
  342. duration.clone(),
  343. amount,
  344. token,
  345. recipient,
  346. spend_hook.clone(),
  347. user_data.clone(),
  348. ]);
  349. let propose_generic = SubCommand::with_name("propose-generic")
  350. .about("Create a generic proposal for a DAO")
  351. .args(&[name.clone(), duration, user_data.clone()]);
  352. let proposals = SubCommand::with_name("proposals").about("List DAO proposals").arg(&name);
  353. let bulla = Arg::with_name("bulla").help("Bulla identifier for the proposal");
  354. let export = Arg::with_name("export").help("Encrypt the proposal and encode it to base64");
  355. let mint_proposal = Arg::with_name("mint-proposal").help("Create the proposal transaction");
  356. let proposal = SubCommand::with_name("proposal").about("View a DAO proposal data").args(&[
  357. bulla.clone(),
  358. export,
  359. mint_proposal,
  360. ]);
  361. let proposal_import = SubCommand::with_name("proposal-import")
  362. .about("Import a base64 encoded and encrypted proposal from stdin");
  363. let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
  364. let vote_weight =
  365. Arg::with_name("vote-weight").help("Optional vote weight (amount of governance tokens)");
  366. let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&[
  367. bulla.clone(),
  368. vote,
  369. vote_weight,
  370. ]);
  371. let early = Arg::with_name("early").long("early").help("Execute the proposal early");
  372. let exec = SubCommand::with_name("exec").about("Execute a DAO proposal").args(&[bulla, early]);
  373. let spend_hook_cmd = SubCommand::with_name("spend-hook")
  374. .about("Print the DAO contract base64-encoded spend hook");
  375. let mining_config =
  376. SubCommand::with_name("mining-config").about("Print a DAO mining configuration").arg(name);
  377. let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
  378. create,
  379. view,
  380. import,
  381. remove,
  382. list,
  383. balance,
  384. mint,
  385. propose_transfer,
  386. propose_generic,
  387. proposals,
  388. proposal,
  389. proposal_import,
  390. vote,
  391. exec,
  392. spend_hook_cmd,
  393. mining_config,
  394. ]);
  395. // AttachFee
  396. let attach_fee = SubCommand::with_name("attach-fee")
  397. .about("Attach the fee call to a transaction given from stdin");
  398. // TxFromCalls
  399. let calls_map =
  400. Arg::with_name("calls-map").help("Optional parent/children dependency map for the calls");
  401. let tx_from_calls = SubCommand::with_name("tx-from-calls")
  402. .about(
  403. "Create a transaction from newline-separated calls from stdin and attach the fee call",
  404. )
  405. .args(&[calls_map]);
  406. // Inspect
  407. let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
  408. // Broadcast
  409. let broadcast =
  410. SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
  411. // Scan
  412. let reset = Arg::with_name("reset")
  413. .long("reset")
  414. .help("Reset wallet state to provided block height and start scanning");
  415. let scan = SubCommand::with_name("scan")
  416. .about("Scan the blockchain and parse relevant transactions")
  417. .args(&[reset]);
  418. // Explorer
  419. let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
  420. let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base64");
  421. let fetch_tx = SubCommand::with_name("fetch-tx")
  422. .about("Fetch a blockchain transaction by hash")
  423. .args(&[tx_hash, encode]);
  424. let simulate_tx =
  425. SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
  426. let tx_hash = Arg::with_name("tx-hash").help("Fetch specific history record (optional)");
  427. let encode = Arg::with_name("encode")
  428. .long("encode")
  429. .help("Encode specific history record transaction to base64");
  430. let txs_history = SubCommand::with_name("txs-history")
  431. .about("Fetch broadcasted transactions history")
  432. .args(&[tx_hash, encode]);
  433. let clear_reverted =
  434. SubCommand::with_name("clear-reverted").about("Remove reverted transactions from history");
  435. let height = Arg::with_name("height").help("Fetch specific height record (optional)");
  436. let scanned_blocks = SubCommand::with_name("scanned-blocks")
  437. .about("Fetch scanned blocks records")
  438. .args(&[height]);
  439. let mining_config = SubCommand::with_name("mining-config")
  440. .about("Read a mining configuration from stdin and display its parts");
  441. let explorer =
  442. SubCommand::with_name("explorer").about("Explorer related subcommands").subcommands(vec![
  443. fetch_tx,
  444. simulate_tx,
  445. txs_history,
  446. clear_reverted,
  447. scanned_blocks,
  448. mining_config,
  449. ]);
  450. // Alias
  451. let alias = Arg::with_name("alias").help("Token alias");
  452. let token = Arg::with_name("token").help("Token to create alias for");
  453. let add = SubCommand::with_name("add").about("Create a Token alias").args(&[alias, token]);
  454. let alias = Arg::with_name("alias")
  455. .short("a")
  456. .long("alias")
  457. .takes_value(true)
  458. .help("Token alias to search for");
  459. let token = Arg::with_name("token")
  460. .short("t")
  461. .long("token")
  462. .takes_value(true)
  463. .help("Token to search alias for");
  464. let show = SubCommand::with_name("show")
  465. .about(
  466. "Print alias info of optional arguments. \
  467. If no argument is provided, list all the aliases in the wallet.",
  468. )
  469. .args(&[alias, token]);
  470. let alias = Arg::with_name("alias").help("Token alias to remove");
  471. let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
  472. let alias = SubCommand::with_name("alias")
  473. .about("Manage Token aliases")
  474. .subcommands(vec![add, show, remove]);
  475. // Token
  476. let secret_key = Arg::with_name("secret-key").help("Mint authority secret key");
  477. let token_blind = Arg::with_name("token-blind").help("Mint authority token blind");
  478. let import = SubCommand::with_name("import")
  479. .about("Import a mint authority")
  480. .args(&[secret_key, token_blind]);
  481. let generate_mint =
  482. SubCommand::with_name("generate-mint").about("Generate a new mint authority");
  483. let list =
  484. SubCommand::with_name("list").about("List token IDs with available mint authorities");
  485. let token = Arg::with_name("token").help("Token ID to mint");
  486. let amount = Arg::with_name("amount").help("Amount to mint");
  487. let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
  488. let mint = SubCommand::with_name("mint")
  489. .about("Mint tokens")
  490. .args(&[token, amount, recipient, spend_hook, user_data]);
  491. let token = Arg::with_name("token").help("Token ID to freeze");
  492. let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
  493. let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
  494. import,
  495. generate_mint,
  496. list,
  497. mint,
  498. freeze,
  499. ]);
  500. // Contract
  501. let generate_deploy =
  502. SubCommand::with_name("generate-deploy").about("Generate a new deploy authority");
  503. let contract_id = Arg::with_name("contract-id").help("Contract ID (optional)");
  504. let list = SubCommand::with_name("list")
  505. .about("List deploy authorities in the wallet (or a specific one)")
  506. .args(&[contract_id]);
  507. let tx_hash = Arg::with_name("tx-hash").help("Record transaction hash");
  508. let export_data = SubCommand::with_name("export-data")
  509. .about("Export a contract history record wasm bincode and deployment instruction, encoded to base64")
  510. .args(&[tx_hash]);
  511. let deploy_auth = Arg::with_name("deploy-auth").help("Contract ID (deploy authority)");
  512. let wasm_path = Arg::with_name("wasm-path").help("Path to contract wasm bincode");
  513. let deploy_ix =
  514. Arg::with_name("deploy-ix").help("Optional path to serialized deploy instruction");
  515. let deploy = SubCommand::with_name("deploy").about("Deploy a smart contract").args(&[
  516. deploy_auth.clone(),
  517. wasm_path,
  518. deploy_ix,
  519. ]);
  520. let lock = SubCommand::with_name("lock").about("Lock a smart contract").args(&[deploy_auth]);
  521. let contract = SubCommand::with_name("contract")
  522. .about("Contract functionalities")
  523. .subcommands(vec![generate_deploy, list, export_data, deploy, lock]);
  524. // Main arguments
  525. let config = Arg::with_name("config")
  526. .short("c")
  527. .long("config")
  528. .takes_value(true)
  529. .help("Configuration file to use");
  530. let network = Arg::with_name("network")
  531. .long("network")
  532. .takes_value(true)
  533. .help("Blockchain network to use");
  534. let command = vec![
  535. interactive,
  536. kaching,
  537. ping,
  538. completions,
  539. wallet,
  540. spend,
  541. unspend,
  542. transfer,
  543. otc,
  544. attach_fee,
  545. tx_from_calls,
  546. inspect,
  547. broadcast,
  548. dao,
  549. scan,
  550. explorer,
  551. alias,
  552. token,
  553. contract,
  554. ];
  555. let fun = Arg::with_name("fun")
  556. .short("f")
  557. .long("fun")
  558. .help("Flag indicating whether you want some fun in your life");
  559. let log = Arg::with_name("log")
  560. .short("l")
  561. .long("log")
  562. .takes_value(true)
  563. .help("Set log file to ouput into");
  564. let verbose = Arg::with_name("verbose")
  565. .short("v")
  566. .multiple(true)
  567. .help("Increase verbosity (-vvv supported)");
  568. let mut app = App::new("drk")
  569. .about(cli_desc!())
  570. .args(&[config, network, fun, log, verbose])
  571. .subcommands(command);
  572. let shell = match Shell::from_str(shell) {
  573. Ok(s) => s,
  574. Err(e) => return Err(Error::Custom(e)),
  575. };
  576. let mut buf = vec![];
  577. app.gen_completions_to("./drk", shell, &mut buf);
  578. Ok(String::from_utf8(buf)?)
  579. }
  580. /// Auxiliary function to print provided string buffer.
  581. pub fn print_output(buf: &[String]) {
  582. for line in buf {
  583. println!("{line}");
  584. }
  585. }
  586. /// Auxiliary function to print or insert provided messages to given
  587. /// buffer reference. If a channel sender is provided, the messages
  588. /// are send to that instead.
  589. pub async fn append_or_print(
  590. buf: &mut Vec<String>,
  591. sender: Option<&Sender<Vec<String>>>,
  592. print: &bool,
  593. messages: Vec<String>,
  594. ) {
  595. // Send the messages to the channel, if provided
  596. if let Some(sender) = sender {
  597. if let Err(e) = sender.send(messages).await {
  598. let err_msg = format!("[append_or_print] Sending messages to channel failed: {e}");
  599. if *print {
  600. println!("{err_msg}");
  601. } else {
  602. buf.push(err_msg);
  603. }
  604. }
  605. return
  606. }
  607. // Print the messages
  608. if *print {
  609. for msg in messages {
  610. println!("{msg}");
  611. }
  612. return
  613. }
  614. // Insert the messages in the buffer
  615. for msg in messages {
  616. buf.push(msg);
  617. }
  618. }
  619. /// Auxiliary function to parse a base64 encoded mining configuration
  620. /// from stdin.
  621. pub async fn parse_mining_config_from_stdin(
  622. ) -> Result<(String, String, Option<String>, Option<String>)> {
  623. let mut buf = String::new();
  624. stdin().read_to_string(&mut buf)?;
  625. let config = buf.trim();
  626. let (recipient, spend_hook, user_data) = match base64::decode(config) {
  627. Some(bytes) => deserialize_async(&bytes).await?,
  628. None => return Err(Error::ParseFailed("Failed to decode mining configuration")),
  629. };
  630. Ok((config.to_string(), recipient, spend_hook, user_data))
  631. }
  632. /// Auxiliary function to parse a base64 encoded mining configuration
  633. /// from provided input or fallback to stdin if its empty.
  634. pub async fn parse_mining_config_from_input(
  635. input: &[String],
  636. ) -> Result<(String, String, Option<String>, Option<String>)> {
  637. match input.len() {
  638. 0 => parse_mining_config_from_stdin().await,
  639. 1 => {
  640. let config = input[0].trim();
  641. let (recipient, spend_hook, user_data) = match base64::decode(config) {
  642. Some(bytes) => deserialize_async(&bytes).await?,
  643. None => return Err(Error::ParseFailed("Failed to decode mining configuration")),
  644. };
  645. Ok((config.to_string(), recipient, spend_hook, user_data))
  646. }
  647. _ => Err(Error::ParseFailed("Multiline input provided")),
  648. }
  649. }
  650. /// Auxiliary function to display the parts of a mining configuration.
  651. pub fn display_mining_config(
  652. config: &str,
  653. recipient_str: &str,
  654. spend_hook: &Option<String>,
  655. user_data: &Option<String>,
  656. output: &mut Vec<String>,
  657. ) {
  658. output.push(format!("DarkFi mining configuration address: {config}"));
  659. match Address::from_str(recipient_str) {
  660. Ok(recipient) => {
  661. output.push(format!("Recipient: {recipient_str}"));
  662. output.push(format!("Public key: {}", recipient.public_key()));
  663. output.push(format!("Network: {:?}", recipient.network()));
  664. }
  665. Err(e) => output.push(format!("Recipient: Invalid ({e})")),
  666. }
  667. let spend_hook = match spend_hook {
  668. Some(spend_hook_str) => match FuncId::from_str(spend_hook_str) {
  669. Ok(_) => String::from(spend_hook_str),
  670. Err(e) => format!("Invalid ({e})"),
  671. },
  672. None => String::from("-"),
  673. };
  674. output.push(format!("Spend hook: {spend_hook}"));
  675. let user_data = match user_data {
  676. Some(user_data_str) => match bs58::decode(&user_data_str).into_vec() {
  677. Ok(bytes) => match bytes.try_into() {
  678. Ok(bytes) => {
  679. if pallas::Base::from_repr(bytes).is_some().into() {
  680. String::from(user_data_str)
  681. } else {
  682. String::from("Invalid")
  683. }
  684. }
  685. Err(e) => format!("Invalid ({e:?})"),
  686. },
  687. Err(e) => format!("Invalid ({e})"),
  688. },
  689. None => String::from("-"),
  690. };
  691. output.push(format!("User data: {user_data}"));
  692. }
  693. /// Cast `ContractCallImport` to `ContractCallLeaf`
  694. fn to_leaf(call: &ContractCallImport) -> ContractCallLeaf {
  695. ContractCallLeaf {
  696. call: call.call().clone(),
  697. proofs: call.proofs().iter().map(|p| Proof::new(p.clone())).collect(),
  698. }
  699. }
  700. /// Recursively build subtree for a DarkTree
  701. fn build_subtree(
  702. idx: usize,
  703. calls: &[ContractCallImport],
  704. children_map: &HashMap<usize, &Vec<usize>>,
  705. ) -> DarkTree<ContractCallLeaf> {
  706. let children_idx = children_map.get(&idx).map(|v| v.as_slice()).unwrap_or(&[]);
  707. let children: Vec<DarkTree<ContractCallLeaf>> =
  708. children_idx.iter().map(|&i| build_subtree(i, calls, children_map)).collect();
  709. DarkTree::new(to_leaf(&calls[idx]), children, None, None)
  710. }
  711. /// Recursively retrieve the signature keys in Post order traversal
  712. fn retrieve_signature_keys(
  713. idx: usize,
  714. calls: &[ContractCallImport],
  715. children_map: &HashMap<usize, &Vec<usize>>,
  716. sig_keys: &mut Vec<Vec<SecretKey>>,
  717. ) {
  718. let children_idx = children_map.get(&idx).map(|v| v.as_slice()).unwrap_or(&[]);
  719. for i in children_idx {
  720. retrieve_signature_keys(*i, calls, children_map, sig_keys)
  721. }
  722. sig_keys.push(calls[idx].secrets().to_vec());
  723. }
  724. /// Build a `Transaction` given a slice of calls and their mapping
  725. pub fn tx_from_calls_mapped(
  726. calls: &[ContractCallImport],
  727. map: &[(usize, Vec<usize>)],
  728. ) -> Result<(TransactionBuilder, Vec<Vec<SecretKey>>)> {
  729. assert_eq!(calls.len(), map.len());
  730. let children_map: HashMap<usize, &Vec<usize>> = map.iter().map(|(k, v)| (*k, v)).collect();
  731. let all_children_idx: HashSet<&usize> = children_map.values().flat_map(|v| *v).collect();
  732. let root_idxs: Vec<usize> =
  733. map.iter().map(|(k, _)| *k).filter(|k| !all_children_idx.contains(k)).collect();
  734. // Build the first root call
  735. let root_idx = root_idxs[0];
  736. let root_children: Vec<DarkTree<ContractCallLeaf>> =
  737. children_map[&root_idx].iter().map(|&i| build_subtree(i, calls, &children_map)).collect();
  738. let mut tx_builder = TransactionBuilder::new(to_leaf(&calls[root_idx]), root_children)?;
  739. // Build remaining root calls
  740. for root_idx in &root_idxs[1..] {
  741. let root_children: Vec<DarkTree<ContractCallLeaf>> = children_map[root_idx]
  742. .iter()
  743. .map(|&i| build_subtree(i, calls, &children_map))
  744. .collect();
  745. tx_builder.append(to_leaf(&calls[*root_idx]), root_children)?;
  746. }
  747. let mut signature_secrets: Vec<Vec<SecretKey>> = vec![];
  748. for idx in root_idxs {
  749. retrieve_signature_keys(idx, calls, &children_map, &mut signature_secrets);
  750. }
  751. Ok((tx_builder, signature_secrets))
  752. }
  753. /// Auxiliary function to parse a contract call mapping.
  754. ///
  755. /// The mapping is in the format of `{0: [1,2], 1: [], 2:[3], 3:[]}`.
  756. /// It supports nesting and this kind of logic as expected.
  757. ///
  758. /// Errors out if there are non-unique keys or cyclic references.
  759. pub fn parse_tree(input: &str) -> std::result::Result<Vec<(usize, Vec<usize>)>, String> {
  760. let s = input
  761. .trim()
  762. .strip_prefix('{')
  763. .and_then(|s| s.strip_suffix('}'))
  764. .ok_or("expected {}")?
  765. .trim();
  766. let mut entries = vec![];
  767. let mut seen_keys = HashSet::new();
  768. if s.is_empty() {
  769. return Ok(entries)
  770. }
  771. let mut rest = s;
  772. while !rest.is_empty() {
  773. // Parse key
  774. let (key_str, after_key) = rest.split_once(':').ok_or("expected ':'")?;
  775. let key: usize = key_str.trim().parse().map_err(|_| "invalid key")?;
  776. if !seen_keys.insert(key) {
  777. return Err(format!("duplicate key: {}", key));
  778. }
  779. // Parse array
  780. let after_key = after_key.trim();
  781. let arr_start = after_key.strip_prefix('[').ok_or("expected '['")?;
  782. let (arr_content, after_arr) = arr_start.split_once(']').ok_or("expected ']'")?;
  783. let children: Vec<usize> = arr_content
  784. .split(',')
  785. .map(|s| s.trim())
  786. .filter(|s| !s.is_empty())
  787. .map(|s| s.parse().map_err(|_| "invalid child"))
  788. .collect::<std::result::Result<_, _>>()?;
  789. entries.push((key, children));
  790. // Move to next entry
  791. rest = after_arr.trim().strip_prefix(',').unwrap_or(after_arr).trim();
  792. }
  793. check_cycles(&entries)?;
  794. Ok(entries)
  795. }
  796. fn check_cycles(entries: &[(usize, Vec<usize>)]) -> std::result::Result<(), String> {
  797. let graph: HashMap<usize, &Vec<usize>> = entries.iter().map(|(k, v)| (*k, v)).collect();
  798. let mut visited = HashSet::new();
  799. let mut path = Vec::new();
  800. fn dfs(
  801. node: usize,
  802. graph: &HashMap<usize, &Vec<usize>>,
  803. visited: &mut HashSet<usize>,
  804. path: &mut Vec<usize>,
  805. ) -> std::result::Result<(), String> {
  806. if let Some(pos) = path.iter().position(|&n| n == node) {
  807. let cycle: Vec<_> = path[pos..].iter().chain(&[node]).map(|n| n.to_string()).collect();
  808. return Err(format!("cycle detected: {}", cycle.join(" -> ")));
  809. }
  810. if visited.contains(&node) {
  811. return Ok(());
  812. }
  813. path.push(node);
  814. if let Some(children) = graph.get(&node) {
  815. for &child in *children {
  816. dfs(child, graph, visited, path)?;
  817. }
  818. }
  819. path.pop();
  820. visited.insert(node);
  821. Ok(())
  822. }
  823. for &(key, _) in entries {
  824. dfs(key, &graph, &mut visited, &mut path)?;
  825. }
  826. Ok(())
  827. }
  828. #[cfg(test)]
  829. mod tests {
  830. use super::*;
  831. use darkfi_sdk::{
  832. crypto::{pasta_prelude::Field, ContractId},
  833. ContractCall,
  834. };
  835. use rand::rngs::OsRng;
  836. #[test]
  837. fn test_parse_tree() {
  838. // Valid inputs
  839. assert_eq!(parse_tree("{}").unwrap(), vec![]);
  840. assert_eq!(parse_tree("{ }").unwrap(), vec![]);
  841. assert_eq!(parse_tree("{ 0: [] }").unwrap(), vec![(0, vec![])]);
  842. assert_eq!(parse_tree("{ 0: [1, 2, 3] }").unwrap(), vec![(0, vec![1, 2, 3])]);
  843. assert_eq!(parse_tree("{0:[],1:[2]}").unwrap(), vec![(0, vec![]), (1, vec![2])]);
  844. assert_eq!(parse_tree("{ 0: [], 1: [], }").unwrap(), vec![(0, vec![]), (1, vec![])]);
  845. assert_eq!(parse_tree("{ 0: [1, 2,] }").unwrap(), vec![(0, vec![1, 2])]);
  846. assert_eq!(
  847. parse_tree("{ 0: [], 1: [2, 3], 2: [], 3: [4], 4: [] }").unwrap(),
  848. vec![(0, vec![]), (1, vec![2, 3]), (2, vec![]), (3, vec![4]), (4, vec![])]
  849. );
  850. assert_eq!(
  851. parse_tree("{ 0 : [ ] , 1 : [ 2 , 3 ] }").unwrap(),
  852. vec![(0, vec![]), (1, vec![2, 3])]
  853. );
  854. assert_eq!(
  855. parse_tree("{ 999: [1000, 1001], 1000: [], 1001: [] }").unwrap(),
  856. vec![(999, vec![1000, 1001]), (1000, vec![]), (1001, vec![])]
  857. );
  858. // Order preservation
  859. let keys: Vec<usize> =
  860. parse_tree("{ 5: [], 2: [], 9: [], 0: [] }").unwrap().iter().map(|(k, _)| *k).collect();
  861. assert_eq!(keys, vec![5, 2, 9, 0]);
  862. // Valid DAG (not a cycle)
  863. assert!(parse_tree("{ 0: [1, 2], 1: [3], 2: [3], 3: [] }").is_ok());
  864. // Syntax errors
  865. assert!(parse_tree("0: [] }").is_err());
  866. assert!(parse_tree("{ 0: []").is_err());
  867. assert!(parse_tree("{ 0 [] }").is_err());
  868. assert!(parse_tree("{ 0: ] }").is_err());
  869. assert!(parse_tree("{ 0: [1, 2 }").is_err());
  870. assert!(parse_tree("{ abc: [] }").is_err());
  871. assert!(parse_tree("{ 0: [abc] }").is_err());
  872. assert!(parse_tree("{ -1: [] }").is_err());
  873. // Duplicate keys
  874. assert!(parse_tree("{ 0: [], 0: [1] }").unwrap_err().contains("duplicate key: 0"));
  875. assert!(parse_tree("{ 0: [], 1: [], 2: [], 1: [] }")
  876. .unwrap_err()
  877. .contains("duplicate key: 1"));
  878. // Cycle detection
  879. let err = parse_tree("{ 0: [0] }").unwrap_err();
  880. assert!(err.contains("cycle detected") && err.contains("0 -> 0"));
  881. let err = parse_tree("{ 0: [1], 1: [0] }").unwrap_err();
  882. assert!(err.contains("cycle detected"));
  883. let err = parse_tree("{ 0: [1], 1: [2], 2: [3], 3: [0] }").unwrap_err();
  884. assert!(err.contains("cycle detected") && err.contains("0 -> 1 -> 2 -> 3 -> 0"));
  885. let err = parse_tree("{ 0: [1], 1: [2], 2: [3], 3: [2] }").unwrap_err();
  886. assert!(err.contains("cycle detected") && err.contains("2 -> 3 -> 2"));
  887. }
  888. #[test]
  889. fn test_tx_from_calls_mapped() {
  890. let contract0 = ContractId::from(pallas::Base::random(&mut OsRng));
  891. let contract1 = ContractId::from(pallas::Base::random(&mut OsRng));
  892. let contract2 = ContractId::from(pallas::Base::random(&mut OsRng));
  893. let call0 = ContractCallImport::new(
  894. ContractCall { contract_id: contract0, data: vec![] },
  895. vec![],
  896. vec![],
  897. );
  898. let call1 = ContractCallImport::new(
  899. ContractCall { contract_id: contract1, data: vec![] },
  900. vec![],
  901. vec![SecretKey::random(&mut OsRng), SecretKey::random(&mut OsRng)],
  902. );
  903. let call2 = ContractCallImport::new(
  904. ContractCall { contract_id: contract2, data: vec![] },
  905. vec![],
  906. vec![SecretKey::random(&mut OsRng)],
  907. );
  908. // Transaction with 3 root calls, each with no children
  909. let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
  910. &[call0.clone(), call1.clone(), call2.clone()],
  911. &parse_tree("{0 : [], 1: [], 2: []}").unwrap(),
  912. )
  913. .unwrap();
  914. let leafs = tx_builder.calls.build_vec().unwrap();
  915. assert_eq!(leafs.len(), 3);
  916. assert_eq!(leafs[0].data.call.contract_id, contract0);
  917. assert_eq!(leafs[1].data.call.contract_id, contract1);
  918. assert_eq!(leafs[2].data.call.contract_id, contract2);
  919. assert_eq!(sig_keys.len(), 3);
  920. assert_eq!(sig_keys[0].len(), 0);
  921. assert_eq!(sig_keys[1].len(), 2);
  922. assert_eq!(sig_keys[2].len(), 1);
  923. // Transaction with 2 root calls, the second call is child of the first
  924. let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
  925. &[call0.clone(), call1.clone(), call2.clone()],
  926. &parse_tree("{0 : [1], 1: [], 2: []}").unwrap(),
  927. )
  928. .unwrap();
  929. let leafs = tx_builder.calls.build_vec().unwrap();
  930. assert_eq!(leafs.len(), 3);
  931. assert_eq!(leafs[0].data.call.contract_id, contract1);
  932. assert_eq!(leafs[1].data.call.contract_id, contract0);
  933. assert_eq!(leafs[2].data.call.contract_id, contract2);
  934. assert_eq!(sig_keys.len(), 3);
  935. assert_eq!(sig_keys[0].len(), 2);
  936. assert_eq!(sig_keys[1].len(), 0);
  937. assert_eq!(sig_keys[2].len(), 1);
  938. // Transaction with 1 root call, the second and third are the children of the first
  939. let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
  940. &[call0.clone(), call1.clone(), call2.clone()],
  941. &parse_tree("{0 : [1, 2], 1: [], 2: []}").unwrap(),
  942. )
  943. .unwrap();
  944. let leafs = tx_builder.calls.build_vec().unwrap();
  945. assert_eq!(leafs.len(), 3);
  946. assert_eq!(leafs[0].data.call.contract_id, contract1);
  947. assert_eq!(leafs[1].data.call.contract_id, contract2);
  948. assert_eq!(leafs[2].data.call.contract_id, contract0);
  949. assert_eq!(sig_keys.len(), 3);
  950. assert_eq!(sig_keys[0].len(), 2);
  951. assert_eq!(sig_keys[1].len(), 1);
  952. assert_eq!(sig_keys[2].len(), 0);
  953. // Transaction with 1 root call, the first is the child of the second, the second is the
  954. // child of the third
  955. let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
  956. &[call0, call1, call2],
  957. &parse_tree("{0 : [], 1: [0], 2: [1]}").unwrap(),
  958. )
  959. .unwrap();
  960. let leafs = tx_builder.calls.build_vec().unwrap();
  961. assert_eq!(leafs.len(), 3);
  962. assert_eq!(leafs[0].data.call.contract_id, contract0);
  963. assert_eq!(leafs[1].data.call.contract_id, contract1);
  964. assert_eq!(leafs[2].data.call.contract_id, contract2);
  965. assert_eq!(sig_keys.len(), 3);
  966. assert_eq!(sig_keys[0].len(), 0);
  967. assert_eq!(sig_keys[1].len(), 2);
  968. assert_eq!(sig_keys[2].len(), 1);
  969. }
  970. }