@@ -24,6 +24,13 @@ use url::Url;
2424const DEFAULT_URI : & str = "//Alice" ;
2525const ENCODED_CALL_DATA_MAX_LEN : usize = 500 ; // Maximum length of encoded call data to display.
2626
27+ fn to_tuple ( args : & [ String ] ) -> String {
28+ if args. len ( ) < 2 {
29+ panic ! ( "Cannot convert to tuple: too few arguments" ) ;
30+ }
31+ format ! ( "({})" , args. join( "," ) )
32+ }
33+
2734/// Command to construct and execute extrinsics with configurable pallets, functions, arguments, and
2835/// signing options.
2936#[ derive( Args , Clone , Default ) ]
@@ -73,8 +80,6 @@ impl CallChainCommand {
7380 pub ( crate ) async fn execute ( mut self ) -> Result < ( ) > {
7481 let mut cli = cli:: Cli ;
7582 cli. intro ( "Call a chain" ) ?;
76- // Check if all fields are specified via the command line.
77- let prompt_to_repeat_call = self . requires_user_input ( ) ;
7883 // Configure the chain.
7984 let chain = chain:: configure (
8085 "Which chain would you like to interact with?" ,
@@ -138,7 +143,7 @@ impl CallChainCommand {
138143 } ,
139144 CallItem :: Constant ( constant) => {
140145 // We already have the value of a constant, so we don't need to query it
141- cli. success ( & raw_value_to_string ( & constant. value ) ?) ?;
146+ cli. success ( & raw_value_to_string ( & constant. value , "" ) ?) ?;
142147 } ,
143148 CallItem :: Storage ( ref storage) => {
144149 // Parse string arguments to Value types for storage query
@@ -206,7 +211,7 @@ impl CallChainCommand {
206211 } ,
207212 } ;
208213
209- if ( !prompt_to_repeat_call && self . skip_confirm ) ||
214+ if self . skip_confirm ||
210215 !cli. confirm ( "Do you want to perform another call?" )
211216 . initial_value ( true )
212217 . interact ( ) ?
@@ -305,7 +310,28 @@ impl CallChainCommand {
305310 let key_param = type_to_param ( & name. to_string ( ) , registry, key_ty)
306311 . map_err ( |e| anyhow ! ( "Failed to parse storage key type: {e}" ) ) ?;
307312
308- let mut params = self . args . clone ( ) ;
313+ let is_composite = key_param. sub_params . len ( ) > 1 ;
314+ let ( mut params, len) =
315+ if self . args . len ( ) == key_param. sub_params . len ( ) && is_composite {
316+ ( vec ! [ to_tuple( self . args. as_slice( ) ) ] , self . args . len ( ) )
317+ } else if self . args . len ( ) == 1 && is_composite {
318+ // Handle composite tuple string like "(A, B, C)"
319+ let arg = self . args [ 0 ]
320+ . trim ( )
321+ . trim_start_matches ( "(" )
322+ . trim_start_matches ( "[" )
323+ . trim_end_matches ( ")" )
324+ . trim_end_matches ( "]" )
325+ . to_string ( ) ;
326+ let len = arg
327+ . split ( ',' )
328+ . map ( |s| s. trim ( ) . to_string ( ) )
329+ . collect :: < Vec < _ > > ( )
330+ . len ( ) ;
331+ ( self . args . clone ( ) , len)
332+ } else {
333+ ( self . args . clone ( ) , self . args . len ( ) )
334+ } ;
309335 if key_param. sub_params . is_empty ( ) && params. is_empty ( ) {
310336 // Prompt user for the storage key
311337 let key_value = prompt_for_param ( cli, & key_param, false ) ?;
@@ -315,7 +341,7 @@ impl CallChainCommand {
315341 storage. query_all = true ;
316342 }
317343 } else {
318- for sub_param in key_param. sub_params . iter ( ) . skip ( self . args . len ( ) ) {
344+ for sub_param in key_param. sub_params . iter ( ) . skip ( len) {
319345 let sub_key_value = prompt_for_param ( cli, sub_param, false ) ?;
320346 if !sub_key_value. is_empty ( ) {
321347 params. push ( sub_key_value) ;
@@ -324,6 +350,9 @@ impl CallChainCommand {
324350 break ;
325351 }
326352 }
353+ if is_composite && params. len ( ) > 1 {
354+ params = vec ! [ to_tuple( params. as_slice( ) ) ] ;
355+ }
327356 }
328357 params
329358 } else {
@@ -449,11 +478,6 @@ impl CallChainCommand {
449478 self . use_wallet = false ;
450479 }
451480
452- // Function to check if all required fields are specified.
453- fn requires_user_input ( & self ) -> bool {
454- self . pallet . is_none ( ) || self . function . is_none ( ) || self . url . is_none ( )
455- }
456-
457481 /// Replaces file arguments with their contents, leaving other arguments unchanged.
458482 fn expand_file_arguments ( & self ) -> Result < Vec < String > > {
459483 self . args
@@ -583,6 +607,9 @@ impl Call {
583607 if self . sudo {
584608 full_message. push_str ( " --sudo" ) ;
585609 }
610+ if self . skip_confirm {
611+ full_message. push_str ( " --skip-confirm" ) ;
612+ }
586613 full_message
587614 }
588615}
@@ -1060,25 +1087,6 @@ mod tests {
10601087 Ok ( ( ) )
10611088 }
10621089
1063- #[ test]
1064- fn requires_user_input_works ( ) -> Result < ( ) > {
1065- let mut call_config = CallChainCommand {
1066- pallet : Some ( "System" . to_string ( ) ) ,
1067- function : Some ( "remark" . to_string ( ) ) ,
1068- args : vec ! [ "0x11" . to_string( ) ] ,
1069- url : Some ( Url :: parse ( urls:: LOCAL ) ?) ,
1070- suri : Some ( DEFAULT_URI . to_string ( ) ) ,
1071- use_wallet : false ,
1072- skip_confirm : false ,
1073- call_data : None ,
1074- sudo : false ,
1075- } ;
1076- assert ! ( !call_config. requires_user_input( ) ) ;
1077- call_config. pallet = None ;
1078- assert ! ( call_config. requires_user_input( ) ) ;
1079- Ok ( ( ) )
1080- }
1081-
10821090 #[ test]
10831091 fn expand_file_arguments_works ( ) -> Result < ( ) > {
10841092 let mut call_config = CallChainCommand {
@@ -1152,7 +1160,7 @@ mod tests {
11521160 let value = result. unwrap ( ) ;
11531161 // The value should be a primitive (block number)
11541162 assert ! ( matches!( value. value, ValueDef :: Primitive ( _) ) ) ;
1155- let formatted_value = raw_value_to_string ( & value) ?;
1163+ let formatted_value = raw_value_to_string ( & value, "" ) ?;
11561164 assert ! ( !formatted_value. is_empty( ) , "Formatted value should not be empty" ) ;
11571165
11581166 // Test querying a map storage item (System::Account with Alice's account)
@@ -1173,7 +1181,7 @@ mod tests {
11731181 let account_result = account_storage. query ( & client, vec ! [ account_key] ) . await ?;
11741182 assert ! ( account_result. is_some( ) , "Alice's account should exist in test chain" ) ;
11751183 let account_value = account_result. unwrap ( ) ;
1176- let formatted_account = raw_value_to_string ( & account_value) ?;
1184+ let formatted_account = raw_value_to_string ( & account_value, "" ) ?;
11771185 assert ! ( !formatted_account. is_empty( ) , "Account data should not be empty" ) ;
11781186
11791187 Ok ( ( ) )
@@ -1202,7 +1210,7 @@ mod tests {
12021210
12031211 // Constants have their values already decoded
12041212 let constant_value = & version_constant. value ;
1205- let formatted_value = raw_value_to_string ( constant_value) ?;
1213+ let formatted_value = raw_value_to_string ( constant_value, "" ) ?;
12061214 assert ! ( !formatted_value. is_empty( ) , "Constant value should not be empty" ) ;
12071215 // Version should be a composite value with spec_name, spec_version, etc.
12081216 assert ! ( matches!( constant_value. value, ValueDef :: Composite ( _) ) ) ;
@@ -1215,7 +1223,7 @@ mod tests {
12151223 . expect ( "System::BlockHashCount constant should exist" ) ;
12161224
12171225 let block_hash_count_value = & block_hash_count_constant. value ;
1218- let formatted_block_hash_count = raw_value_to_string ( block_hash_count_value) ?;
1226+ let formatted_block_hash_count = raw_value_to_string ( block_hash_count_value, "" ) ?;
12191227 assert ! ( !formatted_block_hash_count. is_empty( ) , "BlockHashCount value should not be empty" ) ;
12201228 // BlockHashCount should be a primitive value (u32)
12211229 assert ! ( matches!( block_hash_count_value. value, ValueDef :: Primitive ( _) ) ) ;
@@ -1228,10 +1236,35 @@ mod tests {
12281236 . expect ( "System::SS58Prefix constant should exist" ) ;
12291237
12301238 let ss58_prefix_value = & ss58_prefix_constant. value ;
1231- let formatted_ss58_prefix = raw_value_to_string ( ss58_prefix_value) ?;
1239+ let formatted_ss58_prefix = raw_value_to_string ( ss58_prefix_value, "" ) ?;
12321240 assert ! ( !formatted_ss58_prefix. is_empty( ) , "SS58Prefix value should not be empty" ) ;
12331241 assert ! ( matches!( ss58_prefix_value. value, ValueDef :: Primitive ( _) ) ) ;
12341242
12351243 Ok ( ( ) )
12361244 }
1245+
1246+ #[ tokio:: test]
1247+ async fn query_storage_with_composite_key_works ( ) -> Result < ( ) > {
1248+ // Spawn a test node
1249+ let node = TestNode :: spawn ( ) . await ?;
1250+ let node_url = node. ws_url ( ) ;
1251+
1252+ // Build the command to directly execute a storage query using a composite key
1253+ let cmd = CallChainCommand {
1254+ pallet : Some ( "Assets" . to_string ( ) ) ,
1255+ function : Some ( "Account" . to_string ( ) ) ,
1256+ args : vec ! [
1257+ "10000" . to_string( ) , // AssetId
1258+ // Alice AccountId32 (hex) in dev networks
1259+ "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" . to_string( ) ,
1260+ ] ,
1261+ url : Some ( Url :: parse ( node_url) ?) ,
1262+ skip_confirm : true , // Avoid interactive confirmation at the end of execute loop
1263+ ..Default :: default ( )
1264+ } ;
1265+
1266+ // Execute the command end-to-end; it should parse the composite key and perform the storage
1267+ // query
1268+ cmd. execute ( ) . await
1269+ }
12371270}
0 commit comments