1- use anyhow:: { Context , Result } ;
1+ use anyhow:: { anyhow , bail , Context , Result } ;
22use clap:: Args ;
33use dojo_utils:: { Invoker , TxnConfig } ;
44use dojo_world:: config:: calldata_decoder;
@@ -17,76 +17,114 @@ use crate::utils::{get_account_from_env, CALLDATA_DOC};
1717 world context to be loaded. Use the execute command to execute systems in the \
1818 world context.") ]
1919pub struct InvokeArgs {
20- #[ arg( value_name = "CONTRACT_ADDRESS" , help = "Target contract address." ) ]
21- pub contract : Felt ,
22-
23- #[ arg( value_name = "ENTRYPOINT" , help = "Entrypoint to invoke on the contract." ) ]
24- pub entrypoint : String ,
25-
2620 #[ arg(
27- value_name = "ARG" ,
28- num_args = 0 .. ,
21+ num_args = 1 .. ,
22+ required = true ,
2923 help = format!(
30- "Calldata elements for the entrypoint (space separated).\n \n {}" ,
24+ "Calls to invoke, separated by '/'. \
25+ Each call follows the format <CONTRACT> <ENTRYPOINT> [CALLDATA...]\n \n {}",
3126 CALLDATA_DOC
3227 )
3328 ) ]
34- pub calldata : Vec < String > ,
29+ pub calls : Vec < String > ,
3530
3631 #[ command( flatten) ]
3732 pub transaction : TransactionOptions ,
3833
3934 #[ command( flatten) ]
4035 pub starknet : StarknetOptions ,
4136
37+ #[ arg( long, default_value = "0x0" , help = "Selector for the entrypoint in felt form." ) ]
38+ pub selector : Option < Felt > ,
39+
4240 #[ command( flatten) ]
41+ #[ command( next_help_heading = "Account options" ) ]
4342 pub account : AccountOptions ,
4443}
4544
4645impl InvokeArgs {
4746 pub async fn run ( self , ui : & SozoUi ) -> Result < ( ) > {
4847 trace ! ( args = ?self ) ;
4948
50- let InvokeArgs { contract, entrypoint, calldata, transaction, starknet, account } = self ;
49+ let account = get_account_from_env ( self . account , & self . starknet ) . await ?;
50+ let txn_config: TxnConfig = self . transaction . try_into ( ) ?;
51+ let mut invoker = Invoker :: new ( account, txn_config) ;
5152
52- let account = get_account_from_env ( account , & starknet ) . await ? ;
53- let txn_config : TxnConfig = transaction . try_into ( ) ? ;
53+ let mut calls_iter = self . calls . into_iter ( ) ;
54+ let mut call_index = 0usize ;
5455
55- ui. title ( format ! ( "Invoke contract {:#066x}" , contract) ) ;
56- ui. step ( format ! ( "Entrypoint: {}" , entrypoint) ) ;
56+ while let Some ( target) = calls_iter. next ( ) {
57+ if matches ! ( target. as_str( ) , "/" | "-" | "\\ " ) {
58+ continue ;
59+ }
5760
58- let mut decoded = Vec :: new ( ) ;
59- for item in calldata {
60- let felt_values = calldata_decoder:: decode_single_calldata ( & item)
61- . with_context ( || format ! ( "Failed to parse calldata argument `{item}`" ) ) ?;
62- decoded. extend ( felt_values) ;
63- }
61+ let entrypoint = calls_iter. next ( ) . ok_or_else ( || {
62+ anyhow ! (
63+ "Missing entrypoint for target `{target}`. Provide calls as `<CONTRACT> \
64+ <ENTRYPOINT> [CALLDATA...]`."
65+ )
66+ } ) ?;
67+
68+ let contract_address = parse_contract_address ( & target) ?;
69+ let selector = get_selector_from_name ( & entrypoint) ?;
70+
71+ let mut calldata = Vec :: new ( ) ;
72+ for arg in calls_iter. by_ref ( ) {
73+ match arg. as_str ( ) {
74+ "/" | "-" | "\\ " => break ,
75+ _ => {
76+ let felts =
77+ calldata_decoder:: decode_single_calldata ( & arg) . with_context ( || {
78+ format ! ( "Failed to parse calldata argument `{arg}`" )
79+ } ) ?;
80+ calldata. extend ( felts) ;
81+ }
82+ }
83+ }
6484
65- if decoded. is_empty ( ) {
66- ui. verbose ( "Calldata: <empty>" ) ;
67- } else {
68- ui. verbose ( format ! ( "Calldata ({} felt(s))" , decoded. len( ) ) ) ;
69- }
85+ call_index += 1 ;
86+ ui. step ( format ! ( "Call #{call_index}: {entrypoint} @ {:#066x}" , contract_address) ) ;
87+ if calldata. is_empty ( ) {
88+ ui. verbose ( " Calldata: <empty>" ) ;
89+ } else {
90+ ui. verbose ( format ! ( " Calldata ({} felt(s))" , calldata. len( ) ) ) ;
91+ }
7092
71- let selector = get_selector_from_name ( & entrypoint ) ? ;
72- let call = Call { to : contract , selector , calldata : decoded } ;
93+ invoker . add_call ( Call { to : contract_address , selector , calldata } ) ;
94+ }
7395
74- let invoker = Invoker :: new ( account, txn_config) ;
75- let result = invoker. invoke ( call) . await ?;
96+ if invoker. calls . is_empty ( ) {
97+ bail ! ( "No calls provided to invoke." ) ;
98+ }
7699
77- match result {
78- dojo_utils:: TransactionResult :: Noop => {
79- ui. result ( "Nothing to invoke (noop)." ) ;
80- }
81- dojo_utils:: TransactionResult :: Hash ( hash) => {
82- ui. result ( format ! ( "Invocation sent.\n Tx hash : {hash:#066x}" ) ) ;
83- }
84- dojo_utils:: TransactionResult :: HashReceipt ( hash, receipt) => {
85- ui. result ( format ! ( "Invocation included on-chain.\n Tx hash : {hash:#066x}" ) ) ;
86- ui. debug ( format ! ( "Receipt: {:?}" , receipt) ) ;
100+ let results = invoker. multicall ( ) . await ?;
101+
102+ for ( idx, result) in results. iter ( ) . enumerate ( ) {
103+ let display_idx = idx + 1 ;
104+ match result {
105+ dojo_utils:: TransactionResult :: Noop => {
106+ ui. result ( format ! ( "Call #{display_idx} noop (no transaction sent)." ) ) ;
107+ }
108+ dojo_utils:: TransactionResult :: Hash ( hash) => {
109+ ui. result ( format ! ( "Call #{display_idx} sent.\n Tx hash : {hash:#066x}" ) ) ;
110+ }
111+ dojo_utils:: TransactionResult :: HashReceipt ( hash, receipt) => {
112+ ui. result ( format ! ( "Call #{display_idx} included.\n Tx hash : {hash:#066x}" ) ) ;
113+ ui. debug ( format ! ( "Receipt: {:?}" , receipt) ) ;
114+ }
87115 }
88116 }
89117
90118 Ok ( ( ) )
91119 }
92120}
121+
122+ fn parse_contract_address ( value : & str ) -> Result < Felt > {
123+ if let Ok ( felt) = Felt :: from_hex ( value) {
124+ return Ok ( felt) ;
125+ }
126+
127+ Felt :: from_dec_str ( value) . map_err ( |_| {
128+ anyhow ! ( "Invalid contract address `{value}`. Use hex (0x...) or decimal form." )
129+ } )
130+ }
0 commit comments