drk/
cli_util.rs

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