Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sozo): support multicall for execute command #2897

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

remybar
Copy link
Contributor

@remybar remybar commented Jan 11, 2025

Related issue

#2388

Tests

  • Yes
  • No, because they aren't needed
  • No, because I need help

Added to documentation?

Checklist

  • I've formatted my code (scripts/prettier.sh, scripts/rust_fmt.sh, scripts/cairo_fmt.sh)
  • I've linted my code (scripts/clippy.sh, scripts/docs.sh)
  • I've commented my code
  • I've requested a review after addressing the comments

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced command-line execution capabilities to support multiple contract calls in a single transaction.
    • Updated decoding function for improved accessibility and clarity.
  • Improvements

    • Improved input parsing for contract execution commands.
    • Updated command-line interface to provide clearer instructions for multi-call executions.
  • Technical Enhancements

    • Added ability to compare resource descriptors for equality.
    • Updated documentation for the Execute command to clarify its functionality.

Copy link

coderabbitai bot commented Jan 11, 2025

Walkthrough

Ohayo, sensei! The pull request introduces significant modifications to the sozo execute command, enhancing it with multi-call functionality. The ExecuteArgs struct has been updated to allow multiple calls through a new calls field, which is a vector of strings. This change enables users to specify multiple contract calls in a single execution command. The run method has been restructured to handle the iteration over these calls, facilitating their execution in a single transaction. Additionally, various documentation and visibility updates have been made across related files.

Changes

File Change Summary
bin/sozo/src/commands/execute.rs - Updated ExecuteArgs struct to include calls: Vec<String>
- Restructured run method to support multi-call execution and enhanced error handling
crates/sozo/ops/src/resource_descriptor.rs - Added PartialEq trait to ResourceDescriptor enum for equality comparisons
bin/sozo/src/commands/mod.rs - Updated documentation for Execute command to clarify it can execute "one or several systems"
crates/dojo/world/src/config/calldata_decoder.rs - Renamed decode_inner to decode_single_calldata and changed its visibility to public

Possibly related PRs

Suggested labels

sozo

Suggested reviewers

  • glihm

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
bin/sozo/src/commands/execute.rs (1)

190-215: Ohayo, sensei! Consider adding more test cases for CallArguments::from_str

The current tests cover several cases, but you might want to add tests for invalid calldata formats to ensure robust parsing and error handling.

Here's an example of an additional test case:

    // invalid calldata format
    let res = CallArguments::from_str("0x1234,run,invalid_calldata");
    assert!(res.is_err());
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5377ac and ad1ce8b.

📒 Files selected for processing (2)
  • bin/sozo/src/commands/execute.rs (5 hunks)
  • crates/sozo/ops/src/resource_descriptor.rs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: clippy
🔇 Additional comments (1)
crates/sozo/ops/src/resource_descriptor.rs (1)

10-10: Ohayo, sensei! Nice addition of PartialEq derive for ResourceDescriptor

Deriving PartialEq allows for equality comparisons, which is useful in testing and other logical operations.

Comment on lines +149 to +156
if self.diff {
message.push_str(
" Run the command again with `--diff` to force the fetch of data from \
the chain.",
);
}
anyhow!(message)
})?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ohayo, sensei! Fix the error message condition to display the suggestion correctly

Currently, the error message suggests running the command again with --diff when self.diff is true. However, if self.diff is already true, the suggestion is redundant. The condition should be inverted to check when self.diff is false.

Apply this diff to fix the condition:

-                        if self.diff {
+                        if !self.diff {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if self.diff {
message.push_str(
" Run the command again with `--diff` to force the fetch of data from \
the chain.",
);
}
anyhow!(message)
})?;
if !self.diff {
message.push_str(
" Run the command again with `--diff` to force the fetch of data from \
the chain.",
);
}
anyhow!(message)
})?;

Comment on lines +139 to +145
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.")
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ohayo, sensei! Handle ResourceDescriptor::Name variant to prevent runtime panics

Using unimplemented!() in the match arm for ResourceDescriptor::Name(_) will cause a runtime panic if this case is encountered. Consider implementing proper error handling for this variant to prevent unexpected panics.

Apply this diff to handle the Name variant gracefully:

                         ResourceDescriptor::Name(_) => {
-                            unimplemented!("Expected to be a resolved tag with default namespace.")
+                            return Err(anyhow!("Contract address could not be resolved for descriptor: {descriptor}"));
                         }

Committable suggestion skipped: line range outside the PR's diff.

Copy link

codecov bot commented Jan 11, 2025

Codecov Report

Attention: Patch coverage is 3.92157% with 49 lines in your changes missing coverage. Please review.

Project coverage is 55.20%. Comparing base (c93a058) to head (01aad32).
Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
bin/sozo/src/commands/execute.rs 0.00% 49 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2897      +/-   ##
==========================================
- Coverage   55.85%   55.20%   -0.65%     
==========================================
  Files         449      449              
  Lines       57730    58350     +620     
==========================================
- Hits        32245    32214      -31     
- Misses      25485    26136     +651     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@glihm glihm changed the title sozo: support multicall for execute command feat(sozo): support multicall for execute command Jan 13, 2025
bin/sozo/src/commands/execute.rs Show resolved Hide resolved
pub tag_or_address: ResourceDescriptor,
#[arg(num_args = 1..)]
#[arg(help = "A list of calls to execute.\n
A call is made up of 3 values, separated by a comma (<TAG_OR_ADDRESS>,<ENTRYPOINT>[,<CALLDATA>]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be more natural to have the calldata separated from the two first arguments.

Starkli is doing that: https://github.com/xJonathanLEI/starkli/blob/4c92ac0ee3c8a50289b09760ec535cf27dd64b2c/src/subcommands/invoke.rs#L62

Wondering what could be best to have something easy to use, wdyt on that?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
bin/sozo/src/commands/execute.rs (2)

115-117: ⚠️ Potential issue

Handle ResourceDescriptor::Name variant to prevent runtime panics.

Ohayo, sensei! Using unimplemented!() could cause runtime panics. We should handle this case gracefully.

    ResourceDescriptor::Name(_) => {
-        unimplemented!("Expected to be a resolved tag with default namespace.")
+        return Err(anyhow!("Contract address could not be resolved for descriptor: {descriptor}"));
    }

122-129: ⚠️ Potential issue

Fix the error message condition to display the suggestion correctly.

Ohayo, sensei! The suggestion to use --diff should only be shown when it's not already being used.

-                    if self.diff {
+                    if !self.diff {
🧹 Nitpick comments (2)
bin/sozo/src/commands/execute.rs (2)

137-137: Define call separator characters as constants.

Ohayo, sensei! The separator characters should be defined as constants at the module level for better maintainability.

+const CALL_SEPARATORS: [&str; 3] = ["/", "-", "\\"];

 // Then in the code:
-                    "/" | "-" | "\\" => break,
+                    s if CALL_SEPARATORS.contains(&s) => break,

131-132: Improve error message for missing entrypoint.

Ohayo, sensei! The error message could be more descriptive about what argument is missing.

-                    arg_iter.next().ok_or_else(|| anyhow!("Unexpected number of arguments"))?;
+                    arg_iter.next().ok_or_else(|| anyhow!("Missing entrypoint for contract {descriptor}"))?;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad1ce8b and 5ee7a30.

📒 Files selected for processing (3)
  • bin/sozo/src/commands/execute.rs (2 hunks)
  • bin/sozo/src/commands/mod.rs (1 hunks)
  • crates/dojo/world/src/config/calldata_decoder.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: fmt
🔇 Additional comments (5)
crates/dojo/world/src/config/calldata_decoder.rs (1)

143-143: LGTM! Clean refactoring of the decoder function.

The renaming from decode_inner to decode_single_calldata and making it public improves code clarity and reusability. The function's implementation remains solid with comprehensive test coverage.

Also applies to: 157-157

bin/sozo/src/commands/mod.rs (1)

49-52: LGTM! Documentation updates accurately reflect the new multicall capability.

The command descriptions have been updated to clearly indicate that the Execute command now supports multiple system calls.

Also applies to: 54-54

bin/sozo/src/commands/execute.rs (3)

23-24: Add the required constraint before num_args for proper validation.

Ohayo, sensei! Based on the past review comments, the argument constraints need to be reordered for proper application.

-    #[arg(num_args = 1..)]
-    #[arg(required = true)]
+    #[arg(required = true)]
+    #[arg(num_args = 1..)]

90-97: LGTM! Clean implementation of contract resolution.

The contract resolution logic properly handles both manifest-based and diff-based approaches.


150-154: LGTM! Proper multicall implementation.

The implementation correctly builds and executes multiple calls in a single transaction.

@remybar remybar force-pushed the sozo-multicall_support branch from 5ee7a30 to 09b00d1 Compare January 14, 2025 14:59
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
bin/sozo/src/commands/execute.rs (2)

115-118: ⚠️ Potential issue

Ohayo, sensei! Replace unimplemented!() with proper error handling

Using unimplemented!() for the ResourceDescriptor::Name variant could cause runtime panics.

Apply this diff to handle the Name variant gracefully:

                     ResourceDescriptor::Name(_) => {
-                        unimplemented!("Expected to be a resolved tag with default namespace.")
+                        return Err(anyhow!("Contract address could not be resolved for descriptor: {descriptor}"));
                     }

122-129: ⚠️ Potential issue

Ohayo, sensei! Fix the error message condition

The error message suggests running with --diff when it's already enabled.

Apply this diff to fix the condition:

-                    if self.diff {
+                    if !self.diff {
🧹 Nitpick comments (2)
bin/sozo/src/commands/execute.rs (2)

157-157: Ohayo, sensei! Consider adding transaction simulation

The multicall transaction could benefit from simulation before execution to catch potential issues early.

Consider adding a simulation step before the actual multicall:

+            // Simulate the transaction first
+            let simulation = invoker.simulate_multicall().await?;
+            trace!(simulation=?simulation, "Multicall simulation successful");
+
             let tx_result = invoker.multicall().await?;

104-155: Ohayo, sensei! The multicall implementation is well-structured

The implementation efficiently batches multiple calls into a single transaction, which is a significant improvement for gas efficiency and atomic execution. The parsing logic for calls and calldata is robust and handles edge cases well.

Consider documenting the following characteristics in the codebase:

  1. Gas savings from batching multiple calls
  2. Atomicity guarantees (all calls succeed or all fail)
  3. Maximum recommended number of calls per multicall
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ee7a30 and 09b00d1.

📒 Files selected for processing (3)
  • bin/sozo/src/commands/execute.rs (2 hunks)
  • bin/sozo/src/commands/mod.rs (1 hunks)
  • crates/dojo/world/src/config/calldata_decoder.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • bin/sozo/src/commands/mod.rs
  • crates/dojo/world/src/config/calldata_decoder.rs
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: clippy
🔇 Additional comments (1)
bin/sozo/src/commands/execute.rs (1)

21-51: Ohayo, sensei! The command structure is well-documented and properly implemented.

The help text is comprehensive, providing clear examples and detailed explanations of the call format and supported type prefixes. The implementation correctly follows the previous feedback about argument constraints.

Comment on lines +106 to +108
let mut arg_iter = self.calls.into_iter();

while let Some(arg) = arg_iter.next() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ohayo, sensei! Add validation for minimum number of calls

The iterator implementation should validate that at least one complete call (address + entrypoint) is provided.

Add this validation before the while loop:

             let mut arg_iter = self.calls.into_iter();
+            if self.calls.len() < 2 {
+                return Err(anyhow!("At least one complete call (address and entrypoint) is required"));
+            }
 
             while let Some(arg) = arg_iter.next() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut arg_iter = self.calls.into_iter();
while let Some(arg) = arg_iter.next() {
let mut arg_iter = self.calls.into_iter();
if self.calls.len() < 2 {
return Err(anyhow!("At least one complete call (address and entrypoint) is required"));
}
while let Some(arg) = arg_iter.next() {

Copy link
Collaborator

@glihm glihm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @remybar! One small change and let's roll.

bin/sozo/src/commands/execute.rs Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
bin/sozo/src/commands/execute.rs (2)

21-51: Ohayo, sensei! Consider enhancing the documentation with more comprehensive examples

While the documentation is thorough, consider adding examples that demonstrate:

  1. All supported prefixes (u256, sstr, str, int)
  2. Multiple calls with different calldata types
  3. The maximum number of calls supported in a single transaction

Add an expanded example section:

 EXAMPLE
 
    sozo execute 0x1234 run / ns-Actions move 1 2
+
+   # Multiple calls with different data types
+   sozo execute \
+     0x1234 set_value u256:1000 / \
+     ns-Actions move int:42 sstr:player / \
+     ns-Config update_name str:ByteArrayValue

155-160: Ohayo, sensei! Consider adding debug assertions for call validation

Add debug assertions to validate each call's parameters before adding to the multicall.

+                debug_assert!(!calldata.is_empty(), "Calldata should not be empty");
+                debug_assert!(
+                    contract_address != starknet::core::types::FieldElement::ZERO,
+                    "Contract address should not be zero"
+                );
                 invoker.add_call(Call {
                     to: contract_address,
                     selector: snutils::get_selector_from_name(&entrypoint)?,
                     calldata,
                 });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09b00d1 and 01aad32.

📒 Files selected for processing (1)
  • bin/sozo/src/commands/execute.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: fmt
🔇 Additional comments (3)
bin/sozo/src/commands/execute.rs (3)

123-130: Ohayo, sensei! Fix the error message condition

The error message suggests running with --diff when it's already true.

Apply this diff to fix the condition:

-                    if self.diff {
+                    if !self.diff {
                         message.push_str(
                             " Run the command again with `--diff` to force the fetch of data from \
                              the chain.",
                         );
                     }

116-119: Ohayo, sensei! Handle ResourceDescriptor::Name variant properly

Using unimplemented!() will cause runtime panics. Handle the Name variant gracefully:

                     ResourceDescriptor::Name(_) => {
-                        unimplemented!("Expected to be a resolved tag with default namespace.")
+                        return Err(anyhow!("Contract address could not be resolved for descriptor: {descriptor}"));
                     }

106-107: ⚠️ Potential issue

Ohayo, sensei! Add validation for the number of calls

The iterator setup should validate both minimum and maximum number of calls to prevent:

  1. Empty or incomplete calls
  2. Exceeding Starknet's transaction size limits

Add validation before the iterator setup:

+            const MAX_CALLS: usize = 100; // Adjust based on Starknet's limits
+            if self.calls.len() < 2 {
+                return Err(anyhow!("At least one complete call (address and entrypoint) is required"));
+            }
+            if self.calls.len() > MAX_CALLS * 3 {
+                return Err(anyhow!("Number of calls exceeds maximum limit"));
+            }
+
             let mut arg_iter = self.calls.into_iter();

Likely invalid or redundant comment.

Comment on lines +139 to +146
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ohayo, sensei! Add safety check for calldata parsing

The calldata parsing loop could potentially continue indefinitely if a separator is missing. Additionally, there's no validation of calldata size.

Add safety checks:

                 let mut calldata = vec![];
+                let mut calldata_count = 0;
+                const MAX_CALLDATA_SIZE: usize = 1000; // Adjust based on requirements
                 for arg in &mut arg_iter {
+                    calldata_count += 1;
+                    if calldata_count > MAX_CALLDATA_SIZE {
+                        return Err(anyhow!("Calldata size exceeds maximum limit"));
+                    }
                     let arg = match arg.as_str() {
                         "/" | "-" | "\\" => break,
                         _ => calldata_decoder::decode_single_calldata(&arg)?,
                     };
                     calldata.extend(arg);
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
}
let mut calldata = vec![];
let mut calldata_count = 0;
const MAX_CALLDATA_SIZE: usize = 1000; // Adjust based on requirements
for arg in &mut arg_iter {
calldata_count += 1;
if calldata_count > MAX_CALLDATA_SIZE {
return Err(anyhow!("Calldata size exceeds maximum limit"));
}
let arg = match arg.as_str() {
"/" | "-" | "\\" => break,
_ => calldata_decoder::decode_single_calldata(&arg)?,
};
calldata.extend(arg);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants