1use 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 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#[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#[derive(Debug, Parser, Deserialize, Serialize)]
134#[clap(name = "transaction")]
135pub enum Transaction {
136 Create(Create),
137 Script(Script),
138}
139
140#[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 #[clap(long)]
151 pub bytecode: PathBuf,
152 #[clap(long, default_value_t = 0)]
154 pub bytecode_witness_index: u16,
155 #[clap(long)]
157 pub storage_slots: PathBuf,
158 #[clap(long = "witness", num_args(0..255))]
162 pub witnesses: Vec<String>,
163 #[clap(skip)]
165 pub inputs: Vec<Input>,
166 #[clap(skip)]
168 pub outputs: Vec<Output>,
169}
170
171#[derive(Debug, Parser, Deserialize, Serialize)]
173pub struct Script {
174 #[clap(flatten)]
175 pub gas: Gas,
176 #[clap(flatten)]
177 pub maturity: Maturity,
178 #[clap(long)]
180 pub bytecode: PathBuf,
181 #[clap(long)]
183 pub data: PathBuf,
184 #[clap(long)]
186 pub receipts_root: fuel_tx::Bytes32,
187 #[clap(long = "witness", num_args(0..=255))]
191 pub witnesses: Vec<String>,
192 #[clap(skip)]
194 pub inputs: Vec<Input>,
195 #[clap(skip)]
197 pub outputs: Vec<Output>,
198}
199
200#[derive(Debug, Devault, Clone, Parser, Deserialize, Serialize)]
202pub struct Gas {
203 #[clap(long = "gas-price")]
205 pub price: Option<u64>,
206 #[clap(long = "script-gas-limit")]
208 pub script_gas_limit: Option<u64>,
209 #[clap(long)]
211 pub max_fee: Option<u64>,
212 #[clap(long)]
214 pub tip: Option<u64>,
215}
216
217#[derive(Debug, Args, Default, Deserialize, Serialize)]
219pub struct Maturity {
220 #[clap(long = "maturity", default_value_t = 0)]
222 pub maturity: u32,
223}
224
225#[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 #[clap(long)]
238 pub utxo_id: fuel_tx::UtxoId,
239 #[clap(long)]
241 pub output_ix: u8,
242 #[clap(long)]
244 pub owner: fuel_tx::Address,
245 #[clap(long)]
247 pub amount: u64,
248 #[clap(long)]
250 pub asset_id: fuel_tx::AssetId,
251 #[clap(long)]
253 pub tx_ptr: fuel_tx::TxPointer,
254 #[clap(long)]
256 pub witness_ix: Option<u16>,
257 #[clap(long)]
259 pub maturity: u32,
260 #[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 #[clap(long)]
271 pub utxo_id: fuel_tx::UtxoId,
272 #[clap(long)]
274 pub output_ix: u8,
275 #[clap(long)]
277 pub balance_root: fuel_tx::Bytes32,
278 #[clap(long)]
280 pub state_root: fuel_tx::Bytes32,
281 #[clap(long)]
283 pub tx_ptr: fuel_tx::TxPointer,
284 #[clap(long)]
286 pub contract_id: fuel_tx::ContractId,
287}
288
289#[derive(Debug, Parser, Deserialize, Serialize)]
290pub struct InputMessage {
291 #[clap(long)]
293 pub sender: fuel_tx::Address,
294 #[clap(long)]
296 pub recipient: fuel_tx::Address,
297 #[clap(long)]
299 pub amount: u64,
300 #[clap(long)]
302 pub nonce: fuel_types::Nonce,
303 #[clap(long)]
305 pub msg_data: PathBuf,
306 #[clap(long)]
308 pub witness_ix: Option<u16>,
309 #[clap(long, default_value_t = 0)]
311 pub predicate_gas_used: u64,
312 #[clap(flatten)]
313 pub predicate: Predicate,
314}
315
316#[derive(Debug, Parser, Deserialize, Serialize)]
318pub struct Predicate {
319 #[clap(long = "predicate")]
321 pub bytecode: Option<PathBuf>,
322 #[clap(long = "predicate-data")]
324 pub data: Option<PathBuf>,
325}
326
327#[derive(Debug, Parser, Deserialize, Serialize)]
329pub struct TxPointer {
330 #[clap(long = "tx-ptr-block-height")]
332 pub block_height: u32,
333 #[clap(long = "tx-ptr-ix")]
335 pub tx_ix: u16,
336}
337
338#[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 #[clap(long)]
353 pub to: fuel_tx::Address,
354 #[clap(long)]
356 pub amount: fuel_tx::Word,
357 #[clap(long)]
359 pub asset_id: fuel_tx::AssetId,
360}
361
362#[derive(Debug, Parser, Deserialize, Serialize)]
363pub struct OutputContract {
364 #[clap(long)]
366 pub input_ix: u16,
367 #[clap(long)]
369 pub balance_root: fuel_tx::Bytes32,
370 #[clap(long)]
372 pub state_root: fuel_tx::Bytes32,
373}
374
375#[derive(Debug, Parser, Deserialize, Serialize)]
376pub struct OutputChange {
377 #[clap(long)]
379 pub to: fuel_tx::Address,
380 #[clap(long)]
382 pub amount: fuel_tx::Word,
383 #[clap(long)]
385 pub asset_id: fuel_tx::AssetId,
386}
387
388#[derive(Debug, Parser, Deserialize, Serialize)]
389pub struct OutputVariable {
390 #[clap(long)]
392 pub to: fuel_tx::Address,
393 #[clap(long)]
395 pub amount: fuel_tx::Word,
396 #[clap(long)]
398 pub asset_id: fuel_tx::AssetId,
399}
400
401#[derive(Debug, Parser, Deserialize, Serialize)]
402pub struct OutputContractCreated {
403 #[clap(long)]
405 pub contract_id: fuel_tx::ContractId,
406 #[clap(long)]
408 pub state_root: fuel_tx::Bytes32,
409}
410
411#[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#[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#[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#[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#[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 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 #[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 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 pub fn try_parse() -> Result<Self, ParseError> {
556 Self::try_parse_from_args(std::env::args())
557 }
558
559 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 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 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 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, 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 let max_gas =
737 script_tx.max_gas(consensus_params.gas_costs(), consensus_params.fee_params()) + 1;
738 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 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}