cli_util.rs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  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;
  26. use structopt_toml::clap::{App, Arg, Shell, SubCommand};
  27. use darkfi::{
  28. cli_desc,
  29. tx::{ContractCallLeaf, Transaction, TransactionBuilder},
  30. util::{encoding::base64, parse::decode_base10},
  31. zk::Proof,
  32. Error, Result,
  33. };
  34. use darkfi_money_contract::model::TokenId;
  35. use darkfi_sdk::{
  36. crypto::{keypair::Address, pasta_prelude::PrimeField, FuncId, SecretKey},
  37. dark_tree::DarkTree,
  38. pasta::pallas,
  39. ContractCallImport,
  40. };
  41. use darkfi_serial::deserialize_async;
  42. use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
  43. /// Auxiliary function to parse a base64 encoded transaction from stdin.
  44. pub async fn parse_tx_from_stdin() -> Result<Transaction> {
  45. let mut buf = String::new();
  46. stdin().read_to_string(&mut buf)?;
  47. match base64::decode(buf.trim()) {
  48. Some(bytes) => Ok(deserialize_async(&bytes).await?),
  49. None => Err(Error::ParseFailed("Failed to decode transaction")),
  50. }
  51. }
  52. /// Auxiliary function to parse a base64 encoded transaction from
  53. /// provided input or fallback to stdin if its empty.
  54. pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
  55. match input.len() {
  56. 0 => parse_tx_from_stdin().await,
  57. 1 => match base64::decode(input[0].trim()) {
  58. Some(bytes) => Ok(deserialize_async(&bytes).await?),
  59. None => Err(Error::ParseFailed("Failed to decode transaction")),
  60. },
  61. _ => Err(Error::ParseFailed("Multiline input provided")),
  62. }
  63. }
  64. /// Auxiliary function to parse base64 encoded contract calls from stdin.
  65. pub async fn parse_calls_from_stdin() -> Result<Vec<ContractCallImport>> {
  66. let lines = stdin().lines();
  67. let mut calls = vec![];
  68. for line in lines {
  69. let Some(line) = base64::decode(&line?) else {
  70. return Err(Error::ParseFailed("Failed to decode base64"))
  71. };
  72. calls.push(deserialize_async(&line).await?);
  73. }
  74. Ok(calls)
  75. }
  76. /// Auxiliary function to parse base64 encoded contract calls from
  77. /// provided input or fallback to stdin if its empty.
  78. pub async fn parse_calls_from_input(input: &[String]) -> Result<Vec<ContractCallImport>> {
  79. if input.is_empty() {
  80. return parse_calls_from_stdin().await
  81. }
  82. let mut calls = vec![];
  83. for line in input {
  84. let Some(line) = base64::decode(line) else {
  85. return Err(Error::ParseFailed("Failed to decode base64"))
  86. };
  87. calls.push(deserialize_async(&line).await?);
  88. }
  89. Ok(calls)
  90. }
  91. /// Auxiliary function to parse provided string into a values pair.
  92. pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
  93. let v: Vec<&str> = s.split(':').collect();
  94. if v.len() != 2 {
  95. return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
  96. }
  97. let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
  98. let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
  99. if val0.is_err() || val1.is_err() {
  100. return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
  101. }
  102. Ok((val0.unwrap(), val1.unwrap()))
  103. }
  104. /// Auxiliary function to parse provided string into a tokens pair.
  105. pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
  106. let v: Vec<&str> = s.split(':').collect();
  107. if v.len() != 2 {
  108. return Err(Error::ParseFailed(
  109. "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
  110. A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
  111. ))
  112. }
  113. let tok0 = drk.get_token(v[0].to_string()).await;
  114. let tok1 = drk.get_token(v[1].to_string()).await;
  115. if tok0.is_err() || tok1.is_err() {
  116. return Err(Error::ParseFailed(
  117. "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
  118. A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
  119. ))
  120. }
  121. Ok((tok0.unwrap(), tok1.unwrap()))
  122. }
  123. /// Fun police go away
  124. pub async fn kaching() {
  125. const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
  126. let cursor = Cursor::new(WALLET_MP3);
  127. let Ok(stream_handle) = OutputStreamBuilder::open_default_stream() else { return };
  128. let sink = Sink::connect_new(stream_handle.mixer());
  129. let Ok(source) = Decoder::new(cursor) else { return };
  130. sink.append(source);
  131. sink.detach();
  132. }
  133. /// Auxiliary function to generate provided shell completions.
  134. pub fn generate_completions(shell: &str) -> Result<String> {
  135. // Sub-commands
  136. // Interactive
  137. let interactive = SubCommand::with_name("interactive").about("Enter Drk interactive shell");
  138. // Kaching
  139. let kaching = SubCommand::with_name("kaching").about("Fun");
  140. // Ping
  141. let ping =
  142. SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
  143. // Completions
  144. let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
  145. let completions = SubCommand::with_name("completions")
  146. .about("Generate a SHELL completion script and print to stdout")
  147. .arg(shell_arg);
  148. // Wallet
  149. let initialize = SubCommand::with_name("initialize").about("Initialize wallet database");
  150. let keygen = SubCommand::with_name("keygen").about("Generate a new keypair in the wallet");
  151. let balance = SubCommand::with_name("balance").about("Query the wallet for known balances");
  152. let address = SubCommand::with_name("address").about("Get the default address in the wallet");
  153. let addresses =
  154. SubCommand::with_name("addresses").about("Print all the addresses in the wallet");
  155. let index = Arg::with_name("index").help("Identifier of the address");
  156. let default_address = SubCommand::with_name("default-address")
  157. .about("Set the default address in the wallet")
  158. .arg(index.clone());
  159. let secrets =
  160. SubCommand::with_name("secrets").about("Print all the secret keys from the wallet");
  161. let import_secrets = SubCommand::with_name("import-secrets")
  162. .about("Import secret keys from stdin into the wallet, separated by newlines");
  163. let tree = SubCommand::with_name("tree").about("Print the Merkle tree in the wallet");
  164. let coins = SubCommand::with_name("coins").about("Print all the coins in the wallet");
  165. let spend_hook = Arg::with_name("spend-hook").help("Optional contract spend hook to use");
  166. let user_data = Arg::with_name("user-data").help("Optional user data to use");
  167. let mining_config = SubCommand::with_name("mining-config")
  168. .about("Print a wallet address mining configuration")
  169. .args(&[index, spend_hook.clone(), user_data.clone()]);
  170. let wallet = SubCommand::with_name("wallet").about("Wallet operations").subcommands(vec![
  171. initialize,
  172. keygen,
  173. balance,
  174. address,
  175. addresses,
  176. default_address,
  177. secrets,
  178. import_secrets,
  179. tree,
  180. coins,
  181. mining_config,
  182. ]);
  183. // Spend
  184. let spend = SubCommand::with_name("spend")
  185. .about("Read a transaction from stdin and mark its input coins as spent");
  186. // Unspend
  187. let coin = Arg::with_name("coin").help("base64-encoded coin to mark as unspent");
  188. let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
  189. // Transfer
  190. let amount = Arg::with_name("amount").help("Amount to send");
  191. let token = Arg::with_name("token").help("Token ID to send");
  192. let recipient = Arg::with_name("recipient").help("Recipient address");
  193. let half_split = Arg::with_name("half-split")
  194. .long("half-split")
  195. .help("Split the output coin into two equal halves");
  196. let transfer = SubCommand::with_name("transfer").about("Create a payment transaction").args(&[
  197. amount.clone(),
  198. token.clone(),
  199. recipient.clone(),
  200. spend_hook.clone(),
  201. user_data.clone(),
  202. half_split,
  203. ]);
  204. // Otc
  205. let value_pair = Arg::with_name("value-pair")
  206. .short("v")
  207. .long("value-pair")
  208. .takes_value(true)
  209. .help("Value pair to send:recv (11.55:99.42)");
  210. let token_pair = Arg::with_name("token-pair")
  211. .short("t")
  212. .long("token-pair")
  213. .takes_value(true)
  214. .help("Token pair to send:recv (f00:b4r)");
  215. let init = SubCommand::with_name("init")
  216. .about("Initialize the first half of the atomic swap")
  217. .args(&[value_pair, token_pair]);
  218. let join =
  219. SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
  220. let inspect = SubCommand::with_name("inspect")
  221. .about("Inspect a swap half or the full swap tx from stdin");
  222. let sign = SubCommand::with_name("sign").about("Sign a swap transaction given from stdin");
  223. let otc = SubCommand::with_name("otc")
  224. .about("OTC atomic swap")
  225. .subcommands(vec![init, join, inspect, sign]);
  226. // DAO
  227. let proposer_limit = Arg::with_name("proposer-limit")
  228. .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
  229. let quorum = Arg::with_name("quorum")
  230. .help("Minimal threshold of participating total tokens needed for a proposal to pass");
  231. let early_exec_quorum = Arg::with_name("early-exec-quorum")
  232. .help("Minimal threshold of participating total tokens needed for a proposal to be considered as strongly supported, enabling early execution. Must be greater or equal to normal quorum.");
  233. let approval_ratio = Arg::with_name("approval-ratio")
  234. .help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
  235. let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
  236. let create = SubCommand::with_name("create").about("Create DAO parameters").args(&[
  237. proposer_limit,
  238. quorum,
  239. early_exec_quorum,
  240. approval_ratio,
  241. gov_token_id,
  242. ]);
  243. let view = SubCommand::with_name("view").about("View DAO data from stdin");
  244. let name = Arg::with_name("name").help("Name identifier for the DAO");
  245. let import = SubCommand::with_name("import")
  246. .about("Import DAO data from stdin")
  247. .args(slice::from_ref(&name));
  248. let remove = SubCommand::with_name("remove")
  249. .about("Remove a DAO and all its data")
  250. .args(slice::from_ref(&name));
  251. let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
  252. let list = SubCommand::with_name("list")
  253. .about("List imported DAOs (or info about a specific one)")
  254. .args(&[opt_name]);
  255. let balance = SubCommand::with_name("balance")
  256. .about("Show the balance of a DAO")
  257. .args(slice::from_ref(&name));
  258. let mint = SubCommand::with_name("mint")
  259. .about("Mint an imported DAO on-chain")
  260. .args(slice::from_ref(&name));
  261. let duration = Arg::with_name("duration").help("Duration of the proposal, in block windows");
  262. let propose_transfer = SubCommand::with_name("propose-transfer")
  263. .about("Create a transfer proposal for a DAO")
  264. .args(&[
  265. name.clone(),
  266. duration.clone(),
  267. amount,
  268. token,
  269. recipient,
  270. spend_hook.clone(),
  271. user_data.clone(),
  272. ]);
  273. let propose_generic = SubCommand::with_name("propose-generic")
  274. .about("Create a generic proposal for a DAO")
  275. .args(&[name.clone(), duration, user_data.clone()]);
  276. let proposals = SubCommand::with_name("proposals").about("List DAO proposals").arg(&name);
  277. let bulla = Arg::with_name("bulla").help("Bulla identifier for the proposal");
  278. let export = Arg::with_name("export").help("Encrypt the proposal and encode it to base64");
  279. let mint_proposal = Arg::with_name("mint-proposal").help("Create the proposal transaction");
  280. let proposal = SubCommand::with_name("proposal").about("View a DAO proposal data").args(&[
  281. bulla.clone(),
  282. export,
  283. mint_proposal,
  284. ]);
  285. let proposal_import = SubCommand::with_name("proposal-import")
  286. .about("Import a base64 encoded and encrypted proposal from stdin");
  287. let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
  288. let vote_weight =
  289. Arg::with_name("vote-weight").help("Optional vote weight (amount of governance tokens)");
  290. let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&[
  291. bulla.clone(),
  292. vote,
  293. vote_weight,
  294. ]);
  295. let early = Arg::with_name("early").long("early").help("Execute the proposal early");
  296. let exec = SubCommand::with_name("exec").about("Execute a DAO proposal").args(&[bulla, early]);
  297. let spend_hook_cmd = SubCommand::with_name("spend-hook")
  298. .about("Print the DAO contract base64-encoded spend hook");
  299. let mining_config =
  300. SubCommand::with_name("mining-config").about("Print a DAO mining configuration").arg(name);
  301. let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
  302. create,
  303. view,
  304. import,
  305. remove,
  306. list,
  307. balance,
  308. mint,
  309. propose_transfer,
  310. propose_generic,
  311. proposals,
  312. proposal,
  313. proposal_import,
  314. vote,
  315. exec,
  316. spend_hook_cmd,
  317. mining_config,
  318. ]);
  319. // AttachFee
  320. let attach_fee = SubCommand::with_name("attach-fee")
  321. .about("Attach the fee call to a transaction given from stdin");
  322. // TxFromCalls
  323. let calls_map =
  324. Arg::with_name("calls-map").help("Optional parent/children dependency map for the calls");
  325. let tx_from_calls = SubCommand::with_name("tx-from-calls")
  326. .about(
  327. "Create a transaction from newline-separated calls from stdin and attach the fee call",
  328. )
  329. .args(&[calls_map]);
  330. // Inspect
  331. let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
  332. // Broadcast
  333. let broadcast =
  334. SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
  335. // Scan
  336. let reset = Arg::with_name("reset")
  337. .long("reset")
  338. .help("Reset wallet state to provided block height and start scanning");
  339. let scan = SubCommand::with_name("scan")
  340. .about("Scan the blockchain and parse relevant transactions")
  341. .args(&[reset]);
  342. // Explorer
  343. let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
  344. let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base64");
  345. let fetch_tx = SubCommand::with_name("fetch-tx")
  346. .about("Fetch a blockchain transaction by hash")
  347. .args(&[tx_hash, encode]);
  348. let simulate_tx =
  349. SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
  350. let tx_hash = Arg::with_name("tx-hash").help("Fetch specific history record (optional)");
  351. let encode = Arg::with_name("encode")
  352. .long("encode")
  353. .help("Encode specific history record transaction to base64");
  354. let txs_history = SubCommand::with_name("txs-history")
  355. .about("Fetch broadcasted transactions history")
  356. .args(&[tx_hash, encode]);
  357. let clear_reverted =
  358. SubCommand::with_name("clear-reverted").about("Remove reverted transactions from history");
  359. let height = Arg::with_name("height").help("Fetch specific height record (optional)");
  360. let scanned_blocks = SubCommand::with_name("scanned-blocks")
  361. .about("Fetch scanned blocks records")
  362. .args(&[height]);
  363. let mining_config = SubCommand::with_name("mining-config")
  364. .about("Read a mining configuration from stdin and display its parts");
  365. let explorer =
  366. SubCommand::with_name("explorer").about("Explorer related subcommands").subcommands(vec![
  367. fetch_tx,
  368. simulate_tx,
  369. txs_history,
  370. clear_reverted,
  371. scanned_blocks,
  372. mining_config,
  373. ]);
  374. // Alias
  375. let alias = Arg::with_name("alias").help("Token alias");
  376. let token = Arg::with_name("token").help("Token to create alias for");
  377. let add = SubCommand::with_name("add").about("Create a Token alias").args(&[alias, token]);
  378. let alias = Arg::with_name("alias")
  379. .short("a")
  380. .long("alias")
  381. .takes_value(true)
  382. .help("Token alias to search for");
  383. let token = Arg::with_name("token")
  384. .short("t")
  385. .long("token")
  386. .takes_value(true)
  387. .help("Token to search alias for");
  388. let show = SubCommand::with_name("show")
  389. .about(
  390. "Print alias info of optional arguments. \
  391. If no argument is provided, list all the aliases in the wallet.",
  392. )
  393. .args(&[alias, token]);
  394. let alias = Arg::with_name("alias").help("Token alias to remove");
  395. let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
  396. let alias = SubCommand::with_name("alias")
  397. .about("Manage Token aliases")
  398. .subcommands(vec![add, show, remove]);
  399. // Token
  400. let secret_key = Arg::with_name("secret-key").help("Mint authority secret key");
  401. let token_blind = Arg::with_name("token-blind").help("Mint authority token blind");
  402. let import = SubCommand::with_name("import")
  403. .about("Import a mint authority")
  404. .args(&[secret_key, token_blind]);
  405. let generate_mint =
  406. SubCommand::with_name("generate-mint").about("Generate a new mint authority");
  407. let list =
  408. SubCommand::with_name("list").about("List token IDs with available mint authorities");
  409. let token = Arg::with_name("token").help("Token ID to mint");
  410. let amount = Arg::with_name("amount").help("Amount to mint");
  411. let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
  412. let mint = SubCommand::with_name("mint")
  413. .about("Mint tokens")
  414. .args(&[token, amount, recipient, spend_hook, user_data]);
  415. let token = Arg::with_name("token").help("Token ID to freeze");
  416. let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
  417. let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
  418. import,
  419. generate_mint,
  420. list,
  421. mint,
  422. freeze,
  423. ]);
  424. // Contract
  425. let generate_deploy =
  426. SubCommand::with_name("generate-deploy").about("Generate a new deploy authority");
  427. let contract_id = Arg::with_name("contract-id").help("Contract ID (optional)");
  428. let list = SubCommand::with_name("list")
  429. .about("List deploy authorities in the wallet (or a specific one)")
  430. .args(&[contract_id]);
  431. let tx_hash = Arg::with_name("tx-hash").help("Record transaction hash");
  432. let export_data = SubCommand::with_name("export-data")
  433. .about("Export a contract history record wasm bincode and deployment instruction, encoded to base64")
  434. .args(&[tx_hash]);
  435. let deploy_auth = Arg::with_name("deploy-auth").help("Contract ID (deploy authority)");
  436. let wasm_path = Arg::with_name("wasm-path").help("Path to contract wasm bincode");
  437. let deploy_ix =
  438. Arg::with_name("deploy-ix").help("Optional path to serialized deploy instruction");
  439. let deploy = SubCommand::with_name("deploy").about("Deploy a smart contract").args(&[
  440. deploy_auth.clone(),
  441. wasm_path,
  442. deploy_ix,
  443. ]);
  444. let lock = SubCommand::with_name("lock").about("Lock a smart contract").args(&[deploy_auth]);
  445. let contract = SubCommand::with_name("contract")
  446. .about("Contract functionalities")
  447. .subcommands(vec![generate_deploy, list, export_data, deploy, lock]);
  448. // Main arguments
  449. let config = Arg::with_name("config")
  450. .short("c")
  451. .long("config")
  452. .takes_value(true)
  453. .help("Configuration file to use");
  454. let network = Arg::with_name("network")
  455. .long("network")
  456. .takes_value(true)
  457. .help("Blockchain network to use");
  458. let command = vec![
  459. interactive,
  460. kaching,
  461. ping,
  462. completions,
  463. wallet,
  464. spend,
  465. unspend,
  466. transfer,
  467. otc,
  468. attach_fee,
  469. tx_from_calls,
  470. inspect,
  471. broadcast,
  472. dao,
  473. scan,
  474. explorer,
  475. alias,
  476. token,
  477. contract,
  478. ];
  479. let fun = Arg::with_name("fun")
  480. .short("f")
  481. .long("fun")
  482. .help("Flag indicating whether you want some fun in your life");
  483. let log = Arg::with_name("log")
  484. .short("l")
  485. .long("log")
  486. .takes_value(true)
  487. .help("Set log file to ouput into");
  488. let verbose = Arg::with_name("verbose")
  489. .short("v")
  490. .multiple(true)
  491. .help("Increase verbosity (-vvv supported)");
  492. let mut app = App::new("drk")
  493. .about(cli_desc!())
  494. .args(&[config, network, fun, log, verbose])
  495. .subcommands(command);
  496. let shell = match Shell::from_str(shell) {
  497. Ok(s) => s,
  498. Err(e) => return Err(Error::Custom(e)),
  499. };
  500. let mut buf = vec![];
  501. app.gen_completions_to("./drk", shell, &mut buf);
  502. Ok(String::from_utf8(buf)?)
  503. }
  504. /// Auxiliary function to print provided string buffer.
  505. pub fn print_output(buf: &[String]) {
  506. for line in buf {
  507. println!("{line}");
  508. }
  509. }
  510. /// Auxiliary function to print or insert provided messages to given
  511. /// buffer reference. If a channel sender is provided, the messages
  512. /// are send to that instead.
  513. pub async fn append_or_print(
  514. buf: &mut Vec<String>,
  515. sender: Option<&Sender<Vec<String>>>,
  516. print: &bool,
  517. messages: Vec<String>,
  518. ) {
  519. // Send the messages to the channel, if provided
  520. if let Some(sender) = sender {
  521. if let Err(e) = sender.send(messages).await {
  522. let err_msg = format!("[append_or_print] Sending messages to channel failed: {e}");
  523. if *print {
  524. println!("{err_msg}");
  525. } else {
  526. buf.push(err_msg);
  527. }
  528. }
  529. return
  530. }
  531. // Print the messages
  532. if *print {
  533. for msg in messages {
  534. println!("{msg}");
  535. }
  536. return
  537. }
  538. // Insert the messages in the buffer
  539. for msg in messages {
  540. buf.push(msg);
  541. }
  542. }
  543. /// Auxiliary function to parse a base64 encoded mining configuration
  544. /// from stdin.
  545. pub async fn parse_mining_config_from_stdin(
  546. ) -> Result<(String, String, Option<String>, Option<String>)> {
  547. let mut buf = String::new();
  548. stdin().read_to_string(&mut buf)?;
  549. let config = buf.trim();
  550. let (recipient, spend_hook, user_data) = match base64::decode(config) {
  551. Some(bytes) => deserialize_async(&bytes).await?,
  552. None => return Err(Error::ParseFailed("Failed to decode mining configuration")),
  553. };
  554. Ok((config.to_string(), recipient, spend_hook, user_data))
  555. }
  556. /// Auxiliary function to parse a base64 encoded mining configuration
  557. /// from provided input or fallback to stdin if its empty.
  558. pub async fn parse_mining_config_from_input(
  559. input: &[String],
  560. ) -> Result<(String, String, Option<String>, Option<String>)> {
  561. match input.len() {
  562. 0 => parse_mining_config_from_stdin().await,
  563. 1 => {
  564. let config = input[0].trim();
  565. let (recipient, spend_hook, user_data) = match base64::decode(config) {
  566. Some(bytes) => deserialize_async(&bytes).await?,
  567. None => return Err(Error::ParseFailed("Failed to decode mining configuration")),
  568. };
  569. Ok((config.to_string(), recipient, spend_hook, user_data))
  570. }
  571. _ => Err(Error::ParseFailed("Multiline input provided")),
  572. }
  573. }
  574. /// Auxiliary function to display the parts of a mining configuration.
  575. pub fn display_mining_config(
  576. config: &str,
  577. recipient_str: &str,
  578. spend_hook: &Option<String>,
  579. user_data: &Option<String>,
  580. output: &mut Vec<String>,
  581. ) {
  582. output.push(format!("DarkFi mining configuration address: {config}"));
  583. match Address::from_str(recipient_str) {
  584. Ok(recipient) => {
  585. output.push(format!("Recipient: {recipient_str}"));
  586. output.push(format!("Public key: {}", recipient.public_key()));
  587. output.push(format!("Network: {:?}", recipient.network()));
  588. }
  589. Err(e) => output.push(format!("Recipient: Invalid ({e})")),
  590. }
  591. let spend_hook = match spend_hook {
  592. Some(spend_hook_str) => match FuncId::from_str(spend_hook_str) {
  593. Ok(_) => String::from(spend_hook_str),
  594. Err(e) => format!("Invalid ({e})"),
  595. },
  596. None => String::from("-"),
  597. };
  598. output.push(format!("Spend hook: {spend_hook}"));
  599. let user_data = match user_data {
  600. Some(user_data_str) => match bs58::decode(&user_data_str).into_vec() {
  601. Ok(bytes) => match bytes.try_into() {
  602. Ok(bytes) => {
  603. if pallas::Base::from_repr(bytes).is_some().into() {
  604. String::from(user_data_str)
  605. } else {
  606. String::from("Invalid")
  607. }
  608. }
  609. Err(e) => format!("Invalid ({e:?})"),
  610. },
  611. Err(e) => format!("Invalid ({e})"),
  612. },
  613. None => String::from("-"),
  614. };
  615. output.push(format!("User data: {user_data}"));
  616. }
  617. /// Cast `ContractCallImport` to `ContractCallLeaf`
  618. fn to_leaf(call: &ContractCallImport) -> ContractCallLeaf {
  619. ContractCallLeaf {
  620. call: call.call().clone(),
  621. proofs: call.proofs().iter().map(|p| Proof::new(p.clone())).collect(),
  622. }
  623. }
  624. /// Recursively build subtree for a DarkTree
  625. fn build_subtree(
  626. idx: usize,
  627. calls: &[ContractCallImport],
  628. children_map: &HashMap<usize, &Vec<usize>>,
  629. ) -> DarkTree<ContractCallLeaf> {
  630. let children_idx = children_map.get(&idx).map(|v| v.as_slice()).unwrap_or(&[]);
  631. let children: Vec<DarkTree<ContractCallLeaf>> =
  632. children_idx.iter().map(|&i| build_subtree(i, calls, children_map)).collect();
  633. DarkTree::new(to_leaf(&calls[idx]), children, None, None)
  634. }
  635. /// Build a `Transaction` given a slice of calls and their mapping
  636. pub fn tx_from_calls_mapped(
  637. calls: &[ContractCallImport],
  638. map: &[(usize, Vec<usize>)],
  639. ) -> Result<(TransactionBuilder, Vec<SecretKey>)> {
  640. assert_eq!(calls.len(), map.len());
  641. let signature_secrets: Vec<SecretKey> =
  642. calls.iter().flat_map(|c| c.secrets().to_vec()).collect();
  643. let children_map: HashMap<usize, &Vec<usize>> = map.iter().map(|(k, v)| (*k, v)).collect();
  644. let (root_idx, root_children_idx) = &map[0];
  645. let root_children: Vec<DarkTree<ContractCallLeaf>> =
  646. root_children_idx.iter().map(|&i| build_subtree(i, calls, &children_map)).collect();
  647. let tx_builder = TransactionBuilder::new(to_leaf(&calls[*root_idx]), root_children)?;
  648. Ok((tx_builder, signature_secrets))
  649. }
  650. /// Auxiliary function to parse a contract call mapping.
  651. ///
  652. /// The mapping is in the format of `{0: [1,2], 1: [], 2:[3], 3:[]}`.
  653. /// It supports nesting and this kind of logic as expected.
  654. ///
  655. /// Errors out if there are non-unique keys or cyclic references.
  656. pub fn parse_tree(input: &str) -> std::result::Result<Vec<(usize, Vec<usize>)>, String> {
  657. let s = input
  658. .trim()
  659. .strip_prefix('{')
  660. .and_then(|s| s.strip_suffix('}'))
  661. .ok_or("expected {}")?
  662. .trim();
  663. let mut entries = vec![];
  664. let mut seen_keys = HashSet::new();
  665. if s.is_empty() {
  666. return Ok(entries)
  667. }
  668. let mut rest = s;
  669. while !rest.is_empty() {
  670. // Parse key
  671. let (key_str, after_key) = rest.split_once(':').ok_or("expected ':'")?;
  672. let key: usize = key_str.trim().parse().map_err(|_| "invalid key")?;
  673. if !seen_keys.insert(key) {
  674. return Err(format!("duplicate key: {}", key));
  675. }
  676. // Parse array
  677. let after_key = after_key.trim();
  678. let arr_start = after_key.strip_prefix('[').ok_or("expected '['")?;
  679. let (arr_content, after_arr) = arr_start.split_once(']').ok_or("expected ']'")?;
  680. let children: Vec<usize> = arr_content
  681. .split(',')
  682. .map(|s| s.trim())
  683. .filter(|s| !s.is_empty())
  684. .map(|s| s.parse().map_err(|_| "invalid child"))
  685. .collect::<std::result::Result<_, _>>()?;
  686. entries.push((key, children));
  687. // Move to next entry
  688. rest = after_arr.trim().strip_prefix(',').unwrap_or(after_arr).trim();
  689. }
  690. check_cycles(&entries)?;
  691. Ok(entries)
  692. }
  693. fn check_cycles(entries: &[(usize, Vec<usize>)]) -> std::result::Result<(), String> {
  694. let graph: HashMap<usize, &Vec<usize>> = entries.iter().map(|(k, v)| (*k, v)).collect();
  695. let mut visited = HashSet::new();
  696. let mut path = Vec::new();
  697. fn dfs(
  698. node: usize,
  699. graph: &HashMap<usize, &Vec<usize>>,
  700. visited: &mut HashSet<usize>,
  701. path: &mut Vec<usize>,
  702. ) -> std::result::Result<(), String> {
  703. if let Some(pos) = path.iter().position(|&n| n == node) {
  704. let cycle: Vec<_> = path[pos..].iter().chain(&[node]).map(|n| n.to_string()).collect();
  705. return Err(format!("cycle detected: {}", cycle.join(" -> ")));
  706. }
  707. if visited.contains(&node) {
  708. return Ok(());
  709. }
  710. path.push(node);
  711. if let Some(children) = graph.get(&node) {
  712. for &child in *children {
  713. dfs(child, graph, visited, path)?;
  714. }
  715. }
  716. path.pop();
  717. visited.insert(node);
  718. Ok(())
  719. }
  720. for &(key, _) in entries {
  721. dfs(key, &graph, &mut visited, &mut path)?;
  722. }
  723. Ok(())
  724. }
  725. #[cfg(test)]
  726. mod tests {
  727. use super::*;
  728. #[test]
  729. fn test_parse_tree() {
  730. // Valid inputs
  731. assert_eq!(parse_tree("{}").unwrap(), vec![]);
  732. assert_eq!(parse_tree("{ }").unwrap(), vec![]);
  733. assert_eq!(parse_tree("{ 0: [] }").unwrap(), vec![(0, vec![])]);
  734. assert_eq!(parse_tree("{ 0: [1, 2, 3] }").unwrap(), vec![(0, vec![1, 2, 3])]);
  735. assert_eq!(parse_tree("{0:[],1:[2]}").unwrap(), vec![(0, vec![]), (1, vec![2])]);
  736. assert_eq!(parse_tree("{ 0: [], 1: [], }").unwrap(), vec![(0, vec![]), (1, vec![])]);
  737. assert_eq!(parse_tree("{ 0: [1, 2,] }").unwrap(), vec![(0, vec![1, 2])]);
  738. assert_eq!(
  739. parse_tree("{ 0: [], 1: [2, 3], 2: [], 3: [4], 4: [] }").unwrap(),
  740. vec![(0, vec![]), (1, vec![2, 3]), (2, vec![]), (3, vec![4]), (4, vec![])]
  741. );
  742. assert_eq!(
  743. parse_tree("{ 0 : [ ] , 1 : [ 2 , 3 ] }").unwrap(),
  744. vec![(0, vec![]), (1, vec![2, 3])]
  745. );
  746. assert_eq!(
  747. parse_tree("{ 999: [1000, 1001], 1000: [], 1001: [] }").unwrap(),
  748. vec![(999, vec![1000, 1001]), (1000, vec![]), (1001, vec![])]
  749. );
  750. // Order preservation
  751. let keys: Vec<usize> =
  752. parse_tree("{ 5: [], 2: [], 9: [], 0: [] }").unwrap().iter().map(|(k, _)| *k).collect();
  753. assert_eq!(keys, vec![5, 2, 9, 0]);
  754. // Valid DAG (not a cycle)
  755. assert!(parse_tree("{ 0: [1, 2], 1: [3], 2: [3], 3: [] }").is_ok());
  756. // Syntax errors
  757. assert!(parse_tree("0: [] }").is_err());
  758. assert!(parse_tree("{ 0: []").is_err());
  759. assert!(parse_tree("{ 0 [] }").is_err());
  760. assert!(parse_tree("{ 0: ] }").is_err());
  761. assert!(parse_tree("{ 0: [1, 2 }").is_err());
  762. assert!(parse_tree("{ abc: [] }").is_err());
  763. assert!(parse_tree("{ 0: [abc] }").is_err());
  764. assert!(parse_tree("{ -1: [] }").is_err());
  765. // Duplicate keys
  766. assert!(parse_tree("{ 0: [], 0: [1] }").unwrap_err().contains("duplicate key: 0"));
  767. assert!(parse_tree("{ 0: [], 1: [], 2: [], 1: [] }")
  768. .unwrap_err()
  769. .contains("duplicate key: 1"));
  770. // Cycle detection
  771. let err = parse_tree("{ 0: [0] }").unwrap_err();
  772. assert!(err.contains("cycle detected") && err.contains("0 -> 0"));
  773. let err = parse_tree("{ 0: [1], 1: [0] }").unwrap_err();
  774. assert!(err.contains("cycle detected"));
  775. let err = parse_tree("{ 0: [1], 1: [2], 2: [3], 3: [0] }").unwrap_err();
  776. assert!(err.contains("cycle detected") && err.contains("0 -> 1 -> 2 -> 3 -> 0"));
  777. let err = parse_tree("{ 0: [1], 1: [2], 2: [3], 3: [2] }").unwrap_err();
  778. assert!(err.contains("cycle detected") && err.contains("2 -> 3 -> 2"));
  779. }
  780. }