forc_tx/
lib.rs

1//! A simple tool for constructing transactions from the command line.
2
3use clap::{Args, Parser};
4use devault::Devault;
5use forc_util::tx_utils::Salt;
6use fuel_tx::{
7    output,
8    policies::{Policies, PolicyType},
9    Buildable, Chargeable, ConsensusParameters,
10};
11use fuels_core::types::transaction::TxPolicies;
12use serde::{Deserialize, Serialize};
13use std::path::PathBuf;
14use thiserror::Error;
15
16forc_util::cli_examples! {
17    {
18        // This parser has a custom parser
19        super::Command::try_parse_from_args
20    } {
21    [ Script example => r#"forc tx script --bytecode "{path}/out/debug/name.bin" --data "{path}/data.bin" \
22        --receipts-root 0x2222222222222222222222222222222222222222222222222222222222222222"# ]
23    [ Multiple inputs => r#"forc tx create --bytecode "{name}/out/debug/name.bin"
24        --storage-slots "{path}/out/debug/name-storage_slots.json"
25        --script-gas-limit 100 \
26        --gas-price 0 \
27        --maturity 0 \
28        --witness ADFD \
29        --witness DFDA \
30        input coin \
31            --utxo-id 0 \
32            --output-ix 0 \
33            --owner 0x0000000000000000000000000000000000000000000000000000000000000000 \
34            --amount 100 \
35            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
36            --tx-ptr 89ACBDEFBDEF \
37            --witness-ix 0 \
38            --maturity 0 \
39        input contract \
40            --utxo-id 1 \
41            --output-ix 1 \
42            --balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
43            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
44            --tx-ptr 89ACBDEFBDEF \
45            --contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
46        output coin \
47            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
48            --amount 100 \
49            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
50        output contract \
51            --input-ix 1 \
52            --balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
53            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
54        output change \
55            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
56            --amount 100 \
57            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
58        output variable \
59            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
60            --amount 100 \
61            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
62        output contract-created \
63            --contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
64            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000
65        "#
66    ]
67    [ An example constructing a create transaction => r#"forc tx create \
68        --bytecode {path}/out/debug/name.bin \
69        --storage-slots {path}/out/debug/name-storage_slots.json \
70        --script-gas-limit 100 \
71        --gas-price 0 \
72        --maturity 0 \
73        --witness ADFD \
74        --witness DFDA \
75        input coin \
76            --utxo-id 0 \
77            --output-ix 0 \
78            --owner 0x0000000000000000000000000000000000000000000000000000000000000000 \
79            --amount 100 \
80            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
81            --tx-ptr 89ACBDEFBDEF \
82            --witness-ix 0 \
83            --maturity 0 \
84        input contract \
85            --utxo-id 1 \
86            --output-ix 1 \
87            --balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
88            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
89            --tx-ptr 89ACBDEFBDEF \
90            --contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
91        input message \
92            --sender 0x1111111111111111111111111111111111111111111111111111111111111111 \
93            --recipient 0x2222222222222222222222222222222222222222222222222222222222222222 \
94            --amount 1 \
95            --nonce 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB \
96            --msg-data {path}/message.dat \
97            --predicate {path}/my-predicate2.bin \
98            --predicate-data {path}/my-predicate2.dat \
99        output coin \
100            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
101            --amount 100 \
102            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
103        output contract \
104            --input-ix 1 \
105            --balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
106            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
107        output change \
108            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
109            --amount 100 \
110            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
111        output variable \
112            --to 0x2222222222222222222222222222222222222222222222222222222222222222 \
113            --amount 100 \
114            --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
115        output contract-created \
116            --contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
117            --state-root 0x0000000000000000000000000000000000000000000000000000000000000000"#
118    ]
119    }
120}
121
122/// The top-level `forc tx` command.
123#[derive(Debug, Parser, Deserialize, Serialize)]
124#[clap(about, version, after_help = help())]
125pub struct Command {
126    #[clap(long, short = 'o')]
127    pub output_path: Option<PathBuf>,
128    #[clap(subcommand)]
129    pub tx: Transaction,
130}
131
132/// Construct a transaction.
133#[derive(Debug, Parser, Deserialize, Serialize)]
134#[clap(name = "transaction")]
135pub enum Transaction {
136    Create(Create),
137    Script(Script),
138}
139
140/// Construct a `Create` transaction for deploying a contract.
141#[derive(Debug, Parser, Deserialize, Serialize)]
142pub struct Create {
143    #[clap(flatten)]
144    pub gas: Gas,
145    #[clap(flatten)]
146    pub maturity: Maturity,
147    #[clap(flatten)]
148    pub salt: Salt,
149    /// Path to the contract bytecode.
150    #[clap(long)]
151    pub bytecode: PathBuf,
152    /// Witness index of contract bytecode to create.
153    #[clap(long, default_value_t = 0)]
154    pub bytecode_witness_index: u16,
155    /// Path to a JSON file with a list of storage slots to initialize (key, value).
156    #[clap(long)]
157    pub storage_slots: PathBuf,
158    /// An arbitrary length string of hex-encoded bytes (e.g. "1F2E3D4C5B6A")
159    ///
160    /// Can be specified multiple times.
161    #[clap(long = "witness", num_args(0..255))]
162    pub witnesses: Vec<String>,
163    // Inputs and outputs must follow all other arguments and are parsed separately.
164    #[clap(skip)]
165    pub inputs: Vec<Input>,
166    // Inputs and outputs must follow all other arguments and are parsed separately.
167    #[clap(skip)]
168    pub outputs: Vec<Output>,
169}
170
171/// Construct a `Script` transaction for running a script.
172#[derive(Debug, Parser, Deserialize, Serialize)]
173pub struct Script {
174    #[clap(flatten)]
175    pub gas: Gas,
176    #[clap(flatten)]
177    pub maturity: Maturity,
178    /// Script to execute.
179    #[clap(long)]
180    pub bytecode: PathBuf,
181    /// Script input data (parameters). Specified file is loaded as raw bytes.
182    #[clap(long)]
183    pub data: PathBuf,
184    /// Merkle root of receipts.
185    #[clap(long)]
186    pub receipts_root: fuel_tx::Bytes32,
187    /// An arbitrary length string of hex-encoded bytes (e.g. "1F2E3D4C5B6A")
188    ///
189    /// Can be specified multiple times.
190    #[clap(long = "witness", num_args(0..=255))]
191    pub witnesses: Vec<String>,
192    // Inputs and outputs must follow all other arguments and are parsed separately.
193    #[clap(skip)]
194    pub inputs: Vec<Input>,
195    // Inputs and outputs must follow all other arguments and are parsed separately.
196    #[clap(skip)]
197    pub outputs: Vec<Output>,
198}
199
200/// Flag set for specifying gas price and limit.
201#[derive(Debug, Devault, Clone, Parser, Deserialize, Serialize)]
202pub struct Gas {
203    /// Gas price for the transaction.
204    #[clap(long = "gas-price")]
205    pub price: Option<u64>,
206    /// Gas limit for the transaction.
207    #[clap(long = "script-gas-limit")]
208    pub script_gas_limit: Option<u64>,
209    /// Max fee for the transaction.
210    #[clap(long)]
211    pub max_fee: Option<u64>,
212    /// The tip for the transaction.
213    #[clap(long)]
214    pub tip: Option<u64>,
215}
216
217/// Block until which tx cannot be included.
218#[derive(Debug, Args, Default, Deserialize, Serialize)]
219pub struct Maturity {
220    /// Block height until which tx cannot be included.
221    #[clap(long = "maturity", default_value_t = 0)]
222    pub maturity: u32,
223}
224
225/// Transaction input.
226#[derive(Debug, Parser, Deserialize, Serialize)]
227#[clap(name = "input")]
228pub enum Input {
229    Coin(InputCoin),
230    Contract(InputContract),
231    Message(InputMessage),
232}
233
234#[derive(Debug, Parser, Deserialize, Serialize)]
235pub struct InputCoin {
236    /// Hash of the unspent transaction.
237    #[clap(long)]
238    pub utxo_id: fuel_tx::UtxoId,
239    /// Index of transaction output.
240    #[clap(long)]
241    pub output_ix: u8,
242    /// Owning address or predicate root.
243    #[clap(long)]
244    pub owner: fuel_tx::Address,
245    /// Amount of coins.
246    #[clap(long)]
247    pub amount: u64,
248    /// Asset ID of the coins.
249    #[clap(long)]
250    pub asset_id: fuel_tx::AssetId,
251    /// Points to the TX whose output is being spent. Includes block height, tx index.
252    #[clap(long)]
253    pub tx_ptr: fuel_tx::TxPointer,
254    /// Index of witness that authorizes spending the coin.
255    #[clap(long)]
256    pub witness_ix: Option<u16>,
257    /// UTXO being spent must have been created at least this many blocks ago.
258    #[clap(long)]
259    pub maturity: u32,
260    /// Gas used by predicates.
261    #[clap(long, default_value_t = 0)]
262    pub predicate_gas_used: u64,
263    #[clap(flatten)]
264    pub predicate: Predicate,
265}
266
267#[derive(Debug, Parser, Deserialize, Serialize)]
268pub struct InputContract {
269    /// Hash of the unspent transaction.
270    #[clap(long)]
271    pub utxo_id: fuel_tx::UtxoId,
272    /// Index of transaction output.
273    #[clap(long)]
274    pub output_ix: u8,
275    /// Root of the amount of coins owned by the contract before transaction execution.
276    #[clap(long)]
277    pub balance_root: fuel_tx::Bytes32,
278    /// State root of contract before transaction execution.
279    #[clap(long)]
280    pub state_root: fuel_tx::Bytes32,
281    /// Points to the TX whose output is being spent. Includes block height, tx index.
282    #[clap(long)]
283    pub tx_ptr: fuel_tx::TxPointer,
284    /// The ID of the contract.
285    #[clap(long)]
286    pub contract_id: fuel_tx::ContractId,
287}
288
289#[derive(Debug, Parser, Deserialize, Serialize)]
290pub struct InputMessage {
291    /// The address of the message sender.
292    #[clap(long)]
293    pub sender: fuel_tx::Address,
294    /// The address or predicate root of the message recipient.
295    #[clap(long)]
296    pub recipient: fuel_tx::Address,
297    /// Amount of base asset coins sent with message.
298    #[clap(long)]
299    pub amount: u64,
300    /// The message nonce.
301    #[clap(long)]
302    pub nonce: fuel_types::Nonce,
303    /// The message data.
304    #[clap(long)]
305    pub msg_data: PathBuf,
306    /// Index of witness that authorizes the message.
307    #[clap(long)]
308    pub witness_ix: Option<u16>,
309    /// Gas used by predicates.
310    #[clap(long, default_value_t = 0)]
311    pub predicate_gas_used: u64,
312    #[clap(flatten)]
313    pub predicate: Predicate,
314}
315
316/// Grouped arguments related to an input's predicate.
317#[derive(Debug, Parser, Deserialize, Serialize)]
318pub struct Predicate {
319    /// The predicate bytecode.
320    #[clap(long = "predicate")]
321    pub bytecode: Option<PathBuf>,
322    /// The predicate's input data (parameters). Specified file is loaded as raw bytes.
323    #[clap(long = "predicate-data")]
324    pub data: Option<PathBuf>,
325}
326
327/// The location of the transaction in the block.
328#[derive(Debug, Parser, Deserialize, Serialize)]
329pub struct TxPointer {
330    /// The transaction block height.
331    #[clap(long = "tx-ptr-block-height")]
332    pub block_height: u32,
333    /// Transaction index.
334    #[clap(long = "tx-ptr-ix")]
335    pub tx_ix: u16,
336}
337
338/// Transaction output.
339#[derive(Debug, Parser, Deserialize, Serialize)]
340#[clap(name = "output")]
341pub enum Output {
342    Coin(OutputCoin),
343    Contract(OutputContract),
344    Change(OutputChange),
345    Variable(OutputVariable),
346    ContractCreated(OutputContractCreated),
347}
348
349#[derive(Debug, Parser, Deserialize, Serialize)]
350pub struct OutputCoin {
351    /// Hash of the unspent transaction.
352    #[clap(long)]
353    pub to: fuel_tx::Address,
354    /// Amount of coins.
355    #[clap(long)]
356    pub amount: fuel_tx::Word,
357    /// Asset ID of the coins.
358    #[clap(long)]
359    pub asset_id: fuel_tx::AssetId,
360}
361
362#[derive(Debug, Parser, Deserialize, Serialize)]
363pub struct OutputContract {
364    /// Index of input contract.
365    #[clap(long)]
366    pub input_ix: u16,
367    /// Root of amount of coins owned by contract after transaction execution.
368    #[clap(long)]
369    pub balance_root: fuel_tx::Bytes32,
370    /// State root of contract after transaction execution.
371    #[clap(long)]
372    pub state_root: fuel_tx::Bytes32,
373}
374
375#[derive(Debug, Parser, Deserialize, Serialize)]
376pub struct OutputChange {
377    /// Receiving address or predicate root.
378    #[clap(long)]
379    pub to: fuel_tx::Address,
380    /// Amount of coins to send.
381    #[clap(long)]
382    pub amount: fuel_tx::Word,
383    /// Asset ID of coins.
384    #[clap(long)]
385    pub asset_id: fuel_tx::AssetId,
386}
387
388#[derive(Debug, Parser, Deserialize, Serialize)]
389pub struct OutputVariable {
390    /// Receiving address or predicate root.
391    #[clap(long)]
392    pub to: fuel_tx::Address,
393    /// Amount of coins to send.
394    #[clap(long)]
395    pub amount: fuel_tx::Word,
396    /// Asset ID of coins.
397    #[clap(long)]
398    pub asset_id: fuel_tx::AssetId,
399}
400
401#[derive(Debug, Parser, Deserialize, Serialize)]
402pub struct OutputContractCreated {
403    /// Contract ID
404    #[clap(long)]
405    pub contract_id: fuel_tx::ContractId,
406    /// Initial state root of contract.
407    #[clap(long)]
408    pub state_root: fuel_tx::Bytes32,
409}
410
411/// Errors that can occur while parsing the `Command`.
412#[derive(Debug, Error)]
413pub enum ParseError {
414    #[error("Failed to parse the command")]
415    Command {
416        #[source]
417        err: clap::Error,
418    },
419    #[error("Failed to parse transaction `input`")]
420    Input {
421        #[source]
422        err: clap::Error,
423    },
424    #[error("Failed to parse transaction `output`")]
425    Output {
426        #[source]
427        err: clap::Error,
428    },
429    #[error("Unrecognized argument {arg:?}, expected `input` or `output`")]
430    UnrecognizedArgumentExpectedInputOutput { arg: String, remaining: Vec<String> },
431    #[error("Found argument `input` which isn't valid for a mint transaction")]
432    MintTxHasInput,
433}
434
435/// Errors that can occur during conversion from the CLI transaction
436/// representation to the `fuel-tx` representation.
437#[derive(Debug, Error)]
438pub enum ConvertTxError {
439    #[error("failed to convert create transaction")]
440    Create(#[from] ConvertCreateTxError),
441    #[error("failed to convert script transaction")]
442    Script(#[from] ConvertScriptTxError),
443}
444
445/// Errors that can occur during "create" transaction conversion.
446#[derive(Debug, Error)]
447pub enum ConvertCreateTxError {
448    #[error("failed to open `--storage-slots` from {path:?}")]
449    StorageSlotsOpen {
450        path: PathBuf,
451        #[source]
452        err: std::io::Error,
453    },
454    #[error("failed to deserialize storage slots file")]
455    StorageSlotsDeserialize(#[source] serde_json::Error),
456    #[error("failed to convert an input")]
457    Input(#[from] ConvertInputError),
458}
459
460/// Errors that can occur during "script" transaction conversion.
461#[derive(Debug, Error)]
462pub enum ConvertScriptTxError {
463    #[error("failed to read `--bytecode` from {path:?}")]
464    BytecodeRead {
465        path: PathBuf,
466        #[source]
467        err: std::io::Error,
468    },
469    #[error("failed to read `--data` from {path:?}")]
470    DataRead {
471        path: PathBuf,
472        #[source]
473        err: std::io::Error,
474    },
475    #[error("failed to convert an input")]
476    Input(#[from] ConvertInputError),
477}
478
479/// Errors that can occur during transaction input conversion.
480#[derive(Debug, Error)]
481pub enum ConvertInputError {
482    #[error("failed to read `--msg-data` from {path:?}")]
483    MessageDataRead {
484        path: PathBuf,
485        #[source]
486        err: std::io::Error,
487    },
488    #[error("failed to read `--predicate` from {path:?}")]
489    PredicateRead {
490        path: PathBuf,
491        #[source]
492        err: std::io::Error,
493    },
494    #[error("failed to read `--predicate-data` from {path:?}")]
495    PredicateDataRead {
496        path: PathBuf,
497        #[source]
498        err: std::io::Error,
499    },
500    #[error("input accepts either witness index or predicate, not both")]
501    WitnessPredicateMismatch,
502}
503
504impl ParseError {
505    /// Print the error with clap's fancy formatting.
506    pub fn print(&self) -> Result<(), clap::Error> {
507        match self {
508            ParseError::Command { err } => {
509                err.print()?;
510            }
511            ParseError::Input { err } => {
512                err.print()?;
513            }
514            ParseError::Output { err } => {
515                err.print()?;
516            }
517            ParseError::UnrecognizedArgumentExpectedInputOutput { .. } => {
518                use clap::CommandFactory;
519                // Create a type as a hack to produce consistent-looking clap help output.
520                #[derive(Parser)]
521                enum ForcTxIo {
522                    #[clap(subcommand)]
523                    Input(Input),
524                    #[clap(subcommand)]
525                    Output(Output),
526                }
527                println!("{self}\n");
528                ForcTxIo::command().print_long_help()?;
529            }
530            ParseError::MintTxHasInput => {
531                println!("{self}");
532            }
533        }
534        Ok(())
535    }
536}
537
538impl Command {
539    /// Emulates `clap::Parser::parse` behaviour, but returns the parsed inputs and outputs.
540    ///
541    /// If parsing fails, prints the error along with the help output and exits with an error code.
542    ///
543    /// We provide this custom `parse` function solely due to clap's limitations around parsing
544    /// trailing subcommands.
545    pub fn parse() -> Self {
546        let err = match Self::try_parse() {
547            Err(err) => err,
548            Ok(cmd) => return cmd,
549        };
550        let _ = err.print();
551        std::process::exit(1);
552    }
553
554    /// Parse a full `Transaction` including trailing inputs and outputs.
555    pub fn try_parse() -> Result<Self, ParseError> {
556        Self::try_parse_from_args(std::env::args())
557    }
558
559    /// Parse a full `Transaction` including trailing inputs and outputs from an iterator yielding
560    /// whitespace-separate string arguments.
561    pub fn try_parse_from_args(args: impl IntoIterator<Item = String>) -> Result<Self, ParseError> {
562        const INPUT: &str = "input";
563        const OUTPUT: &str = "output";
564
565        fn is_input_or_output(s: &str) -> bool {
566            s == INPUT || s == OUTPUT
567        }
568
569        fn push_input(cmd: &mut Transaction, input: Input) -> Result<(), ParseError> {
570            match cmd {
571                Transaction::Create(ref mut create) => create.inputs.push(input),
572                Transaction::Script(ref mut script) => script.inputs.push(input),
573            }
574            Ok(())
575        }
576
577        fn push_output(cmd: &mut Transaction, output: Output) {
578            match cmd {
579                Transaction::Create(ref mut create) => create.outputs.push(output),
580                Transaction::Script(ref mut script) => script.outputs.push(output),
581            }
582        }
583
584        let mut args = args.into_iter().peekable();
585
586        // Collect args until the first `input` or `output` is reached.
587        let mut cmd = {
588            let cmd_args = std::iter::from_fn(|| args.next_if(|s| !is_input_or_output(s)));
589            Command::try_parse_from(cmd_args).map_err(|err| ParseError::Command { err })?
590        };
591
592        // The remaining args (if any) are the inputs and outputs.
593        while let Some(arg) = args.next() {
594            let args_til_next = std::iter::once(arg.clone()).chain(std::iter::from_fn(|| {
595                args.next_if(|s| !is_input_or_output(s))
596            }));
597            match &arg[..] {
598                INPUT => {
599                    let input = Input::try_parse_from(args_til_next)
600                        .map_err(|err| ParseError::Input { err })?;
601                    push_input(&mut cmd.tx, input)?
602                }
603                OUTPUT => {
604                    let output = Output::try_parse_from(args_til_next)
605                        .map_err(|err| ParseError::Output { err })?;
606                    push_output(&mut cmd.tx, output)
607                }
608                arg => {
609                    return Err(ParseError::UnrecognizedArgumentExpectedInputOutput {
610                        arg: arg.to_string(),
611                        remaining: args.collect(),
612                    })
613                }
614            }
615        }
616
617        // If there are args remaining, report them.
618        if args.peek().is_some() {
619            return Err(ParseError::UnrecognizedArgumentExpectedInputOutput {
620                arg: args.peek().unwrap().to_string(),
621                remaining: args.collect(),
622            });
623        }
624
625        Ok(cmd)
626    }
627}
628
629impl TryFrom<Transaction> for fuel_tx::Transaction {
630    type Error = ConvertTxError;
631    fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
632        let tx = match tx {
633            Transaction::Create(create) => Self::Create(<_>::try_from(create)?),
634            Transaction::Script(script) => Self::Script(<_>::try_from(script)?),
635        };
636        Ok(tx)
637    }
638}
639
640impl TryFrom<Create> for fuel_tx::Create {
641    type Error = ConvertCreateTxError;
642    fn try_from(create: Create) -> Result<Self, Self::Error> {
643        let storage_slots = {
644            let file = std::fs::File::open(&create.storage_slots).map_err(|err| {
645                ConvertCreateTxError::StorageSlotsOpen {
646                    path: create.storage_slots,
647                    err,
648                }
649            })?;
650            let reader = std::io::BufReader::new(file);
651            serde_json::from_reader(reader)
652                .map_err(ConvertCreateTxError::StorageSlotsDeserialize)?
653        };
654        let inputs = create
655            .inputs
656            .into_iter()
657            .map(fuel_tx::Input::try_from)
658            .collect::<Result<Vec<_>, _>>()?;
659        let outputs = create
660            .outputs
661            .into_iter()
662            .map(fuel_tx::Output::from)
663            .collect();
664        let witnesses = create
665            .witnesses
666            .into_iter()
667            .map(|s| fuel_tx::Witness::from(s.as_bytes()))
668            .collect();
669
670        let maturity = (create.maturity.maturity != 0).then_some(create.maturity.maturity.into());
671        let mut policies = Policies::default();
672        policies.set(PolicyType::Tip, create.gas.price);
673        policies.set(PolicyType::Maturity, maturity);
674
675        let create = fuel_tx::Transaction::create(
676            create.bytecode_witness_index,
677            policies,
678            create.salt.salt.unwrap_or_default(),
679            storage_slots,
680            inputs,
681            outputs,
682            witnesses,
683        );
684
685        Ok(create)
686    }
687}
688
689impl TryFrom<Script> for fuel_tx::Script {
690    type Error = ConvertScriptTxError;
691    fn try_from(script: Script) -> Result<Self, Self::Error> {
692        let script_bytecode =
693            std::fs::read(&script.bytecode).map_err(|err| ConvertScriptTxError::BytecodeRead {
694                path: script.bytecode,
695                err,
696            })?;
697
698        let script_data =
699            std::fs::read(&script.data).map_err(|err| ConvertScriptTxError::DataRead {
700                path: script.data,
701                err,
702            })?;
703        let inputs = script
704            .inputs
705            .into_iter()
706            .map(fuel_tx::Input::try_from)
707            .collect::<Result<Vec<_>, _>>()?;
708        let outputs = script
709            .outputs
710            .into_iter()
711            .map(fuel_tx::Output::from)
712            .collect();
713        let witnesses = script
714            .witnesses
715            .into_iter()
716            .map(|s| fuel_tx::Witness::from(s.as_bytes()))
717            .collect();
718
719        let mut policies = Policies::default().with_maturity(script.maturity.maturity.into());
720        policies.set(PolicyType::Tip, script.gas.price);
721        let mut script_tx = fuel_tx::Transaction::script(
722            0, // Temporary value. Will be replaced below
723            script_bytecode,
724            script_data,
725            policies,
726            inputs,
727            outputs,
728            witnesses,
729        );
730
731        if let Some(script_gas_limit) = script.gas.script_gas_limit {
732            script_tx.set_script_gas_limit(script_gas_limit)
733        } else {
734            let consensus_params = ConsensusParameters::default();
735            // Get `max_gas` used by everything except the script execution. Add `1` because of rounding.
736            let max_gas =
737                script_tx.max_gas(consensus_params.gas_costs(), consensus_params.fee_params()) + 1;
738            // Increase `script_gas_limit` to the maximum allowed value.
739            script_tx.set_script_gas_limit(consensus_params.tx_params().max_gas_per_tx() - max_gas);
740        }
741
742        Ok(script_tx)
743    }
744}
745
746impl TryFrom<Input> for fuel_tx::Input {
747    type Error = ConvertInputError;
748    fn try_from(input: Input) -> Result<Self, Self::Error> {
749        let input = match input {
750            Input::Coin(coin) => {
751                let InputCoin {
752                    utxo_id,
753                    // TODO: Should this be verified / checked in some way?
754                    output_ix: _,
755                    owner,
756                    amount,
757                    asset_id,
758                    tx_ptr: tx_pointer,
759                    maturity: _,
760                    predicate_gas_used,
761                    predicate,
762                    witness_ix,
763                } = coin;
764                match (witness_ix, predicate.bytecode, predicate.data) {
765                    (Some(witness_index), None, None) => fuel_tx::Input::coin_signed(
766                        utxo_id,
767                        owner,
768                        amount,
769                        asset_id,
770                        tx_pointer,
771                        witness_index,
772                    ),
773                    (None, Some(predicate), Some(predicate_data)) => {
774                        fuel_tx::Input::coin_predicate(
775                            utxo_id,
776                            owner,
777                            amount,
778                            asset_id,
779                            tx_pointer,
780                            predicate_gas_used,
781                            std::fs::read(&predicate).map_err(|err| {
782                                ConvertInputError::PredicateRead {
783                                    path: predicate,
784                                    err,
785                                }
786                            })?,
787                            std::fs::read(&predicate_data).map_err(|err| {
788                                ConvertInputError::PredicateDataRead {
789                                    path: predicate_data,
790                                    err,
791                                }
792                            })?,
793                        )
794                    }
795                    _ => return Err(ConvertInputError::WitnessPredicateMismatch),
796                }
797            }
798
799            Input::Contract(contract) => fuel_tx::Input::contract(
800                contract.utxo_id,
801                contract.balance_root,
802                contract.state_root,
803                contract.tx_ptr,
804                contract.contract_id,
805            ),
806
807            Input::Message(msg) => {
808                let InputMessage {
809                    sender,
810                    recipient,
811                    amount,
812                    nonce,
813                    msg_data,
814                    witness_ix,
815                    predicate_gas_used,
816                    predicate,
817                } = msg;
818                let data =
819                    std::fs::read(&msg_data).map_err(|err| ConvertInputError::MessageDataRead {
820                        path: msg_data,
821                        err,
822                    })?;
823                match (witness_ix, predicate.bytecode, predicate.data) {
824                    (Some(witness_index), None, None) => {
825                        if data.is_empty() {
826                            fuel_tx::Input::message_coin_signed(
827                                sender,
828                                recipient,
829                                amount,
830                                nonce,
831                                witness_index,
832                            )
833                        } else {
834                            fuel_tx::Input::message_data_signed(
835                                sender,
836                                recipient,
837                                amount,
838                                nonce,
839                                witness_index,
840                                data,
841                            )
842                        }
843                    }
844                    (None, Some(predicate), Some(predicate_data)) => {
845                        let predicate = std::fs::read(&predicate).map_err(|err| {
846                            ConvertInputError::PredicateRead {
847                                path: predicate,
848                                err,
849                            }
850                        })?;
851                        let predicate_data = std::fs::read(&predicate_data).map_err(|err| {
852                            ConvertInputError::PredicateDataRead {
853                                path: predicate_data,
854                                err,
855                            }
856                        })?;
857
858                        if data.is_empty() {
859                            fuel_tx::Input::message_coin_predicate(
860                                sender,
861                                recipient,
862                                amount,
863                                nonce,
864                                predicate_gas_used,
865                                predicate,
866                                predicate_data,
867                            )
868                        } else {
869                            fuel_tx::Input::message_data_predicate(
870                                sender,
871                                recipient,
872                                amount,
873                                nonce,
874                                predicate_gas_used,
875                                data,
876                                predicate,
877                                predicate_data,
878                            )
879                        }
880                    }
881                    _ => return Err(ConvertInputError::WitnessPredicateMismatch),
882                }
883            }
884        };
885        Ok(input)
886    }
887}
888
889impl From<Output> for fuel_tx::Output {
890    fn from(output: Output) -> Self {
891        match output {
892            Output::Coin(coin) => fuel_tx::Output::Coin {
893                to: coin.to,
894                amount: coin.amount,
895                asset_id: coin.asset_id,
896            },
897            Output::Contract(contract) => fuel_tx::Output::Contract(output::contract::Contract {
898                input_index: contract.input_ix,
899                balance_root: contract.balance_root,
900                state_root: contract.state_root,
901            }),
902            Output::Change(change) => fuel_tx::Output::Change {
903                to: change.to,
904                amount: change.amount,
905                asset_id: change.asset_id,
906            },
907            Output::Variable(variable) => fuel_tx::Output::Variable {
908                to: variable.to,
909                amount: variable.amount,
910                asset_id: variable.asset_id,
911            },
912            Output::ContractCreated(contract_created) => fuel_tx::Output::ContractCreated {
913                contract_id: contract_created.contract_id,
914                state_root: contract_created.state_root,
915            },
916        }
917    }
918}
919
920impl From<&Gas> for TxPolicies {
921    fn from(gas: &Gas) -> Self {
922        let mut policies = TxPolicies::default();
923        if let Some(max_fee) = gas.max_fee {
924            policies = policies.with_max_fee(max_fee);
925        }
926        if let Some(script_gas_limit) = gas.script_gas_limit {
927            policies = policies.with_script_gas_limit(script_gas_limit);
928        }
929        if let Some(tip) = gas.tip {
930            policies = policies.with_tip(tip);
931        }
932        policies
933    }
934}
935
936#[test]
937fn test_parse_create() {
938    let cmd = r#"
939        forc-tx create
940            --bytecode ./my-contract/out/debug/my-contract.bin
941            --storage-slots ./my-contract/out/debug/my-contract-storage_slots.json
942            --script-gas-limit 100
943            --gas-price 0
944            --maturity 0
945            --witness ADFD
946            --witness DFDA
947    "#;
948    dbg!(Command::try_parse_from_args(cmd.split_whitespace().map(|s| s.to_string())).unwrap());
949}
950
951#[test]
952fn test_parse_script() {
953    let receipts_root = fuel_tx::Bytes32::default();
954    let cmd = format!(
955        r#"
956        forc-tx script
957            --bytecode ./my-script/out/debug/my-script.bin
958            --data ./my-script.dat
959            --script-gas-limit 100
960            --gas-price 0
961            --maturity 0
962            --receipts-root {receipts_root}
963            --witness ADFD
964            --witness DFDA
965    "#
966    );
967    dbg!(Command::try_parse_from_args(cmd.split_whitespace().map(|s| s.to_string())).unwrap());
968}
969
970#[test]
971fn test_parse_create_inputs_outputs() {
972    let address = fuel_tx::Address::default();
973    let asset_id = fuel_tx::AssetId::default();
974    let tx_ptr = fuel_tx::TxPointer::default();
975    let balance_root = fuel_tx::Bytes32::default();
976    let state_root = fuel_tx::Bytes32::default();
977    let contract_id = fuel_tx::ContractId::default();
978    let nonce = fuel_types::Nonce::default();
979    let sender = fuel_tx::Address::default();
980    let recipient = fuel_tx::Address::default();
981    let args = format!(
982        r#"
983        forc-tx create
984            --bytecode ./my-contract/out/debug/my-contract.bin
985            --storage-slots ./my-contract/out/debug/my-contract-storage_slots.json
986            --script-gas-limit 100
987            --gas-price 0
988            --maturity 0
989            --witness ADFD
990            --witness DFDA
991            input coin
992                --utxo-id 0
993                --output-ix 0
994                --owner {address}
995                --amount 100
996                --asset-id {asset_id}
997                --tx-ptr {tx_ptr:X}
998                --witness-ix 0
999                --maturity 0
1000                --predicate ./my-predicate/out/debug/my-predicate.bin
1001                --predicate-data ./my-predicate.dat
1002            input contract
1003                --utxo-id 1
1004                --output-ix 1
1005                --balance-root {balance_root}
1006                --state-root {state_root}
1007                --tx-ptr {tx_ptr:X}
1008                --contract-id {contract_id}
1009            input message
1010                --sender {sender}
1011                --recipient {recipient}
1012                --amount 1
1013                --nonce {nonce}
1014                --witness-ix 1
1015                --msg-data ./message.dat
1016                --predicate ./my-predicate2/out/debug/my-predicate2.bin
1017                --predicate-data ./my-predicate2.dat
1018            output coin
1019                --to {address}
1020                --amount 100
1021                --asset-id {asset_id}
1022            output contract
1023                --input-ix 1
1024                --balance-root {balance_root}
1025                --state-root {state_root}
1026            output change
1027                --to {address}
1028                --amount 100
1029                --asset-id {asset_id}
1030            output variable
1031                --to {address}
1032                --amount 100
1033                --asset-id {asset_id}
1034            output contract-created
1035                --contract-id {contract_id}
1036                --state-root {state_root}
1037    "#
1038    );
1039    dbg!(Command::try_parse_from_args(args.split_whitespace().map(|s| s.to_string())).unwrap());
1040}