diff --git a/bin/sozo/src/commands/execute.rs b/bin/sozo/src/commands/execute.rs index fe5e37f62c..009e7cb820 100644 --- a/bin/sozo/src/commands/execute.rs +++ b/bin/sozo/src/commands/execute.rs @@ -18,26 +18,37 @@ use super::options::world::WorldOptions; use crate::utils; #[derive(Debug, Args)] -#[command(about = "Execute a system with the given calldata.")] +#[command(about = "Execute one or several systems with the given calldata.")] pub struct ExecuteArgs { - #[arg( - help = "The address or the tag (ex: dojo_examples:actions) of the contract to be executed." - )] - pub tag_or_address: ResourceDescriptor, - - #[arg(help = "The name of the entrypoint to be executed.")] - pub entrypoint: String, - - #[arg(short, long)] - #[arg(help = "The calldata to be passed to the system. Comma separated values e.g., \ - 0x12345,128,u256:9999999999. Sozo supports some prefixes that you can use to \ - automatically parse some types. The supported prefixes are: - - u256: A 256-bit unsigned integer. - - sstr: A cairo short string. - - str: A cairo string (ByteArray). - - int: A signed integer. - - no prefix: A cairo felt or any type that fit into one felt.")] - pub calldata: Option, + #[arg(num_args = 1..)] + #[arg(required = true)] + #[arg(help = "A list of calls to execute, separated by a /. + +A call is made up of a , an and an optional : + +- : the address or the tag (ex: dojo_examples-actions) of the contract to be \ + called, + +- : the name of the entry point to be called, + +- : the calldata to be passed to the system. + + Space separated values e.g., 0x12345 128 u256:9999999999. + Sozo supports some prefixes that you can use to automatically parse some types. The supported \ + prefixes are: + - u256: A 256-bit unsigned integer. + - sstr: A cairo short string. + - str: A cairo string (ByteArray). + - int: A signed integer. + - no prefix: A cairo felt or any type that fit into one felt. + +EXAMPLE + + sozo execute 0x1234 run / ns-Actions move 1 2 + +Executes the run function of the contract at the address 0x1234 without calldata, +and the move function of the ns-Actions contract, with the calldata [1,2].")] + pub calls: Vec, #[arg(long)] #[arg(help = "If true, sozo will compute the diff of the world from the chain to translate \ @@ -65,8 +76,6 @@ impl ExecuteArgs { let profile_config = ws.load_profile_config()?; - let descriptor = self.tag_or_address.ensure_namespace(&profile_config.namespace.default); - #[cfg(feature = "walnut")] let walnut_debugger = WalnutDebugger::new_from_flag( self.transaction.walnut, @@ -76,64 +85,81 @@ impl ExecuteArgs { let txn_config: TxnConfig = self.transaction.try_into()?; config.tokio_handle().block_on(async { - let (contract_address, contracts) = match &descriptor { - ResourceDescriptor::Address(address) => (Some(*address), Default::default()), - ResourceDescriptor::Tag(tag) => { - let contracts = utils::contracts_from_manifest_or_diff( - self.account.clone(), - self.starknet.clone(), - self.world, - &ws, - self.diff, - ) - .await?; - - (contracts.get(tag).map(|c| c.address), contracts) - } - ResourceDescriptor::Name(_) => { - unimplemented!("Expected to be a resolved tag with default namespace.") - } - }; - - let contract_address = contract_address.ok_or_else(|| { - let mut message = format!("Contract {descriptor} not found in the manifest."); - if self.diff { - message.push_str( - " Run the command again with `--diff` to force the fetch of data from the \ - chain.", - ); - } - anyhow!(message) - })?; - - trace!( - contract=?descriptor, - entrypoint=self.entrypoint, - calldata=?self.calldata, - "Executing Execute command." - ); - - let calldata = if let Some(cd) = self.calldata { - calldata_decoder::decode_calldata(&cd)? - } else { - vec![] - }; - - let call = Call { - calldata, - to: contract_address, - selector: snutils::get_selector_from_name(&self.entrypoint)?, - }; - let (provider, _) = self.starknet.provider(profile_config.env.as_ref())?; + let contracts = utils::contracts_from_manifest_or_diff( + self.account.clone(), + self.starknet.clone(), + self.world, + &ws, + self.diff, + ) + .await?; + let account = self .account .account(provider, profile_config.env.as_ref(), &self.starknet, &contracts) .await?; - let invoker = Invoker::new(&account, txn_config); - let tx_result = invoker.invoke(call).await?; + let mut invoker = Invoker::new(&account, txn_config); + + let mut arg_iter = self.calls.into_iter(); + + while let Some(arg) = arg_iter.next() { + let tag_or_address = arg; + let descriptor = ResourceDescriptor::from_string(&tag_or_address)? + .ensure_namespace(&profile_config.namespace.default); + + let contract_address = match &descriptor { + ResourceDescriptor::Address(address) => Some(*address), + ResourceDescriptor::Tag(tag) => contracts.get(tag).map(|c| c.address), + ResourceDescriptor::Name(_) => { + unimplemented!("Expected to be a resolved tag with default namespace.") + } + }; + + let contract_address = contract_address.ok_or_else(|| { + let mut message = format!("Contract {descriptor} not found in the manifest."); + if self.diff { + message.push_str( + " Run the command again with `--diff` to force the fetch of data from \ + the chain.", + ); + } + anyhow!(message) + })?; + + let entrypoint = arg_iter.next().ok_or_else(|| { + anyhow!( + "You must specify the entry point of the contract `{tag_or_address}` to \ + invoke, and optionally the calldata." + ) + })?; + + let mut calldata = vec![]; + for arg in &mut arg_iter { + let arg = match arg.as_str() { + "/" | "-" | "\\" => break, + _ => calldata_decoder::decode_single_calldata(&arg)?, + }; + calldata.extend(arg); + } + + trace!( + contract=?descriptor, + entrypoint=entrypoint, + calldata=?calldata, + "Decoded call." + ); + + invoker.add_call(Call { + to: contract_address, + selector: snutils::get_selector_from_name(&entrypoint)?, + calldata, + }); + } + + let tx_result = invoker.multicall().await?; #[cfg(feature = "walnut")] if let Some(walnut_debugger) = walnut_debugger { diff --git a/bin/sozo/src/commands/mod.rs b/bin/sozo/src/commands/mod.rs index 61efdf83e4..bcc1e51d93 100644 --- a/bin/sozo/src/commands/mod.rs +++ b/bin/sozo/src/commands/mod.rs @@ -50,7 +50,7 @@ pub enum Commands { #[command(about = "Run a migration, declaring and deploying contracts as necessary to update \ the world")] Migrate(Box), - #[command(about = "Execute a system with the given calldata.")] + #[command(about = "Execute one or several systems with the given calldata.")] Execute(Box), #[command(about = "Inspect the world")] Inspect(Box), diff --git a/crates/dojo/world/src/config/calldata_decoder.rs b/crates/dojo/world/src/config/calldata_decoder.rs index fd74e0aa46..15d87112ad 100644 --- a/crates/dojo/world/src/config/calldata_decoder.rs +++ b/crates/dojo/world/src/config/calldata_decoder.rs @@ -140,7 +140,7 @@ pub fn decode_calldata(input: &str) -> DecoderResult> { let mut calldata = vec![]; for item in items { - calldata.extend(decode_inner(item)?); + calldata.extend(decode_single_calldata(item)?); } Ok(calldata) @@ -154,7 +154,7 @@ pub fn decode_calldata(input: &str) -> DecoderResult> { /// /// # Returns /// A vector of [`Felt`]s. -fn decode_inner(item: &str) -> DecoderResult> { +pub fn decode_single_calldata(item: &str) -> DecoderResult> { let item = item.trim(); let felts = if let Some((prefix, value)) = item.split_once(ITEM_PREFIX_DELIMITER) { diff --git a/crates/sozo/ops/src/resource_descriptor.rs b/crates/sozo/ops/src/resource_descriptor.rs index 99ad4a15cf..5401fab213 100644 --- a/crates/sozo/ops/src/resource_descriptor.rs +++ b/crates/sozo/ops/src/resource_descriptor.rs @@ -7,7 +7,7 @@ use anyhow::Result; use dojo_world::contracts::naming; use starknet::core::types::Felt; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum ResourceDescriptor { Address(Felt), Name(String),