Skip to content

feat(torii-indexer): transaction calls and outside calls - #3085

Merged
Larkooo merged 39 commits into
dojoengine:mainfrom
Larkooo:parsed-entrypoints
Mar 20, 2025
Merged

feat(torii-indexer): transaction calls and outside calls#3085
Larkooo merged 39 commits into
dojoengine:mainfrom
Larkooo:parsed-entrypoints

Conversation

@Larkooo

@Larkooo Larkooo commented Mar 7, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Introduced an enhanced caching mechanism to improve contract data retrieval speed.
    • Upgraded transaction processing to extract and store detailed function call information in a structured format.
    • Expanded the GraphQL API to support querying function call details, including pagination and ordering.
    • Added a new table to efficiently store and index transaction call records in the database.
    • Implemented support for complex SQL queries involving joins, enhancing data retrieval capabilities.
    • Added new functionality for retrieving entry point names from contract classes.
    • Introduced a structured representation for contract calls with new types for call details.
    • Enhanced error handling capabilities by integrating provider-related errors into the existing error structure.
    • Added a new dependency to facilitate project compilation and linking.
    • Improved modularity and error handling in token balance and transfer processing.
    • Enhanced transaction management capabilities within the executor module.
    • Introduced a new struct for managing transaction queries and improved message handling for transaction processing.
    • Added new methods and structures to support enhanced transaction processing logic and error handling.
    • Introduced new types for handling transaction and call data, improving the overall structure and maintainability of the code.

@Larkooo
Larkooo marked this pull request as ready for review March 14, 2025 10:58
@coderabbitai

coderabbitai Bot commented Mar 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Ohayo, sensei! This pull request introduces a caching mechanism for contract classes in the SQLite module via a new ContractClassCache struct. It also updates the transaction storage logic to directly parse calldata into structured function calls rather than converting it into a string. Additionally, the GraphQL schema is expanded with a new function call object and corresponding mapping, and a new SQL migration file is added to create a dedicated table for transaction calls.

Changes

File(s) Change Summary
crates/torii/sqlite/src/cache.rs
crates/torii/sqlite/src/lib.rs
Introduces ContractClassCache with methods new and get for caching contract classes. Updates store_transaction to accept multiple parameters instead of a Transaction object, allowing for direct handling of transaction data.
crates/torii/graphql/src/constants.rs
crates/torii/graphql/src/mapping.rs
crates/torii/graphql/src/object/transaction.rs
crates/torii/graphql/src/schema.rs
Adds new constants and mappings for function calls. Introduces CallObject with methods for resolving transactions and their associated calls. Updates import statements and GraphQL object registration to include the new function call object.
crates/torii/migrations/20250317023025_transaction_calls.sql Adds a new SQL migration file that creates the transaction_calls table with defined columns and indexes to optimize query performance.
crates/torii/sqlite/src/types.rs Introduces CallType enum and ParsedCall struct to represent contract call data.
crates/torii/indexer/src/engine.rs
crates/torii/indexer/src/processors/mod.rs
crates/torii/indexer/src/processors/store_transaction.rs
Updates Engine struct to include contract_class_cache. Modifies TransactionProcessor trait and StoreTransactionProcessor to utilize the cache for processing transactions, enhancing transaction handling logic.

Sequence Diagram(s)

sequenceDiagram
    participant C as Caller
    participant Cache as ContractClassCache
    participant P as Provider

    C ->> Cache: get(contract_address, block_id)
    alt Entry exists in cache
        Cache -->> C: Return cached ContractClass
    else Entry missing in cache
        Cache ->> P: Fetch contract hash for contract_address
        P -->> Cache: Return contract hash
        Cache ->> P: Fetch ContractClass using contract hash
        P -->> Cache: Return ContractClass
        Cache ->> Cache: Store ContractClass in cache
        Cache -->> C: Return ContractClass
    end
Loading
sequenceDiagram
    participant T as Transaction
    participant S as store_transaction Method
    participant CP as Call Parser
    participant DB as Database

    T ->> S: Invoke store_transaction(transaction_hash, sender_address, ...)
    S ->> CP: Parse calldata to extract FunctionCall details
    CP -->> S: Return lists of calls and outsideCalls
    S ->> DB: Insert transaction record with JSON serialized calls
    DB -->> S: Acknowledge insertion
Loading

Possibly related PRs

  • feat(torii-indexer): relation between txns and contracts #3055: The changes in the main PR introduce a new caching mechanism for contract classes and enhance transaction processing, while the retrieved PR modifies transaction processing methods to include contract addresses, indicating a direct relationship through the handling of contract-related data in transaction processing.

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 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
crates/torii/sqlite/src/cache.rs (1)

165-179: Error handling needs improvement in the get method.

Sensei, while the cache implementation is solid, I noticed you're using unwrap() on lines 175 and 176 which could cause panics if the provider operations fail. Consider proper error handling instead.

- let class_hash = self.provider.get_class_hash_at(block_id, contract_address).await.unwrap();
- let class = self.provider.get_class(block_id, class_hash).await.unwrap();
+ let class_hash = self.provider.get_class_hash_at(block_id, contract_address).await?;
+ let class = self.provider.get_class(block_id, class_hash).await?;
crates/torii/sqlite/src/lib.rs (1)

565-596: Consider extracting outside call parsing into a helper function.

Ohayo, sensei! The outside call parsing logic is duplicated for both v2 and v3 cases. Consider extracting this into a helper function to avoid code duplication.

+ fn parse_outside_calls(calldata: &[Felt], calldata_offset: usize, has_v3_formatting: bool) -> Vec<FunctionCall> {
+     let mut outside_calls = vec![];
+     let outside_calls_idx = if has_v3_formatting { calldata_offset + 5 } else { calldata_offset + 4 };
+     let outside_calls_len: usize = calldata[outside_calls_idx].try_into().unwrap_or(0);
+     
+     let offset_start = if has_v3_formatting { calldata_offset + 6 } else { calldata_offset + 5 };
+     
+     for _ in 0..outside_calls_len {
+         let to_offset = offset_start;
+         let selector_offset = to_offset + 1;
+         let calldata_offset = selector_offset + 2;
+         let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap_or(0);
+         
+         let outside_call = FunctionCall {
+             contract_address: calldata[to_offset],
+             entry_point_selector: calldata[selector_offset],
+             calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
+         };
+         outside_calls.push(outside_call);
+     }
+     
+     outside_calls
+ }

// Then in the code:
- if call.entry_point_selector == selector!("execute_from_outside_v3") {
-     let outside_calls_len: usize = calldata[calldata_offset + 5].try_into().unwrap();
-     for _ in 0..outside_calls_len {
-         let to_offset = calldata_offset + 6;
-         let selector_offset = to_offset + 1;
-         let calldata_offset = selector_offset + 2;
-         let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
-         let outside_call = FunctionCall {
-             contract_address: calldata[to_offset],
-             entry_point_selector: calldata[selector_offset],
-             calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
-         };
-         outside_calls.push(outside_call);
-     }
- } else if call.entry_point_selector == selector!("execute_from_outside_v2") {
-     // the execute_from_outside_v2 nonce is only a felt, thus we have a 4 offset
-     let outside_calls_len: usize = calldata[calldata_offset + 4].try_into().unwrap();
-     for _ in 0..outside_calls_len {
-         let to_offset = calldata_offset + 5;
-         let selector_offset = to_offset + 1;
-         let calldata_offset = selector_offset + 2;
-         let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
-         let outside_call = FunctionCall {
-             contract_address: calldata[to_offset],
-             entry_point_selector: calldata[selector_offset],
-             calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
-         };
-         outside_calls.push(outside_call);
-     }
+ if call.entry_point_selector == selector!("execute_from_outside_v3") {
+     outside_calls.extend(parse_outside_calls(calldata, calldata_offset, true));
+ } else if call.entry_point_selector == selector!("execute_from_outside_v2") {
+     outside_calls.extend(parse_outside_calls(calldata, calldata_offset, false));
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b83295 and ec99203.

📒 Files selected for processing (3)
  • crates/torii/migrations/20250307134638_transaction-calls.sql (1 hunks)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
  • crates/torii/sqlite/src/lib.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: ensure-windows
  • GitHub Check: test-hurl
  • GitHub Check: ensure-wasm
  • GitHub Check: dojo-core-test
  • GitHub Check: docs
  • GitHub Check: clippy
  • GitHub Check: dojo-examples-test
🔇 Additional comments (5)
crates/torii/migrations/20250307134638_transaction-calls.sql (1)

1-7: LGTM! The migration adds necessary columns for transaction analysis.

Ohayo, sensei! This migration looks well-structured, adding TEXT columns for storing parsed calls from transaction calldata and outside calls. This aligns perfectly with the PR objective to enhance transaction handling functionality.

crates/torii/sqlite/src/cache.rs (1)

154-163: Well-structured contract class cache implementation.

Ohayo, sensei! The ContractClassCache struct looks clean and follows good design patterns with proper generics constraints. Nice job using RwLock for thread safety!

crates/torii/sqlite/src/lib.rs (3)

14-15: LGTM! Imports updated for the new functionality.

Ohayo, sensei! The imports look good with the addition of FunctionCall and the selector macro.


524-532: LGTM! Updated transaction field access.

Sensei, you've correctly modified this section to directly use the calldata fields instead of converting them to strings right away.


602-614: LGTM! SQL query updated to handle new transaction data.

Nice work adding the new columns to the SQL query and properly serializing the calls data as JSON, sensei!

Comment thread crates/torii/sqlite/src/lib.rs Outdated
Comment on lines +553 to +563
for _ in 0..calls_len {
let to_offset = offset + 1;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();

let call = FunctionCall {
contract_address: calldata[to_offset],
entry_point_selector: calldata[selector_offset],
calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add safeguards against out-of-bounds access.

Sensei, this section of code calculates various offsets and accesses elements by index without verifying array bounds first. This could lead to panics if called with unexpected data.

Consider adding checks before accessing array elements:

  for _ in 0..calls_len {
      let to_offset = offset + 1;
      let selector_offset = to_offset + 1;
      let calldata_offset = selector_offset + 2;
+     
+     if selector_offset + 1 >= calldata.len() {
+         break; // or return an error
+     }
      let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+     
+     if calldata_offset + calldata_len > calldata.len() {
+         break; // or return an error
+     }

      let call = FunctionCall {
          contract_address: calldata[to_offset],
          entry_point_selector: calldata[selector_offset],
          calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
      };
📝 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
for _ in 0..calls_len {
let to_offset = offset + 1;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
let call = FunctionCall {
contract_address: calldata[to_offset],
entry_point_selector: calldata[selector_offset],
calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
};
for _ in 0..calls_len {
let to_offset = offset + 1;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
if selector_offset + 1 >= calldata.len() {
break; // or return an error
}
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
if calldata_offset + calldata_len > calldata.len() {
break; // or return an error
}
let call = FunctionCall {
contract_address: calldata[to_offset],
entry_point_selector: calldata[selector_offset],
calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
};

Comment thread crates/torii/sqlite/src/lib.rs Outdated
@codecov

codecov Bot commented Mar 14, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 4.70263% with 689 lines in your changes missing coverage. Please review.

Project coverage is 56.82%. Comparing base (5e0de11) to head (65cb243).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
.../torii/indexer/src/processors/store_transaction.rs 0.00% 153 Missing ⚠️
...ates/torii/graphql/src/object/erc/token_balance.rs 0.00% 119 Missing ⚠️
...tes/torii/graphql/src/object/erc/token_transfer.rs 0.00% 112 Missing ⚠️
crates/torii/graphql/src/object/transaction.rs 20.61% 104 Missing ⚠️
crates/torii/sqlite/src/cache.rs 4.91% 58 Missing ⚠️
crates/torii/graphql/src/object/mod.rs 0.00% 42 Missing ⚠️
crates/torii/graphql/src/query/data.rs 0.00% 37 Missing ⚠️
crates/torii/sqlite/src/executor/mod.rs 0.00% 36 Missing ⚠️
crates/torii/sqlite/src/lib.rs 0.00% 22 Missing ⚠️
crates/torii/sqlite/src/types.rs 0.00% 5 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3085      +/-   ##
==========================================
- Coverage   57.14%   56.82%   -0.33%     
==========================================
  Files         442      442              
  Lines       61142    61561     +419     
==========================================
+ Hits        34942    34979      +37     
- Misses      26200    26582     +382     

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

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/torii/graphql/src/mapping.rs (1)

67-71: Consider using camelCase for field names!

Ohayo sensei! The new FUNCTION_CALL_MAPPING fields use snake_case (contract_address, entry_point_selector, calldata), but GraphQL conventions and other mappings in this file use camelCase. Consider renaming these fields to maintain consistency:

-        (Name::new("contract_address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
-        (Name::new("entry_point_selector"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
+        (Name::new("contractAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
+        (Name::new("entryPointSelector"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between ec99203 and e3bdbb6.

📒 Files selected for processing (4)
  • crates/torii/graphql/src/constants.rs (1 hunks)
  • crates/torii/graphql/src/mapping.rs (3 hunks)
  • crates/torii/graphql/src/object/transaction.rs (1 hunks)
  • crates/torii/graphql/src/schema.rs (2 hunks)
🔇 Additional comments (8)
crates/torii/graphql/src/constants.rs (1)

33-33: LGTM! New type name constant follows convention.

Ohayo! The constant FUNCTION_CALL_TYPE_NAME is properly defined and follows the same naming pattern as other type names in this file. Good consistency, sensei!

crates/torii/graphql/src/schema.rs (2)

29-29: Import looks good!

Ohayo! The import for FunctionCallObject alongside TransactionObject is properly structured. Clean change, sensei!


138-138: Excellent addition to the objects list!

The FunctionCallObject is properly added to the predefined objects list following the same pattern as other Basic objects. This ensures it will be registered in the GraphQL schema correctly.

crates/torii/graphql/src/object/transaction.rs (3)

5-6: Import pattern looks good!

Ohayo! The constant imports are well-organized with the addition of FUNCTION_CALL_TYPE_NAME. Nicely done, sensei!


7-7: Proper mapping import!

Adding the import for FUNCTION_CALL_MAPPING follows the established patterns. Clean addition, sensei!


10-25: New FunctionCallObject implementation looks good!

Ohayo! The implementation of FunctionCallObject follows the existing pattern of other objects in this codebase. The naming is consistent with GraphQL conventions using camelCase for "functionCall".

crates/torii/graphql/src/mapping.rs (2)

7-7: Import addition is correct!

Ohayo! The import for FUNCTION_CALL_TYPE_NAME is properly added to the existing imports. Good organization, sensei!


86-93: LGTM! New fields for transaction calls look good!

Ohayo! The additions of calls and outsideCalls fields to the TRANSACTION_MAPPING correctly use the FUNCTION_CALL_MAPPING for their nested types. The naming uses camelCase which follows GraphQL conventions. Excellent work, sensei!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
crates/torii/sqlite/src/lib.rs (2)

582-597: ⚠️ Potential issue

Add safeguards against out-of-bounds access.

Sensei, this section of code calculates various offsets and accesses elements by index without verifying array bounds first. This could lead to panics if called with unexpected data.

Consider adding checks before accessing array elements:

  for _ in 0..calls_len {
      let to_offset = offset + 1;
      let selector_offset = to_offset + 1;
      let calldata_offset = selector_offset + 2;
+     
+     if selector_offset + 1 >= calldata.len() {
+         break; // or return an error
+     }
      let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+     
+     if calldata_offset + calldata_len > calldata.len() {
+         break; // or return an error
+     }

      let call = FunctionCall {
          contract_address: calldata[to_offset],
          entry_point_selector: calldata[selector_offset],
          calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
      };

582-634: ⚠️ Potential issue

Transaction calldata parsing logic needs better error handling.

Ohayo, sensei! The transaction parsing logic is intricate, but I'm concerned about the error handling. You're using unwrap() for array conversions and accessing elements without bounds checking, which could lead to runtime panics.

- let calls_len: usize = calldata[0].try_into().unwrap();
+ let calls_len: usize = calldata.get(0)
+     .ok_or_else(|| anyhow!("Calldata is empty"))?
+     .try_into()
+     .map_err(|_| anyhow!("Failed to parse calls length"))?;

// Add similar error handling for other unwraps and array accesses

Also, add checks before accessing array indices to ensure they're within bounds:

+ if calldata.len() <= selector_offset + 1 {
+     return Err(anyhow!("Calldata too short for selector_offset"));
+ }
  let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+ if calldata.len() < calldata_offset + calldata_len {
+     return Err(anyhow!("Calldata too short for calldata_len"));
+ }
🧹 Nitpick comments (1)
crates/torii/sqlite/src/lib.rs (1)

599-614: Replace magic numbers with named constants.

Ohayo! The code uses magic numbers like calldata_offset + 5 for offsets, making it difficult to understand the structure of the calldata. Consider defining named constants for these offsets to improve readability.

+     // Offsets in execute_from_outside_v3 calldata
+     const OUTSIDE_CALLS_LEN_OFFSET_V3: usize = 5;
+     const OUTSIDE_CALLS_START_OFFSET_V3: usize = 6;

      if call.entry_point_selector == selector!("execute_from_outside_v3") {
-         let outside_calls_len: usize = calldata[calldata_offset + 5].try_into().unwrap();
+         let outside_calls_len: usize = calldata[calldata_offset + OUTSIDE_CALLS_LEN_OFFSET_V3].try_into().unwrap();
          for _ in 0..outside_calls_len {
-             let to_offset = calldata_offset + 6;
+             let to_offset = calldata_offset + OUTSIDE_CALLS_START_OFFSET_V3;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 30423c9 and 270235f.

📒 Files selected for processing (2)
  • crates/torii/migrations/20250317023025_transaction_calls.sql (1 hunks)
  • crates/torii/sqlite/src/lib.rs (3 hunks)
🔇 Additional comments (8)
crates/torii/migrations/20250317023025_transaction_calls.sql (2)

1-9: Well-structured table design with appropriate constraints.

The table structure is well designed with the appropriate foreign key constraint linking transaction calls to transactions. The default value for call_type is a nice touch to maintain consistent data.


11-14: Good indexing strategy for query optimization.

You've created indexes on all the important columns that will be used in WHERE clauses. This will ensure optimal query performance when filtering by transaction hash, contract address, entry point selector, or call type.

crates/torii/sqlite/src/lib.rs (6)

14-15: LGTM! Appropriate imports added.

The additions of FunctionCall from starknet::core::types and the selector macro are necessary for the new transaction call parsing functionality.


524-525: Direct calldata handling improves data integrity.

Ohayo, sensei! By passing the raw calldata directly rather than converting it to a string representation, you're preserving the original data structure for later parsing. This is a good improvement for data integrity.

Also applies to: 532-533, 540-541


558-559: Good, still using string representation for storage.

While you're processing the raw calldata for the call parsing, you correctly maintain the string representation for the main transactions table. This ensures backward compatibility with existing queries.


577-580: Clean control flow for transaction type filtering.

The early return for non-INVOKE transactions keeps the code clean and avoids unnecessary processing.


637-650: LGTM! Clean insertion of transaction calls.

The insertion of call data into the transaction_calls table is well-implemented with proper SQL queries and argument handling.


652-666: LGTM! Good differentiation between call types.

Setting 'EXECUTE_FROM_OUTSIDE' as the call_type for outside calls helps distinguish them from regular calls in queries, which is valuable for filtering and analysis.

Comment thread crates/torii/migrations/20250317023025_transaction_calls.sql Outdated
Comment thread crates/torii/sqlite/src/lib.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
crates/torii/sqlite/src/lib.rs (2)

582-587: 🛠️ Refactor suggestion

Add error handling for calldata parsing

Sensei, the current implementation uses unwrap() which could panic if the calldata is malformed. Consider adding proper error handling here.

-let calls_len: usize = calldata[0].try_into().unwrap();
+let calls_len: usize = calldata.get(0)
+    .ok_or_else(|| anyhow!("Calldata is empty"))?
+    .try_into()
+    .map_err(|_| anyhow!("Failed to parse calls length"))?;

588-598: ⚠️ Potential issue

Add safeguards against out-of-bounds access

Ohayo, sensei! This section calculates various offsets and accesses elements without verifying array bounds, which could lead to panics with unexpected data.

for _ in 0..calls_len {
    let to_offset = offset + 1;
    let selector_offset = to_offset + 1;
    let calldata_offset = selector_offset + 2;
+     
+    if selector_offset + 1 >= calldata.len() {
+        return Err(anyhow!("Calldata too short for selector_offset"));
+    }
    let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+     
+    if calldata_offset + calldata_len > calldata.len() {
+        return Err(anyhow!("Calldata too short for calldata_len"));
+    }

    let call = FunctionCall {
        contract_address: calldata[to_offset],
        entry_point_selector: calldata[selector_offset],
        calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
    };
🧹 Nitpick comments (6)
crates/torii/graphql/src/mapping.rs (1)

89-96: Ohayo sensei! Confirm consistent usage of these new fields.
Adding both calls and outsideCalls is clear, but be sure the rest of the code—like resolvers and database queries—handles them in a way that clearly differentiates the two. If usage might overlap, consider a more descriptive naming convention.

crates/torii/migrations/20250317023025_transaction_calls.sql (1)

12-15: Ohayo sensei! Indexes look solid.
If queries frequently combine these columns, consider multi-column indexes in a future iteration to improve performance for more complex queries.

crates/torii/graphql/src/query/data.rs (2)

35-41: Ohayo sensei! Comprehensive join configuration struct.
In the future, you might add validations (e.g., verifying on_condition syntax) to catch potential errors early.


72-106: Ohayo sensei! Good unified approach to fetch a single row with joins.

  1. Watch out for SQL injection: the dynamic string interpolation is fine here if all user inputs are validated.
  2. The pipeline flagged formatting issues at lines 82 and 95. Please run rustfmt to clear them up.
🧰 Tools
🪛 GitHub Actions: ci

[error] 82-82: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 95-95: Rust formatting check failed. Please run 'rustfmt' to format the code.

crates/torii/graphql/src/object/transaction.rs (2)

156-159: Improve error handling for pagination parameters

Sensei, you're using unwrap_or and unwrap_or_else for pagination parameters, which is good for providing defaults. However, it would be even better to validate these parameters to ensure they're within reasonable ranges.

-let limit: u64 = extract(ctx.args.as_index_map(), "limit").unwrap_or(50);
-let offset: u64 = extract(ctx.args.as_index_map(), "offset").unwrap_or(0);
+let limit: u64 = extract(ctx.args.as_index_map(), "limit").unwrap_or(50).min(100); // Cap at 100
+let offset: u64 = extract(ctx.args.as_index_map(), "offset").unwrap_or(0);

-let order_by: String = extract(ctx.args.as_index_map(), "orderBy").unwrap_or_else(|_| "executed_at".to_string());
-let order_direction: String = extract(ctx.args.as_index_map(), "orderDirection").unwrap_or_else(|_| "DESC".to_string());
+// Validate order_by is a valid column name to prevent SQL injection
+let order_by: String = {
+    let column = extract(ctx.args.as_index_map(), "orderBy").unwrap_or_else(|_| "executed_at".to_string());
+    match column.as_str() {
+        "executed_at" | "transaction_hash" | "sender_address" | "block_number" => column,
+        _ => "executed_at".to_string(), // Default to a safe column if invalid
+    }
+};
+
+// Validate order_direction is either ASC or DESC
+let order_direction: String = {
+    let direction = extract(ctx.args.as_index_map(), "orderDirection").unwrap_or_else(|_| "DESC".to_string());
+    match direction.as_str() {
+        "ASC" | "DESC" => direction,
+        _ => "DESC".to_string(), // Default to DESC if invalid
+    }
+};

1-213: Fix Rust formatting issues

Ohayo, sensei! The pipeline has flagged multiple Rust formatting issues. Please run rustfmt on this file to resolve these formatting issues.

rustfmt crates/torii/graphql/src/object/transaction.rs
🧰 Tools
🪛 GitHub Actions: ci

[error] 1-1: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 8-8: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 63-63: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 72-72: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 94-94: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 103-103: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 113-113: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 122-122: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 151-151: Rust formatting check failed. Please run 'rustfmt' to format the code.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 270235f and 88db0a8.

📒 Files selected for processing (7)
  • crates/torii/graphql/src/constants.rs (2 hunks)
  • crates/torii/graphql/src/mapping.rs (3 hunks)
  • crates/torii/graphql/src/object/mod.rs (2 hunks)
  • crates/torii/graphql/src/object/transaction.rs (2 hunks)
  • crates/torii/graphql/src/query/data.rs (2 hunks)
  • crates/torii/migrations/20250317023025_transaction_calls.sql (1 hunks)
  • crates/torii/sqlite/src/lib.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/torii/graphql/src/constants.rs
🧰 Additional context used
🪛 GitHub Actions: ci
crates/torii/graphql/src/object/mod.rs

[error] 28-28: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 35-35: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 271-271: Rust formatting check failed. Please run 'rustfmt' to format the code.

crates/torii/sqlite/src/lib.rs

[warning] 580-580: Rust formatting issues detected. Please run 'rustfmt' to format the code.


[warning] 669-669: Rust formatting issues detected. Please run 'rustfmt' to format the code.


[warning] 686-686: Rust formatting issues detected. Please run 'rustfmt' to format the code.

crates/torii/graphql/src/query/data.rs

[error] 82-82: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 95-95: Rust formatting check failed. Please run 'rustfmt' to format the code.

crates/torii/graphql/src/object/transaction.rs

[error] 1-1: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 8-8: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 63-63: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 72-72: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 94-94: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 103-103: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 113-113: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 122-122: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 151-151: Rust formatting check failed. Please run 'rustfmt' to format the code.

🔇 Additional comments (15)
crates/torii/graphql/src/mapping.rs (1)

7-9: Ohayo sensei! Nice addition of the new imports.
They align well with the rest of the file’s constants.

crates/torii/graphql/src/object/mod.rs (2)

31-31: Ohayo sensei! Good import alignment.
These additional imports are consistent with the new join-based functionality introduced in the codebase.


35-35: Ohayo sensei! JoinConfig import looks correct.
Everything is properly set up to handle advanced join scenarios.

🧰 Tools
🪛 GitHub Actions: ci

[error] 35-35: Rust formatting check failed. Please run 'rustfmt' to format the code.

crates/torii/migrations/20250317023025_transaction_calls.sql (1)

1-10: Ohayo sensei! Double-check the foreign key referencing.
transaction_hash references transactions(id)—verify that the transactions table indeed uses id as a primary key for these hashes. If the primary key in transactions is actually transaction_hash, point your foreign key to the matching column.

crates/torii/graphql/src/query/data.rs (2)

43-49: Ohayo sensei! Creative approach for unsupported SQLite joins.
Mapping RIGHT JOIN and FULL JOIN to LEFT JOIN is practical. Just ensure the rest of the code and queries handle potential data disparities if you rely on right/full semantics.


51-60: Ohayo sensei! The as_sql method is straightforward.
If you add more join types or need more robust cross-database support, consider an adapter pattern for better maintainability.

crates/torii/sqlite/src/lib.rs (6)

14-15: New imports for improved code structure

Ohayo, sensei! Good job adding the FunctionCall type to the imports and including the selector macro. This properly organizes the dependencies needed for the new transaction call parsing functionality.


524-540: Calldata handling improvement

Nice optimization to pass calldata references directly instead of converting them to strings prematurely. This approach is more efficient and preserves the original data structure for proper parsing.


577-580: Early return for non-INVOKE transactions

Good pattern to check transaction type and return early if processing isn't needed. This makes the code path clearer and more efficient.

🧰 Tools
🪛 GitHub Actions: ci

[warning] 580-580: Rust formatting issues detected. Please run 'rustfmt' to format the code.


600-631: Refactor duplicated outside call parsing logic

The logic for parsing outside calls is duplicated between v2 and v3 handling with only small differences in offsets. This reduces maintainability.

+fn parse_outside_calls(
+    calldata: &[Felt],
+    calldata_offset: usize,
+    calls_len_offset: usize,
+    calls_start_offset: usize,
+) -> Result<Vec<(Felt, FunctionCall)>> {
+    let mut outside_calls = Vec::new();
+    let outside_calls_len: usize = calldata.get(calldata_offset + calls_len_offset)
+        .ok_or_else(|| anyhow!("Calldata too short"))?
+        .try_into()
+        .map_err(|_| anyhow!("Failed to parse outside calls length"))?;
+    
+    let mut offset = 0;
+    for _ in 0..outside_calls_len {
+        let to_offset = calldata_offset + calls_start_offset + offset;
+        let selector_offset = to_offset + 1;
+        let calldata_offset = selector_offset + 2;
+        
+        if selector_offset + 1 >= calldata.len() {
+            return Err(anyhow!("Calldata too short for outside call selector_offset"));
+        }
+        let calldata_len: usize = calldata[selector_offset + 1].try_into()
+            .map_err(|_| anyhow!("Failed to parse outside call calldata length"))?;
+        
+        if calldata_offset + calldata_len > calldata.len() {
+            return Err(anyhow!("Calldata too short for outside call calldata_len"));
+        }
+        
+        let outside_call = FunctionCall {
+            contract_address: calldata[to_offset],
+            entry_point_selector: calldata[selector_offset],
+            calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
+        };
+        outside_calls.push((calldata[0], outside_call));
+        offset += 3 + calldata_len;
+    }
+    
+    Ok(outside_calls)
+}

if call.entry_point_selector == selector!("execute_from_outside_v3") {
-    let outside_calls_len: usize = calldata[calldata_offset + 5].try_into().unwrap();
-    for _ in 0..outside_calls_len {
-        // ... duplicated logic ...
-    }
+    outside_calls.extend(parse_outside_calls(calldata, calldata_offset, 5, 6)?);
} else if call.entry_point_selector == selector!("execute_from_outside_v2") {
-    // ... duplicated logic ...
+    outside_calls.extend(parse_outside_calls(calldata, calldata_offset, 4, 5)?);
}

637-652: Good separation of transaction call storage

Ohayo! I like how you've separated the storage of the main calls into their own loop. This improves code readability and maintainability.

Some minor formatting issues were flagged in the pipeline. Remember to run rustfmt on the final code.


654-669: Good handling of outside calls with appropriate call type

Nice work differentiating between regular calls and outside calls by using the "EXECUTE_FROM_OUTSIDE" call type. This will be helpful for filtering and querying later.

Remember to fix the formatting issues flagged by the pipeline by running rustfmt.

🧰 Tools
🪛 GitHub Actions: ci

[warning] 669-669: Rust formatting issues detected. Please run 'rustfmt' to format the code.

crates/torii/graphql/src/object/transaction.rs (3)

1-14: Updated imports for new functionality

Ohayo, sensei! Good work organizing all necessary imports for the new GraphQL functionality. The inclusion of HashMap and Row imports will be useful for the new query processing.

🧰 Tools
🪛 GitHub Actions: ci

[error] 1-1: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 8-8: Rust formatting check failed. Please run 'rustfmt' to format the code.


15-30: Well-structured FunctionCallObject implementation

Nice job implementing the new FunctionCallObject struct with clear method names and references to constants. This follows the same pattern as other GraphQL objects in your system.


49-57: Updated resolvers to include new function call methods

Good refactoring to include the new resolver methods for transactions with calls. This maintains a clean API structure.

Comment thread crates/torii/graphql/src/mapping.rs Outdated
Comment on lines +69 to +74
pub static ref FUNCTION_CALL_MAPPING: TypeMapping = IndexMap::from([
(Name::new("contractAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("entryPointSelector"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("calldata"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))),
(Name::new("callType"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ohayo sensei! Consider using felt-like fields for contract addresses.
Currently, contractAddress and entryPointSelector are typed as strings. If these values represent FELT-252 addresses/selectors, you could store them as numeric or typed FELT strings to maintain consistency across the codebase and possibly prevent accidental misuse.

Comment thread crates/torii/graphql/src/object/mod.rs Outdated
Comment on lines +274 to +316
// Resolves single object queries with joins, returns current object of type type_name with related data
pub fn resolve_one_with_joins(
table_name: &str,
id_column: &str,
field_name: &str,
type_name: &str,
type_mapping: &TypeMapping,
joins: Vec<JoinConfig>,
select_columns: Option<Vec<String>>,
) -> Field {
let type_mapping = type_mapping.clone();
let table_name = table_name.to_owned();
let id_column = id_column.to_owned();
let joins = joins.to_owned();
let select_columns = select_columns.to_owned();
let argument = InputValue::new(id_column.to_case(Case::Camel), TypeRef::named_nn(TypeRef::ID));

Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
let type_mapping = type_mapping.clone();
let table_name = table_name.to_owned();
let id_column = id_column.to_owned();
let joins = joins.to_owned();
let select_columns = select_columns.to_owned();

FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id: String =
extract::<String>(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?;
let data = fetch_single_row_with_joins(
&mut conn,
&table_name,
&id_column,
&id,
joins,
select_columns,
).await?;
let model = value_mapping_from_row(&data, &type_mapping, false, true)?;
Ok(Some(Value::Object(model)))
})
})
.argument(argument)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ohayo sensei! Consider refactoring out common parts with resolve_one.
resolve_one_with_joins largely duplicates logic from resolve_one. Extracting shared parts (like argument handling or row retrieval) into helper functions could improve maintainability and reduce potential inconsistencies.

Comment thread crates/torii/graphql/src/object/transaction.rs Outdated
Comment thread crates/torii/graphql/src/object/transaction.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
crates/torii/sqlite/src/lib.rs (3)

587-598: ⚠️ Potential issue

Implement bounds checking for calldata access.

Ohayo, sensei! This section is still vulnerable to out-of-bounds accesses, just like in the previous review. The code accesses array elements without verifying they exist first.

  for _ in 0..calls_len {
      let to_offset = offset + 1;
      let selector_offset = to_offset + 1;
      let calldata_offset = selector_offset + 2;
+     
+     if to_offset >= calldata.len() || selector_offset >= calldata.len() {
+         break; // or return an error
+     }
      let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+     
+     if calldata_offset + calldata_len > calldata.len() {
+         break; // or return an error
+     }

      let call = FunctionCall {
          contract_address: calldata[to_offset],
          entry_point_selector: calldata[selector_offset],
          calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
      };

600-631: 🛠️ Refactor suggestion

Refactor duplicated outside call parsing logic.

Ohayo, sensei! There's significant duplication between the parsing logic for v2 and v3 outside calls. This is the same issue noted in the previous review.

Extract the common logic into a helper function:

+     fn parse_outside_calls(
+         calldata: &[Felt],
+         calldata_offset: usize,
+         calls_len_offset: usize,
+         calls_start_offset: usize,
+     ) -> Vec<(Felt, FunctionCall)> {
+         let mut outside_calls = Vec::new();
+         let outside_calls_len: usize = calldata[calldata_offset + calls_len_offset].try_into().unwrap();
+         
+         let mut offset = 0;
+         for _ in 0..outside_calls_len {
+             let to_offset = calldata_offset + calls_start_offset + offset;
+             let selector_offset = to_offset + 1;
+             let calldata_offset = selector_offset + 2;
+             let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+             
+             let outside_call = FunctionCall {
+                 contract_address: calldata[to_offset],
+                 entry_point_selector: calldata[selector_offset],
+                 calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
+             };
+             outside_calls.push((call.contract_address, outside_call));
+             offset += 3 + calldata_len;
+         }
+         
+         outside_calls
+     }

      if call.entry_point_selector == selector!("execute_from_outside_v3") {
-         let outside_calls_len: usize = calldata[calldata_offset + 5].try_into().unwrap();
-         for _ in 0..outside_calls_len {
-             // ... duplicated logic ...
-         }
+         outside_calls.extend(parse_outside_calls(calldata, calldata_offset, 5, 6));
      } else if call.entry_point_selector == selector!("execute_from_outside_v2") {
-         // ... duplicated logic ...
+         outside_calls.extend(parse_outside_calls(calldata, calldata_offset, 4, 5));
      }

582-635: 🛠️ Refactor suggestion

Improve error handling in transaction calls parsing.

Ohayo, sensei! The transaction parsing logic needs better error handling. You're using unwrap() for conversions and not handling potential errors.

- let calls_len: usize = calldata[0].try_into().unwrap();
+ let calls_len: usize = calldata.get(0)
+     .ok_or_else(|| anyhow!("Calldata is empty"))?
+     .try_into()
+     .map_err(|_| anyhow!("Failed to parse calls length"))?;

// Similarly for other unwraps:
- let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
+ let calldata_len: usize = calldata.get(selector_offset + 1)
+     .ok_or_else(|| anyhow!("Missing calldata length"))?
+     .try_into()
+     .map_err(|_| anyhow!("Failed to parse calldata length"))?;

This would make the function return meaningful errors instead of panicking.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 88db0a8 and 701d4cc.

📒 Files selected for processing (1)
  • crates/torii/sqlite/src/lib.rs (3 hunks)
🧰 Additional context used
🪛 GitHub Actions: ci
crates/torii/sqlite/src/lib.rs

[error] 580-580: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 669-669: Rust formatting check failed. Please run 'rustfmt' to format the code.


[error] 686-686: Rust formatting check failed. Please run 'rustfmt' to format the code.

🔇 Additional comments (4)
crates/torii/sqlite/src/lib.rs (4)

14-15: Added the new FunctionCall import and selector! macro.

Ohayo, sensei! I see you've added the FunctionCall type to your imports and included the selector! macro. These additions align perfectly with your new transaction call parsing feature.


524-540: Improved parameter handling for calldata.

Nice improvement here! You've changed from converting the calldata to a string to directly passing it as a reference, which is much more efficient for the parsing approach you're implementing.


582-586: Added variables for tracking function calls.

You're correctly initializing the needed data structures for parsing and storing both direct calls and outside calls.


638-652: Good implementation of transaction calls storage.

The SQLite insertion for regular function calls looks good. You've correctly mapped all necessary fields and used appropriate types.

Comment thread crates/torii/sqlite/src/lib.rs Outdated
Comment on lines +655 to +669
for outside_call in outside_calls {
self.executor.send(QueryMessage::other(
"INSERT OR IGNORE INTO transaction_calls (transaction_hash, contract_address, entry_point_selector, calldata, call_type, caller_address) \
VALUES (?, ?, ?, ?, ?, ?)"
.to_string(),
vec![
transaction_hash.clone(),
Argument::FieldElement(outside_call.1.contract_address),
Argument::FieldElement(outside_call.1.entry_point_selector),
Argument::String(felts_to_sql_string(&outside_call.1.calldata)),
Argument::String("EXECUTE_FROM_OUTSIDE".to_string()),
Argument::FieldElement(outside_call.0),
],
))?;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Need to fix formatting in outside calls storage.

The implementation for storing outside calls is correct, but the pipeline reports a formatting issue.

Run rustfmt on the file to fix the formatting issues on line 669.

🧰 Tools
🪛 GitHub Actions: ci

[error] 669-669: Rust formatting check failed. Please run 'rustfmt' to format the code.

Comment thread crates/torii/sqlite/src/lib.rs Outdated
Comment on lines +577 to +580
// Parse and store calls for INVOKE transactions
if transaction_type != "INVOKE" {
return Ok(());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Logic added to process only INVOKE transactions.

Good check to only process INVOKE transactions for call parsing. However, there's a formatting issue reported by the pipeline.

-        // Parse and store calls for INVOKE transactions
-        if transaction_type != "INVOKE" {
-            return Ok(());
-        }
+        // Parse and store calls for INVOKE transactions
+        if transaction_type != "INVOKE" {
+            return Ok(());
+        }

Run rustfmt to fix the formatting issues.

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

🧰 Tools
🪛 GitHub Actions: ci

[error] 580-580: Rust formatting check failed. Please run 'rustfmt' to format the code.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 701d4cc and 094bdd8.

📒 Files selected for processing (7)
  • crates/torii/indexer/src/engine.rs (5 hunks)
  • crates/torii/indexer/src/processors/mod.rs (3 hunks)
  • crates/torii/indexer/src/processors/store_transaction.rs (2 hunks)
  • crates/torii/migrations/20250317023025_transaction_calls.sql (1 hunks)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
  • crates/torii/sqlite/src/lib.rs (3 hunks)
  • crates/torii/sqlite/src/types.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/torii/migrations/20250317023025_transaction_calls.sql
🔇 Additional comments (21)
crates/torii/sqlite/src/types.rs (2)

230-243: Ohayo! Clean implementation of CallType enum.

The CallType enum with Display implementation follows best practices and provides a clear way to distinguish between different types of contract calls.


245-252: Well-structured ParsedCall struct, sensei!

This struct provides all the necessary fields to represent a contract call in a structured way. Good job including the caller_address which will be helpful for call attribution.

crates/torii/indexer/src/engine.rs (6)

26-26: Ohayo! Adding ContractClassCache import.

This import properly brings in the new caching functionality we're implementing.


233-233: Nice field addition for caching, sensei!

Adding the contract_class_cache field to the Engine struct will help optimize transaction processing by avoiding redundant contract class retrievals.


260-261: Clean provider initialization.

Wrapping the provider in Arc directly without redundant code.


265-266: Using cloned provider consistently.

Good practice to use the already Arc-wrapped provider.


278-279: Proper cache initialization, sensei!

Correctly initializing the contract_class_cache with the provider.


779-780: Passing cache reference to processor.

Correctly passing the contract class cache to the transaction processor.

crates/torii/indexer/src/processors/mod.rs (4)

2-2: Adding Arc import for shared caching.

Good addition of the Arc import needed for the contract class cache.


9-9: Importing the cache module.

Proper import of the ContractClassCache component.


95-95: Adding Debug trait bound, nice work sensei!

Adding std::fmt::Debug trait bound will help with debugging issues related to transaction processing.


106-107: Adding contract_class_cache parameter to the process method.

This change enables the TransactionProcessor to access cached contract class data, which will help with parsing and interpreting transaction calls.

crates/torii/sqlite/src/cache.rs (1)

157-166: Ohayo! Well-designed ContractClassCache struct.

The cache implementation with RwLock provides thread-safe access to cached contract classes, which is important in an asynchronous environment.

crates/torii/sqlite/src/lib.rs (3)

14-15: Ohayo sensei, imports look good!
No issues spotted.

Also applies to: 18-18


546-551: Ohayo sensei, storing associated contracts looks good!
This insertion uses two placeholders for two arguments, properly matching columns.


553-566: Ohayo sensei, storing calls in transaction_calls is well-structured!
The placeholders match the arguments. No issues spotted.

crates/torii/indexer/src/processors/store_transaction.rs (5)

5-10: Ohayo sensei, the new imports align perfectly with usage!
They are referenced properly within the file.


29-35: Ohayo sensei, good check on transaction type!
Skipping non-Invoke or non-L1Handler transactions ensures the rest of the code won’t process irrelevant data.


37-64: Ohayo sensei, the expanded match arms are clear, but watch for missing boundary checks.
If calldata is empty in Transaction::Invoke(V3), calling calldata[0] triggers a panic.

We previously advised adding out-of-bounds checks for calldata usage. Please ensure the code checks calldata.len() before indexing.


66-144: Ohayo sensei, calls parsing logic looks good but needs boundary checks.
Parsing calls_len and iterating could cause panics if calldata lacks sufficient elements. This has already been flagged in a past review.


146-158: Ohayo sensei, storing the transaction with associated calls is properly invoked.
No further concerns here.

Comment thread crates/torii/sqlite/src/cache.rs
Comment thread crates/torii/sqlite/src/cache.rs Outdated
Comment thread crates/torii/sqlite/src/lib.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/torii/indexer/src/processors/store_transaction.rs (2)

66-80: Good implementation of calldata parsing!

The implementation correctly extracts each call from the calldata array by calculating offsets and lengths. Using the contract_class_cache to fetch contract classes and resolve entrypoint names is a great approach to make the data more readable and useful.

Consider adding error handling for the try_into() unwrap operations to gracefully handle malformed calldata.

-        let calls_len: usize = calldata[0].try_into().unwrap();
+        let calls_len: usize = calldata[0].try_into().unwrap_or_else(|_| {
+            log::warn!("Failed to parse calls_len from calldata, defaulting to 0");
+            0
+        });

89-114: Ohayo! Nice handling of execute_from_outside_v3 calls, sensei!

The special handling for execute_from_outside_v3 calls correctly extracts the nested calls, which is crucial for tracking all transaction interactions. The code is well-structured with clear offset calculations.

However, there's some duplication between this block and the v2 handler. Consider refactoring to reduce duplication.

You could extract the common outside call parsing logic into a helper function to reduce duplication between v2 and v3 handlers:

fn parse_outside_calls(
    calls: &mut Vec<ParsedCall>,
    calldata: &[Felt],
    base_offset: usize,
    calls_len_offset: usize,
    caller_address: Felt,
    block_number: u64,
    contract_class_cache: &ContractClassCache<P>
) -> Result<(), Error> {
    let outside_calls_len: usize = calldata[base_offset + calls_len_offset].try_into().unwrap();
    let start_offset = base_offset + calls_len_offset + 1;
    
    for _ in 0..outside_calls_len {
        // Extract and push outside calls (existing logic)
        // ...
    }
    
    Ok(())
}

Then call it with appropriate offsets for v3 and v2.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 094bdd8 and c62243c.

📒 Files selected for processing (1)
  • crates/torii/indexer/src/processors/store_transaction.rs (3 hunks)
🔇 Additional comments (8)
crates/torii/indexer/src/processors/store_transaction.rs (8)

5-10: Ohayo! Nice job with the imports, sensei!

The new imports are well-organized and clearly reflect the new functionality being added. The addition of ContractClassCache and related imports aligns perfectly with the PR objective of implementing transaction calls parsing.


19-30: Method signature update looks good, sensei!

Adding the contract_class_cache parameter to the process method ensures that contract class information can be retrieved efficiently during transaction processing, which is essential for resolving entrypoint names from selectors.


31-35: Transaction type matching is well-implemented!

The matching of transaction types to identify "INVOKE" and "L1_HANDLER" transactions is clean and provides early return for unsupported types, which is a good programming practice.


37-64: Excellent transaction data extraction, sensei!

You've handled all the different transaction types (InvokeV3, InvokeV1, L1Handler) thoroughly, extracting the necessary fields from each. The pattern matching is clean and comprehensive.

I particularly appreciate the explicit handling of fields that don't exist in certain transaction types (using Felt::ZERO for missing max_fee and empty vector for missing signature).


81-88: ParsedCall struct creation is well-structured!

Creating a ParsedCall object with the extracted information is a clean approach. The fields are properly populated with the relevant data from the transaction.


114-140: Good handling of execute_from_outside_v2 calls!

The implementation correctly handles the v2 format which has a different offset structure. The comment explaining the difference is helpful for understanding the code.

As mentioned for the v3 handler, consider refactoring to reduce duplication with the v3 logic.


141-144: Logical call addition and offset update, sensei!

Adding the main call to the calls vector after processing any outside calls is the correct approach. The offset calculation for moving to the next call in the calldata is accurate.


146-158: Store transaction call looks good!

The method signature for db.store_transaction has been updated to include the new calls parameter, ensuring the parsed call data is stored in the database.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (3)
crates/torii/indexer/src/processors/store_transaction.rs (3)

94-151: Be careful with hard-coded offsets in outside calls parsing, sensei.

While the implementation for both execute_from_outside_v3 and execute_from_outside_v2 looks thorough, the hard-coded offsets (like calldata_offset + 5 and calldata_offset + 4) make the code somewhat brittle. Consider extracting these magic numbers into named constants or calculating them based on the calldata structure to improve maintainability.

- let outside_calls_len: usize = calldata[calldata_offset + 5].try_into().unwrap();
+ // V3 structure: [max_amount, nonce_low, nonce_high, block_timestamp, expiration_timestamp, calls_len, ...]
+ const V3_CALLS_LEN_OFFSET: usize = 5;
+ let outside_calls_len: usize = calldata[calldata_offset + V3_CALLS_LEN_OFFSET].try_into().unwrap();

Similar refactoring could be applied to other offsets in the code.


152-156: Be mindful of the loop structure and offset calculation, sensei.

The offset calculation seems accurate but can be error-prone when the calldata structure changes. It would be good to add a comment explaining the calldata structure or the logic behind the calculation for future reference.


94-151: Consider refactoring the duplicate code in handling outside calls, sensei.

The code for handling execute_from_outside_v3 and execute_from_outside_v2 is very similar with only slight differences in offsets. This could be refactored into a helper function to reduce duplication and improve maintainability.

fn process_outside_calls(
    calldata: &[Felt],
    base_offset: usize,
    calls_len_offset: usize,
    start_offset: usize,
    block_number: u64,
    caller_address: Felt,
    contract_class_cache: &ContractClassCache<P>,
    calls: &mut Vec<ParsedCall>,
) -> Result<(), Error> {
    let outside_calls_len: usize = calldata[base_offset + calls_len_offset].try_into().unwrap();
    for _ in 0..outside_calls_len {
        let to_offset = base_offset + start_offset;
        let selector_offset = to_offset + 1;
        // Rest of the implementation...
    }
    Ok(())
}

// Then call it like:
if call.entrypoint == "execute_from_outside_v3" {
    process_outside_calls(calldata, calldata_offset, 5, 6, block_number, call.contract_address, contract_class_cache, &mut calls)?;
} else if call.entrypoint == "execute_from_outside_v2" {
    process_outside_calls(calldata, calldata_offset, 4, 5, block_number, call.contract_address, contract_class_cache, &mut calls)?;
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 064a74f and dc0d657.

📒 Files selected for processing (1)
  • crates/torii/indexer/src/processors/store_transaction.rs (3 hunks)
🔇 Additional comments (4)
crates/torii/indexer/src/processors/store_transaction.rs (4)

5-7: Ohayo! The updated imports and method signature look good.

The new parameter contract_class_cache along with the necessary imports for ContractClassCache, get_entrypoint_name_from_class, ParsedCall, and other types are well organized and match the implementation needs.

Also applies to: 9-10, 19-21, 31-32


33-67: Nice match pattern implementation for transaction types, sensei!

The extraction of common transaction fields is well-structured, with proper handling of different transaction variants. I like how you've organized the code to handle both InvokeTransaction (V1 and V3) and L1Handler transactions, with appropriate default values for missing fields.


68-93: The function call parsing logic is well implemented!

The implementation correctly handles the calldata structure for invoke transactions, calculating offsets properly and extracting contract addresses and entrypoints. Using the contract class cache to resolve the human-readable entrypoint name is a nice touch for improved debugging and readability.


158-170: The updates to db.store_transaction look good!

The transaction storage function is now called with the parsed calls, which aligns with the purpose of this PR. This allows storing structured function calls instead of just raw calldata.

Comment on lines +68 to +156
let mut calls: Vec<ParsedCall> = vec![];

if transaction_type == "INVOKE" {
let calls_len: usize = calldata[0].try_into().unwrap();
let mut offset = 0;
for _ in 0..calls_len {
let to_offset = offset + 1;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
let contract_address = calldata[to_offset];
let contract_class = contract_class_cache
.get(contract_address, BlockId::Number(block_number))
.await?;
let entrypoint =
get_entrypoint_name_from_class(&contract_class, calldata[selector_offset])
.unwrap_or(format!("{:#x}", calldata[selector_offset]));

let call = ParsedCall {
contract_address,
entrypoint,
calldata: calldata[calldata_offset..calldata_offset + calldata_len].to_vec(),
call_type: CallType::Execute,
caller_address: sender_address,
};

if call.entrypoint == "execute_from_outside_v3" {
let outside_calls_len: usize =
calldata[calldata_offset + 5].try_into().unwrap();
for _ in 0..outside_calls_len {
let to_offset = calldata_offset + 6;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
let contract_address = calldata[to_offset];
let contract_class = contract_class_cache
.get(contract_address, BlockId::Number(block_number))
.await?;
let entrypoint = get_entrypoint_name_from_class(
&contract_class,
calldata[selector_offset],
)
.unwrap_or(format!("{:#x}", calldata[selector_offset]));

let outside_call = ParsedCall {
contract_address,
entrypoint,
calldata: calldata[calldata_offset..calldata_offset + calldata_len]
.to_vec(),
call_type: CallType::ExecuteFromOutside,
caller_address: call.contract_address,
};
calls.push(outside_call);
}
} else if call.entrypoint == "execute_from_outside_v2" {
// the execute_from_outside_v2 nonce is only a felt, thus we have a 4 offset
let outside_calls_len: usize =
calldata[calldata_offset + 4].try_into().unwrap();
for _ in 0..outside_calls_len {
let to_offset = calldata_offset + 5;
let selector_offset = to_offset + 1;
let calldata_offset = selector_offset + 2;
let calldata_len: usize = calldata[selector_offset + 1].try_into().unwrap();
let contract_address = calldata[to_offset];
let contract_class = contract_class_cache
.get(contract_address, BlockId::Number(block_number))
.await?;
let entrypoint = get_entrypoint_name_from_class(
&contract_class,
calldata[selector_offset],
)
.unwrap_or(format!("{:#x}", calldata[selector_offset]));

let outside_call = ParsedCall {
contract_address,
entrypoint,
calldata: calldata[calldata_offset..calldata_offset + calldata_len]
.to_vec(),
call_type: CallType::ExecuteFromOutside,
caller_address: call.contract_address,
};
calls.push(outside_call);
}
}

calls.push(call);
offset += 3 + calldata_len;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Good implementation of transaction call parsing, but needs bounds checking, sensei.

The current implementation accesses array elements directly without bounds checking, which could lead to panics if the calldata is malformed or shorter than expected. Consider adding bounds checking before accessing array elements or use safe access methods like get().


🏁 Script executed:

#!/bin/bash
# Check if there are any existing error handling for calldata access in the codebase
rg "unwrap\(\)" --type rust crates/torii/indexer/src/processors/ -A 1 -B 1

Length of output: 4535


Ohayo sensei, let's add those bounds checks!

The transaction call parsing logic in crates/torii/indexer/src/processors/store_transaction.rs is structured nicely. However, as it stands, it directly indexes into the calldata array without verifying that the indices are within range. This approach (using constructs like calldata[0].try_into().unwrap()) could lead to panics if the input calldata is malformed or shorter than expected. While other modules in the codebase also use similar unwrap calls, enhancing safety here would be beneficial for robustness.

  • Please consider:
    • Adding explicit bounds checking before each array access (or using safe methods like .get()).
    • Revisiting the offset calculations in both the primary and nested loops to ensure they don't exceed calldata’s length.

Comment thread crates/torii/indexer/src/processors/store_transaction.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
crates/torii/graphql/src/object/transaction.rs (1)

114-166: ⚠️ Potential issue

Ohayo sensei, watch out for SQL injection in LIKE clause!
Embedding {transaction_hash} directly in the string can be risky. Consider parameterizing the pattern (e.g., using et.event_id LIKE '%' || ? || '%') for safety.

🧹 Nitpick comments (2)
crates/torii/graphql/src/object/erc/token_transfer.rs (1)

263-382: Ohayo sensei, commendable modular design!
token_transfer_mapping_from_row unifies parsing logic and error handling across all token types. However, consider splitting out sub-logic (e.g., metadata parsing) into helper functions for improved readability.

crates/torii/graphql/src/object/erc/token_balance.rs (1)

319-448: Ohayo sensei, robust token balance mapping!
token_balance_mapping_from_row handles multiple ERC types elegantly. If it grows further, consider extracting metadata parsing into helper methods for clarity.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 6883145 and 0dd0bc0.

📒 Files selected for processing (4)
  • crates/torii/graphql/src/constants.rs (2 hunks)
  • crates/torii/graphql/src/object/erc/token_balance.rs (2 hunks)
  • crates/torii/graphql/src/object/erc/token_transfer.rs (4 hunks)
  • crates/torii/graphql/src/object/transaction.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/torii/graphql/src/constants.rs
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/graphql/src/object/erc/token_transfer.rs (4)
crates/torii/graphql/src/object/erc/token_balance.rs (2) (2)
  • row (335:335)
  • row (391:391)
crates/torii/indexer/src/engine.rs (1) (1)
  • get_transaction_hash_from_event_id (929:931)
crates/torii/sqlite/src/executor/erc.rs (2) (2)
  • token_id (72:72)
  • token_id (109:109)
crates/torii/sqlite/src/types.rs (1) (1)
  • from_str (195:204)
🔇 Additional comments (8)
crates/torii/graphql/src/object/erc/token_transfer.rs (3)

21-21: Ohayo sensei, nice import alignment!
Bringing in Erc1155Token and Erc721Token is helpful for handling multiple ERC token types with clarity.


245-253: Ohayo sensei, great error-handling approach!
Continuing after a failed row transformation ensures partial data is still processed while logging issues for debugging.


394-394: Ohayo sensei, good call on making TransferQueryResultRaw public!
This change improves cross-module accessibility and reuse without duplicating structs.

crates/torii/graphql/src/object/erc/token_balance.rs (2)

301-310: Ohayo sensei, nice integration of the mapping function!
Invoking token_balance_mapping_from_row here streamlines the transformation and centralizes error handling.


312-317: Ohayo sensei, succinct connection construction!
Returning early with the connection object is straightforward and reduces clutter in the calling code.

crates/torii/graphql/src/object/transaction.rs (3)

19-20: Ohayo sensei, neat introduction of CallObject!
Defining a dedicated struct keeps calls organized and flexible for future expansions.


22-38: Ohayo sensei, well-structured BasicObject implementation!
Providing related_fields as None keeps CallObject lean and consistent until additional relationships are needed.


55-55: Ohayo sensei, good combination of fields!
Exposing calls and tokenTransfers at the transaction level enhances discoverability of contextual data.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
crates/torii/sqlite/src/types.rs (1)

254-268: Consider a numeric type for block_number instead of String.

Currently, storing block_number as a String might introduce overhead in parsing and data validation. Converting it to an integer-based type (e.g., i64) could be helpful, if feasible.

crates/torii/sqlite/src/executor/mod.rs (1)

451-476: Ohayo sensei, storing transaction data and calls in a single transaction is sound.

One optimization to consider is a bulk insert if there are many calls, which can improve performance. Otherwise, logic is straightforward and correct.

crates/torii/sqlite/src/cache.rs (2)

157-162: Ohayo sensei, consider adding bounds to your cache.
Currently, the cache can grow unbounded, potentially leading to large memory usage in production. An eviction or size-limiting strategy could help.


164-196: Check handling of multiple block IDs, sensei.
Because the cache stores classes keyed only by address, subsequent calls with different block_id values for the same address might reuse outdated data. Ensure that this fallback to BlockTag::Pending and ignoring block ID variations is intentional for your scenario.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd0bc0 and 2980f7e.

📒 Files selected for processing (4)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (8 hunks)
  • crates/torii/sqlite/src/lib.rs (2 hunks)
  • crates/torii/sqlite/src/types.rs (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/sqlite/src/lib.rs (1)
crates/torii/sqlite/src/utils.rs (2) (2)
  • felts_to_sql_string (32-35)
  • utc_dt_string_from_timestamp (28-30)
🔇 Additional comments (19)
crates/torii/sqlite/src/types.rs (2)

229-244: Ohayo sensei, this enum addition looks great!

Nice and concise implementation with matching Display variants. No issues spotted here.


245-252: Ohayo sensei, this struct is clear and straightforward!

The fields cover all necessary data for a parsed call, and the design is readable.

crates/torii/sqlite/src/executor/mod.rs (9)

1-1: Ohayo sensei, new import looks fine!


11-11: Ohayo sensei, no concerns about this addition.


27-27: Ohayo sensei, importing ParsedCall and Transaction is appropriate.


29-29: No issues with these utility imports.


54-54: BrokerMessage variant inclusion looks good!


108-113: Ohayo sensei, this struct effectively captures new transaction data!


116-116: Enum variant addition is consistent with the new struct.


141-141: Renaming to avoid conflicts is a good call.


874-874: Publishing the transaction as a broker message is clearly integrated.

crates/torii/sqlite/src/lib.rs (5)

13-13: Ohayo sensei, this import aligns perfectly with the new transaction logic.


15-15: No issues with this import.


18-18: Importing ParsedCall is consistent with usage in store_transaction.


507-520: Function signature is cohesive and well-defined.

The chosen parameters cover all crucial transaction fields.


522-544: Ohayo sensei, the query logic is correctly aligned with the arguments.

The column-argument mapping matches, preventing accidental shifts. Good job!

crates/torii/sqlite/src/cache.rs (3)

2-2: Ohayo sensei, nice usage of Arc!
This import facilitates shared ownership of your provider instance across async tasks.


7-12: Ohayo sensei, these imports look solid for contract interactions.
They neatly cover the essentials for fetching contract classes and handling provider errors.


198-257: Ohayo sensei, unwrap() usage here can still cause panics.
This duplicates a prior comment regarding unwrapping at lines 225 and 249. Consider graceful error handling or returning None if parsing fails.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
crates/torii/graphql/src/object/erc/token_balance.rs (1)

326-452: Ohayo sensei! Well-structured token mapping function with robust error handling.

The extracted token_balance_mapping_from_row function provides a centralized place for token type conversion logic. The error handling is thorough, especially for metadata parsing.

One small improvement could be to use more descriptive variable names for clarity in the token_id splitting operations, particularly for ERC721 and ERC1155 sections.

- let token_id = row.token_id.split(':').collect::<Vec<&str>>();
- if token_id.len() != 2 {
-     return Err(format!("Invalid token_id format: {}", row.token_id));
- }
+ let token_id_parts = row.token_id.split(':').collect::<Vec<&str>>();
+ if token_id_parts.len() != 2 {
+     return Err(format!("Invalid token_id format (expected 'contract:id'): {}", row.token_id));
+ }
+ let contract_part = token_id_parts[0];
+ let id_part = token_id_parts[1];
crates/torii/graphql/src/object/transaction.rs (1)

143-145: Consider using a prepared statement for consistency

Ohayo, sensei! While this query isn't vulnerable to SQL injection since it only uses constants, consider using a style consistent with other queries for better maintainability.

-                    let query = &format!(
-                        "SELECT * FROM {TRANSACTION_CALLS_TABLE} WHERE transaction_hash = ?"
-                    );
+                    let query = &format!(
+                        "SELECT * FROM [{}] WHERE transaction_hash = ?", TRANSACTION_CALLS_TABLE
+                    );

This makes the query format consistent with other queries in the codebase using table name in square brackets.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 2980f7e and 61102d3.

📒 Files selected for processing (10)
  • crates/torii/graphql/src/mapping.rs (1 hunks)
  • crates/torii/graphql/src/object/erc/token_balance.rs (2 hunks)
  • crates/torii/graphql/src/object/erc/token_transfer.rs (4 hunks)
  • crates/torii/graphql/src/object/mod.rs (2 hunks)
  • crates/torii/graphql/src/object/transaction.rs (3 hunks)
  • crates/torii/graphql/src/query/data.rs (2 hunks)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (8 hunks)
  • crates/torii/sqlite/src/lib.rs (2 hunks)
  • crates/torii/sqlite/src/types.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/torii/graphql/src/object/erc/token_transfer.rs
  • crates/torii/sqlite/src/types.rs
🧰 Additional context used
🧬 Code Definitions (4)
crates/torii/graphql/src/object/mod.rs (1)
crates/torii/graphql/src/query/data.rs (11) (11)
  • data (197-197)
  • data (198-198)
  • data (223-223)
  • data (224-224)
  • data (227-227)
  • data (228-228)
  • count_rows (11-26)
  • fetch_multiple_rows (109-244)
  • fetch_single_row (62-70)
  • fetch_single_row_with_joins (72-106)
  • joins (87-96)
crates/torii/sqlite/src/lib.rs (2)
crates/torii/sqlite/src/executor/mod.rs (2) (2)
  • new (166-168)
  • new (241-267)
crates/torii/sqlite/src/utils.rs (2) (2)
  • felts_to_sql_string (32-35)
  • utc_dt_string_from_timestamp (28-30)
crates/torii/graphql/src/object/transaction.rs (2)
crates/torii/graphql/src/object/erc/token_transfer.rs (8) (8)
  • token_transfer_mapping_from_row (264-383)
  • name (31-33)
  • type_name (35-37)
  • type_mapping (39-41)
  • ctx (57-57)
  • extract (59-59)
  • row (289-289)
  • row (331-331)
crates/torii/graphql/src/object/mod.rs (16) (16)
  • resolve_many (321-374)
  • resolve_one (246-273)
  • name (46-46)
  • type_name (49-49)
  • type_mapping (52-52)
  • ctx (84-84)
  • ctx (109-109)
  • ctx (123-123)
  • ctx (148-148)
  • ctx (161-161)
  • ctx (190-190)
  • ctx (264-264)
  • ctx (301-301)
  • ctx (339-339)
  • extract (266-266)
  • extract (303-303)
crates/torii/graphql/src/object/erc/token_balance.rs (8)
crates/torii/graphql/src/object/erc/token_transfer.rs (2) (2)
  • row (289-289)
  • row (331-331)
crates/torii/graphql/src/object/erc/erc_token.rs (2) (2)
  • row (597-597)
  • row (639-639)
crates/torii/graphql/src/object/connection/mod.rs (2) (2)
  • row (138-138)
  • row (139-139)
crates/torii/graphql/src/query/mod.rs (7) (7)
  • row (123-123)
  • row (125-125)
  • row (310-310)
  • row (312-312)
  • row (319-320)
  • row (326-326)
  • e (67-81)
crates/torii/grpc/src/server/mod.rs (3) (3)
  • row (1130-1130)
  • row (1131-1135)
  • value (121-121)
crates/torii/sqlite/src/executor/mod.rs (2) (2)
  • None (300-300)
  • None (848-848)
crates/torii/sqlite/src/executor/erc.rs (2) (2)
  • token_id (72-72)
  • token_id (109-109)
crates/torii/sqlite/src/types.rs (1) (1)
  • from_str (195-204)
🔇 Additional comments (29)
crates/torii/graphql/src/mapping.rs (1)

67-74: Ohayo sensei! Consider using felt-like fields for contract addresses.

Currently, contractAddress and entrypoint are typed as strings. If these values represent FELT-252 addresses/selectors, you could store them as numeric or typed FELT strings to maintain consistency across the codebase (like you did in other mappings such as TRANSACTION_MAPPING).

-        (Name::new("contractAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
-        (Name::new("entrypoint"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
+        (Name::new("contractAddress"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string()))),
+        (Name::new("entrypoint"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string()))),
crates/torii/graphql/src/object/mod.rs (1)

275-318: Ohayo sensei! The implementation looks good but consider refactoring out common parts with resolve_one.

The new function resolve_one_with_joins largely duplicates logic from resolve_one. Consider extracting shared parts (like argument handling or field creation) into helper functions to improve maintainability and reduce potential inconsistencies.

crates/torii/graphql/src/query/data.rs (1)

35-60: Ohayo sensei! Clean implementation of join types and configuration.

The JoinType enum and JoinConfig struct are well-designed and provide a flexible way to configure SQL joins. I appreciate the handling of SQLite's limitations for RIGHT and FULL joins in the as_sql method. This makes the API more forgiving for users who might not be aware of SQLite's limitations.

crates/torii/graphql/src/object/erc/token_balance.rs (2)

147-159: Ohayo sensei! Good refactoring of token balance logic for subscriptions.

The refactoring to use the centralized token_balance_mapping_from_row function improves code maintainability and consistency. The error handling with warning logs is also beneficial for debugging.


307-316: Ohayo sensei! Good refactoring of token balance output logic.

Extracting the token mapping logic into a separate function makes the code more maintainable and easier to understand. The improved error handling with warning logs helps debugging potential issues.

crates/torii/graphql/src/object/transaction.rs (5)

22-41: Ohayo! New CallObject implementation looks good!

The new CallObject struct and its implementation of the BasicObject trait follow the established pattern in the codebase. The type mappings are properly referenced from constants.


58-60: Related fields implementation looks clean, sensei!

Good addition of both call and token transfer fields to the transaction object, making the GraphQL API more feature-rich.


84-112: Nice subscription implementation!

The subscription field for transactions with optional hash filtering is well implemented. The filter_map pattern allows for efficient filtering of transaction events.


115-131: Complete value mapping for transaction fields!

All the necessary transaction fields are correctly mapped to GraphQL field names with appropriate types.


133-163: Good calls field implementation, sensei!

The implementation fetches and maps transaction calls correctly. The query uses proper parameter binding with the ? placeholder, which is secure against SQL injection.

crates/torii/sqlite/src/cache.rs (6)

2-2: Ohayo sensei, this import looks correct.
No concerns here.


7-12: Ohayo sensei, these new imports are well-organized.
Everything seems in order.


157-163: Ohayo sensei, nice concurrency setup.
Storing (Felt, ContractClass) in an RwLock<HashMap<...>> is straightforward. Consider memory usage if the cache grows large.


164-168: Ohayo sensei, neat constructor for ContractClassCache.
It cleanly injects the provider and read/write lock.


198-254: Ohayo sensei, consider handling .unwrap() safely.
Calls to .unwrap() can panic on invalid JSON or selector mismatch. Returning None or an error is safer.

- let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi).unwrap();
+ let abi: Vec<AbiEntry> = match serde_json::from_str(&sierra.abi) {
+     Ok(abi) => abi,
+     Err(_) => return None,
+ };

- if get_selector_from_name(&function.name).unwrap() == selector
+ if get_selector_from_name(&function.name).ok()? == selector

169-195:

Details

❓ Verification inconclusive

Ohayo sensei, verify cache key usage with different blocks.
The get method stores contract classes keyed only by contract address, ignoring block_id. This may lead to returning stale data if a contract address differs across blocks.

Here’s a script to see if references exist regarding multi-block caching strategy:


🏁 Script executed:

#!/bin/bash
rg --context 5 "multi-block" 

Length of output: 28


Ohayo, sensei! Quick heads-up on the caching strategy:
The get method currently caches contract classes using only the contract address and ignores the block_id. This design may lead to serving stale data in cases where a contract’s class could differ between blocks. The grep search for "multi-block" didn't yield any supporting evidence for a multi-block caching strategy elsewhere in the codebase, which suggests this might be an oversight.

  • Please double-check whether the intention was to cache solely based on contract address.
  • If contract variants across blocks are valid, consider incorporating block_id into the cache key to avoid stale data.
crates/torii/sqlite/src/executor/mod.rs (8)

1-1: Ohayo sensei, this addition looks good!
No concerns with importing HashSet. It's neatly used for storing distinct contract addresses.


11-11: Ohayo sensei, no issues found here!
Aliasing the Transaction type to SqlxTransaction helps avoid naming collisions. Continue.


27-27: No immediate concerns with the new imports.
Introducing ParsedCall is a welcome addition for structured call data handling.


29-29: Neat usage of helpers.
felt_to_sql_string and felts_to_sql_string are consistent with the existing code. Good to see them used here.


108-112: Ohayo sensei, nice addition of StoreTransactionQuery.
Encapsulating the set of contract addresses and parsed calls in one struct clarifies the code’s intent. Consider including other relevant transaction metadata if needed in future expansions.


116-116: Enum variant addition looks good.
Adding StoreTransaction(StoreTransactionQuery) is consistent with the rest of the QueryType enum usage.


141-141: Smooth transition to SqlxTransaction.
Renaming prevents confusion with your internal Transaction type while preserving clarity.


451-489: Ohayo sensei, solid handling of the new StoreTransaction variant!

  1. The logic for inserting into transaction_contract and transaction_calls is cohesive and straightforward.
  2. Using INSERT OR IGNORE prevents duplicates, but confirm that ignoring collisions is desired behavior. If updates are ever needed, consider using INSERT ... ON CONFLICT ... DO UPDATE.
  3. Fetching and using the newly inserted transaction row is well-managed. The error handling with .with_context(...) is helpful for debugging.
  4. Pushing BrokerMessage::Transaction to the queue is a good approach for decoupling.

Looking great overall!

Do you want to confirm that collisions for the tables transaction_contract and transaction_calls are never expected to require subsequent updates? If so, verifying no manual updates are needed might be wise.

crates/torii/sqlite/src/lib.rs (5)

13-13: Imports look neat.
No issues with referencing StoreTransactionQuery, Felt, and the new ParsedCall.

Also applies to: 15-15, 18-18


509-519: Ohayo sensei, good signature for your new function parameters!
Explicit fields like transaction_hash, sender_address, and nonce make everything more transparent. This design is more flexible than passing a single Transaction object.


521-522: Nice comment clarifying the insertion.
The purposeful approach of ignoring existing records might be fine if the transaction data won't change.


528-538: Double usage of transaction_hash for both id and transaction_hash columns is now aligned.

  1. The argument count matches the number of columns.
  2. Reusing the same transaction_hash for both columns can be intentional if they are effectively the same ID. Confirm this design meets your business requirements.

Do you want me to check for references to a separate schema where id could differ from transaction_hash?


539-542: Linking StoreTransactionQuery in the final step is consistent with your design.
You're properly encapsulating the contract addresses and calls in the query. Looks good!

Comment on lines +72 to +106
pub async fn fetch_single_row_with_joins(
conn: &mut SqliteConnection,
table_name: &str,
id_column: &str,
id: &str,
joins: Vec<JoinConfig>,
select_columns: Option<Vec<String>>,
) -> sqlx::Result<SqliteRow> {
// Build the SELECT clause
let select = match select_columns {
Some(columns) => columns.join(", "),
None => format!("[{}].*", table_name),
};

// Build the JOIN clauses
let join_clauses = joins
.iter()
.map(|join| {
let table_ref = match &join.alias {
Some(alias) => format!("[{}] AS {}", join.table, alias),
None => format!("[{}]", join.table),
};
format!("{} {} ON {}", join.join_type.as_sql(), table_ref, join.on_condition)
})
.collect::<Vec<String>>()
.join(" ");

// Build the complete query
let query = format!(
"SELECT {} FROM [{}] {} WHERE [{}].{} = '{}'",
select, table_name, join_clauses, table_name, id_column, id
);

sqlx::query(&query).fetch_one(conn).await
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ohayo sensei! Be cautious about potential SQL injection in the join query builder.

While the function builds SQL queries dynamically, it directly interpolates values from the JoinConfig struct into the query string. Consider parameterizing these values where possible or implementing additional sanitization to prevent potential SQL injection attacks.

- let query = format!(
-     "SELECT {} FROM [{}] {} WHERE [{}].{} = '{}'",
-     select, table_name, join_clauses, table_name, id_column, id
- );
+ let query = format!(
+     "SELECT {} FROM [{}] {} WHERE [{}].{} = ?",
+     select, table_name, join_clauses, table_name, id_column
+ );
+ sqlx::query(&query).bind(id).fetch_one(conn).await
📝 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
pub async fn fetch_single_row_with_joins(
conn: &mut SqliteConnection,
table_name: &str,
id_column: &str,
id: &str,
joins: Vec<JoinConfig>,
select_columns: Option<Vec<String>>,
) -> sqlx::Result<SqliteRow> {
// Build the SELECT clause
let select = match select_columns {
Some(columns) => columns.join(", "),
None => format!("[{}].*", table_name),
};
// Build the JOIN clauses
let join_clauses = joins
.iter()
.map(|join| {
let table_ref = match &join.alias {
Some(alias) => format!("[{}] AS {}", join.table, alias),
None => format!("[{}]", join.table),
};
format!("{} {} ON {}", join.join_type.as_sql(), table_ref, join.on_condition)
})
.collect::<Vec<String>>()
.join(" ");
// Build the complete query
let query = format!(
"SELECT {} FROM [{}] {} WHERE [{}].{} = '{}'",
select, table_name, join_clauses, table_name, id_column, id
);
sqlx::query(&query).fetch_one(conn).await
}
pub async fn fetch_single_row_with_joins(
conn: &mut SqliteConnection,
table_name: &str,
id_column: &str,
id: &str,
joins: Vec<JoinConfig>,
select_columns: Option<Vec<String>>,
) -> sqlx::Result<SqliteRow> {
// Build the SELECT clause
let select = match select_columns {
Some(columns) => columns.join(", "),
None => format!("[{}].*", table_name),
};
// Build the JOIN clauses
let join_clauses = joins
.iter()
.map(|join| {
let table_ref = match &join.alias {
Some(alias) => format!("[{}] AS {}", join.table, alias),
None => format!("[{}]", join.table),
};
format!("{} {} ON {}", join.join_type.as_sql(), table_ref, join.on_condition)
})
.collect::<Vec<String>>()
.join(" ");
// Build the complete query
let query = format!(
"SELECT {} FROM [{}] {} WHERE [{}].{} = ?",
select, table_name, join_clauses, table_name, id_column
);
sqlx::query(&query).bind(id).fetch_one(conn).await
}

Comment thread crates/torii/graphql/src/object/transaction.rs
Comment thread crates/torii/graphql/src/object/transaction.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
crates/torii/graphql/src/object/transaction.rs (1)

175-199: ⚠️ Potential issue

Potential SQL injection in token transfers query!

Ohayo, sensei! The SQL query is vulnerable to injection because you're directly interpolating the transaction_hash into the LIKE pattern instead of using a parameterized query.

Fix this by using parameters properly:

-                        WHERE
-                            et.event_id LIKE '%:{transaction_hash}:%'
+                        WHERE
+                            et.event_id LIKE '%:' || ? || ':%'

This ensures the transaction hash is properly escaped and handles any special characters safely.

🧹 Nitpick comments (3)
crates/torii/sqlite/src/types.rs (2)

230-234: Consider deriving more traits for CallType enum

Ohayo, sensei! The CallType enum looks good but could benefit from deriving additional traits for better functionality:

-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub enum CallType {
     Execute,
     ExecuteFromOutside,
 }

Adding these traits would enable easy comparison, use in HashMap/HashSet collections, and serialization/deserialization - making the enum more versatile throughout the codebase.


245-252: ParsedCall struct could benefit from serialization traits

Ohayo sensei! The ParsedCall struct looks well-structured for representing calls. For consistency with other types in this file, consider adding serialization traits:

-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ParsedCall {
     pub contract_address: Felt,
     pub entrypoint: String,
     pub calldata: Vec<Felt>,
     pub call_type: CallType,
     pub caller_address: Felt,
 }

This would make it easier to serialize/deserialize this struct when needed, especially for API responses or persistence.

crates/torii/graphql/src/object/transaction.rs (1)

204-211: Consider optimizing token transfer data processing

The current approach processes each row individually. For better performance with large result sets, consider using batch processing or parallelization.

-                    let mut results = Vec::new();
-                    for row in &rows {
-                        let row = TransferQueryResultRaw::from_row(row)?;
-                        let result = token_transfer_mapping_from_row(&row)?;
-                        results.push(FieldValue::owned_any(result));
-                    }
+                    let results = rows.iter()
+                        .map(|row| -> Result<FieldValue, async_graphql::Error> {
+                            let row = TransferQueryResultRaw::from_row(row)?;
+                            let result = token_transfer_mapping_from_row(&row)?;
+                            Ok(FieldValue::owned_any(result))
+                        })
+                        .collect::<Result<Vec<_>, _>>()?;

This functional approach is more concise and makes the error handling more explicit.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 74590a2 and 2e1aed2.

📒 Files selected for processing (3)
  • crates/torii/graphql/src/mapping.rs (2 hunks)
  • crates/torii/graphql/src/object/transaction.rs (3 hunks)
  • crates/torii/sqlite/src/types.rs (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/graphql/src/object/transaction.rs (2)
crates/torii/graphql/src/object/erc/token_transfer.rs (8) (8)
  • token_transfer_mapping_from_row (264-383)
  • name (31-33)
  • type_name (35-37)
  • type_mapping (39-41)
  • ctx (57-57)
  • extract (59-59)
  • row (289-289)
  • row (331-331)
crates/torii/graphql/src/object/mod.rs (16) (16)
  • resolve_many (321-374)
  • resolve_one (246-273)
  • name (46-46)
  • type_name (49-49)
  • type_mapping (52-52)
  • ctx (84-84)
  • ctx (109-109)
  • ctx (123-123)
  • ctx (148-148)
  • ctx (161-161)
  • ctx (190-190)
  • ctx (264-264)
  • ctx (301-301)
  • ctx (339-339)
  • extract (266-266)
  • extract (303-303)
🔇 Additional comments (10)
crates/torii/sqlite/src/types.rs (2)

236-243: Display implementation looks good

The Display trait implementation is clear and follows the naming convention consistently. Nice work!


254-268: Transaction struct implementation looks good

The Transaction struct is well-defined with appropriate fields and trait derivations. The camelCase serialization annotation is consistent with other structs in this file, and the inclusion of FromRow makes it compatible with SQLx for database operations.

crates/torii/graphql/src/mapping.rs (2)

67-74: Ohayo sensei! Consider using felt-like fields for contract addresses.

Currently, contractAddress, entrypoint, and callerAddress are typed as strings in your new CALL_MAPPING. If these values represent FELT-252 addresses/selectors, it would be more consistent to use TypeRef::named(Primitive::Felt252(None).to_string()) like you've done in other mappings, rather than simple strings.


109-112: Looks good! Adding blockNumber to transaction mapping.

The blockNumber field is properly added to the transaction mapping. Using STRING is appropriate here since block numbers are often represented in both decimal and hex formats in client applications.

crates/torii/graphql/src/object/transaction.rs (6)

22-41: Ohayo, the CallObject implementation looks good!

The BasicObject implementation for CallObject is clean and follows the established pattern in the codebase. It properly references the constants and mappings defined elsewhere.


58-60: Good addition of related fields to TransactionObject!

Adding calls and token transfers as related fields to transactions provides a nice GraphQL structure that allows clients to fetch this data in a single query.


84-112: Nice implementation of transaction subscriptions!

The subscription implementation allows clients to receive real-time updates about transactions, with the option to filter by transaction hash. The filter logic is clean and efficient.


115-130: Clean value mapping implementation for transactions.

This method properly maps all transaction fields to their GraphQL representation. The calldata splitting on "/" is an interesting approach - make sure this matches how calldata is stored in the database.


133-163: Ohayo sensei! The calls_field implementation looks good.

The implementation correctly fetches call data associated with a transaction. You're properly using parameterized queries to prevent SQL injection.


202-202: Make sure SQL bindings match query parameters

The query uses string interpolation for transaction_hash (which is unsafe) but then also tries to bind it as a parameter. When fixing the LIKE clause, ensure you're using the bound parameter correctly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
crates/torii/graphql/src/object/transaction.rs (1)

178-205: ⚠️ Potential issue

SQL injection vulnerability in token_transfers query!

Ohayo, sensei! The query is vulnerable to SQL injection because you're directly interpolating the transaction_hash into the LIKE pattern instead of using a parameterized query. Additionally, you're binding a parameter on line 205, but there's no corresponding placeholder in the query.

Fix this by using parameters properly:

                    let query = format!(
                        r#"
                        SELECT 
                            et.id,
                            et.contract_address,
                            et.from_address,
                            et.to_address,
                            et.amount,
                            et.token_id,
                            et.executed_at,
                            t.name,
                            t.symbol,
                            t.decimals,
                            c.contract_type,
                            t.metadata
                        FROM
                            {TOKEN_TRANSFER_TABLE} et
                        JOIN
                            tokens t ON et.token_id = t.id
                        JOIN
                            contracts c ON t.contract_address = c.contract_address
                        WHERE
-                            et.event_id LIKE '%:{transaction_hash}:%'
+                            et.event_id LIKE '%:' || ? || ':%'
                        "#
                    );

                    let rows =
                        sqlx::query(&query).bind(&transaction_hash).fetch_all(&mut *conn).await?;
🧹 Nitpick comments (1)
crates/torii/graphql/src/object/transaction.rs (1)

115-134: Clean implementation of value_mapping

This helper function efficiently transforms the Transaction struct into a GraphQL-compatible value mapping. The use of split("/") on line 123 for calldata works, but consider documenting the expected format to avoid future confusion.

            (
                Name::new("calldata"),
-                Value::from(transaction.calldata.split("/").collect::<Vec<_>>()),
+                // Split calldata on "/" as it's stored as slash-separated values
+                Value::from(transaction.calldata.split("/").collect::<Vec<_>>()),
            ),
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1aed2 and bdf0668.

📒 Files selected for processing (1)
  • crates/torii/graphql/src/object/transaction.rs (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/graphql/src/object/transaction.rs (5)
crates/torii/sqlite/src/lib.rs (3) (3)
  • sqlx (87-87)
  • sqlx (175-175)
  • sqlx (182-182)
crates/torii/graphql/src/object/erc/token_transfer.rs (5) (5)
  • name (31-33)
  • type_name (35-37)
  • type_mapping (39-41)
  • row (289-289)
  • row (331-331)
crates/torii/graphql/src/object/mod.rs (5) (5)
  • name (46-46)
  • type_name (49-49)
  • type_mapping (52-52)
  • related_fields (55-57)
  • subscriptions (216-218)
crates/torii/graphql/src/object/erc/token_balance.rs (6) (6)
  • name (37-39)
  • type_name (41-43)
  • type_mapping (45-47)
  • subscriptions (94-168)
  • row (341-341)
  • row (396-396)
crates/torii/graphql/src/query/data.rs (1) (1)
  • list (291-298)
🔇 Additional comments (5)
crates/torii/graphql/src/object/transaction.rs (5)

22-41: Good implementation of CallObject struct, ohayo sensei!

The new CallObject structure is well-implemented with all required trait methods. It follows the same pattern as other objects in the codebase, making the code consistent and maintainable.


58-60: LGTM! Nice enhancement to related_fields

Adding related fields for calls and token transfers improves the GraphQL schema by enabling clients to fetch transaction-related data in a single query.


84-113: Good implementation of GraphQL subscriptions, sensei!

The subscription implementation allows clients to subscribe to transaction updates by hash or to all transactions. The filtering logic is clean and effective.


136-166: Nice implementation of calls_field, ohayo!

The function correctly fetches calls for a transaction from the database. Good use of parameterized queries to prevent SQL injection!


207-219: LGTM! Good result handling logic

The code correctly processes the query results by mapping database rows to the expected GraphQL output format.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
crates/torii/graphql/src/object/transaction.rs (1)

194-218: ⚠️ Potential issue

Potential SQL injection vulnerability in token transfers query!

Ohayo, sensei! The SQL query is vulnerable to injection because you're directly interpolating the transaction_hash into the LIKE pattern instead of using a parameterized query.

Fix this by using parameters properly:

-                    let query = format!(
-                        r#"
-                        SELECT 
-                            et.id,
-                            et.contract_address,
-                            et.from_address,
-                            et.to_address,
-                            et.amount,
-                            et.token_id,
-                            et.executed_at,
-                            t.name,
-                            t.symbol,
-                            t.decimals,
-                            c.contract_type,
-                            t.metadata
-                        FROM
-                            {TOKEN_TRANSFER_TABLE} et
-                        JOIN
-                            tokens t ON et.token_id = t.id
-                        JOIN
-                            contracts c ON t.contract_address = c.contract_address
-                        WHERE
-                            et.event_id LIKE '%:{transaction_hash}:%'
-                        "#
-                    );
+                    let query = format!(
+                        r#"
+                        SELECT 
+                            et.id,
+                            et.contract_address,
+                            et.from_address,
+                            et.to_address,
+                            et.amount,
+                            et.token_id,
+                            et.executed_at,
+                            t.name,
+                            t.symbol,
+                            t.decimals,
+                            c.contract_type,
+                            t.metadata
+                        FROM
+                            {TOKEN_TRANSFER_TABLE} et
+                        JOIN
+                            tokens t ON et.token_id = t.id
+                        JOIN
+                            contracts c ON t.contract_address = c.contract_address
+                        WHERE
+                            et.event_id LIKE '%:' || ? || ':%'
+                        "#
+                    );

This ensures the transaction hash is properly escaped and prevents SQL injection.

🧹 Nitpick comments (1)
crates/torii/graphql/src/object/transaction.rs (1)

139-140: Consider using collect with type annotation for clarity.

Sensei, the code works but could be slightly clearer with an explicit type annotation:

- Value::from(transaction.calldata.split("/").collect::<Vec<_>>()),
+ Value::from(transaction.calldata.split("/").collect::<Vec<&str>>()),
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between bdf0668 and 476a98a.

📒 Files selected for processing (3)
  • crates/torii/graphql/src/object/transaction.rs (3 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (8 hunks)
  • crates/torii/sqlite/src/types.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/torii/sqlite/src/types.rs
🔇 Additional comments (16)
crates/torii/sqlite/src/executor/mod.rs (8)

1-1: Ohayo! Good addition of HashSet import.

The HashSet is appropriately imported for efficient storage of unique contract addresses in the new transaction storage mechanism.


11-11: Nice type renaming to avoid name conflicts, sensei!

Renaming Transaction to SqlxTransaction is a smart move to prevent confusion with the domain Transaction type from your types module.


27-27: Good additions to support structured transaction calls.

Adding ParsedCall to the imports and the felts_to_sql_string utility function properly sets up the structure needed for the new transaction call handling mechanism.

Also applies to: 29-29


108-112: Well-designed struct for transaction query!

The StoreTransactionQuery struct elegantly captures both the contract addresses and function calls associated with a transaction. Using a HashSet for addresses ensures uniqueness.


116-116: Clean enum addition, sensei!

The new StoreTransaction variant in the QueryType enum follows the existing pattern and integrates well with the codebase.


141-141: Consistent field type update.

The transaction field type is correctly updated to match the renamed SqlxTransaction type, maintaining consistency throughout the codebase.


451-492: Excellent implementation of transaction storage with structured calls!

The implementation nicely handles:

  1. Retrieving the transaction from the database
  2. Storing contract addresses in the transaction_contract table
  3. Storing structured calls in the transaction_calls table
  4. Publishing the transaction message

The code properly uses parameterized queries to prevent SQL injection and includes proper error handling.


890-890: Updated broker message handling appropriately.

The send_broker_message function is correctly updated to handle the new transaction message type.

crates/torii/graphql/src/object/transaction.rs (8)

3-5: Ohayo, sensei! Good addition of SubscriptionField types.

I see you've added imports for supporting the new subscription functionality. Clear and thoughtful inclusion of the necessary dynamic types.


15-17: Constants added appropriately for the new functionality.

The constants for CALL_TYPE_NAME and TRANSACTION_CALLS_TABLE are properly imported, maintaining consistency with the project's pattern of centralizing constants.


19-23: Great job importing the CALL_MAPPING!

Adding the CALL_MAPPING import aligns well with how you're handling other type mappings in the system.


25-44: Nice implementation of the CallObject struct!

The implementation follows the established pattern for GraphQL objects in the system. Clean and consistent with other object implementations.


61-63: Well-structured related_fields implementation.

You've properly updated the related_fields method to include both calls and token transfers, which creates a good GraphQL schema structure.


131-149: Clean value_mapping implementation!

The method properly converts Transaction fields to GraphQL values. I like how you're using IndexMap for predictable field ordering.


152-182: Well implemented calls_field function!

The function correctly fetches and maps function calls data. Good job using parameterized queries to prevent SQL injection.


221-221: Make sure SQL bindings match query parameters.

The query uses string interpolation for transaction_hash (which is unsafe) but then also tries to bind it as a parameter. When fixing the LIKE clause, ensure you're using the bound parameter correctly.

Comment thread crates/torii/graphql/src/object/transaction.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/torii/sqlite/src/executor/mod.rs (1)

462-503: Ohayo sensei! Add error handling for calls collection processing.

The implementation for handling StoreTransaction queries looks solid overall, but I'm concerned about the error handling when processing the calls collection. If there's an issue with the format of any call data, the current code might panic.

Consider adding error checking before accessing call data elements, especially when processing large call collections:

  for call in &store_transaction.calls {
+     // Validate call data before inserting
+     if call.calldata.is_empty() {
+         tracing::warn!(target: LOG_TARGET, "Empty calldata for call to {} with entrypoint {}", &call.contract_address, &call.entrypoint);
+     }
+     
      sqlx::query(
          "INSERT OR IGNORE INTO transaction_calls (transaction_hash, \
           contract_address, entrypoint, calldata, call_type, caller_address) \
           VALUES (?, ?, ?, ?, ?, ?)",
      )
      // ...
  }
crates/torii/sqlite/src/lib.rs (1)

538-551: Ohayo! Updated function signature looks good but needs documentation.

The function signature change from accepting a Transaction object to individual parameters makes the function more flexible but could benefit from documentation to explain the purpose and expected values of each parameter.

Consider adding a docstring to explain the function parameters:

+    /// Stores a transaction and its associated calls in the database
+    /// 
+    /// # Arguments
+    /// * `transaction_hash` - The hash of the transaction
+    /// * `sender_address` - The address of the transaction sender
+    /// * `calldata` - The transaction calldata
+    /// * `max_fee` - The maximum fee for the transaction
+    /// * `signature` - The transaction signature
+    /// * `nonce` - The transaction nonce
+    /// * `block_number` - The block number where the transaction was included
+    /// * `contract_addresses` - Set of contract addresses associated with this transaction
+    /// * `transaction_type` - The type of transaction (e.g., "INVOKE")
+    /// * `block_timestamp` - The timestamp of the block
+    /// * `calls` - The parsed calls contained in this transaction
     pub fn store_transaction(
         &mut self,
         transaction_hash: Felt,
         sender_address: Felt,
         calldata: &[Felt],
         max_fee: Felt,
         signature: &[Felt],
         nonce: Felt,
         block_number: u64,
         contract_addresses: &HashSet<Felt>,
         transaction_type: &str,
         block_timestamp: u64,
         calls: &[ParsedCall],
     ) -> Result<()> {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 476a98a and 58aed92.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • crates/torii/indexer/src/processors/mod.rs (3 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (9 hunks)
  • crates/torii/sqlite/src/lib.rs (2 hunks)
  • crates/torii/sqlite/src/types.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/torii/indexer/src/processors/mod.rs
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/sqlite/src/lib.rs (2)
crates/torii/sqlite/src/executor/mod.rs (2) (2)
  • new (177-179)
  • new (252-278)
crates/torii/sqlite/src/utils.rs (2) (2)
  • felts_to_sql_string (35-38)
  • utc_dt_string_from_timestamp (31-33)
🔇 Additional comments (16)
crates/torii/sqlite/src/types.rs (4)

227-231: Ohayo, nice enum implementation for call types!

Clean and straightforward implementation with the two call types. The naming is descriptive and follows Rust conventions.


233-240: Display implementation looks good, sensei.

The Display trait implementation correctly formats both variants as uppercase strings, which will be useful for storing in the database.


242-249: Solid ParsedCall struct design.

This struct neatly encapsulates all the essential information about a contract call. The fields are aptly named and the field types are appropriate.


251-270: Transaction struct looks good with the new fields!

I like how you've added the new fields for calls and contract_addresses with the #[sqlx(skip)] attribute to ensure they're not included in database operations. This separation of concerns makes sense given the complex nature of these fields.

crates/torii/sqlite/src/executor/mod.rs (8)

1-1: Nice HashSet addition to the imports.

Adding HashSet to the existing HashMap import is clean and makes sense for the new contract_addresses field.


11-11: Good rename to avoid naming conflict, sensei!

Renaming Transaction to SqlxTransaction avoids confusion with your new Transaction struct.


27-27: Clean import addition for ParsedCall.

Adding ParsedCall to the existing imports from types.rs is an efficient way to manage the imports.


29-29: Well-organized utility function import.

Adding the felts_to_sql_string utility shows good code organization.


54-54: BrokerMessage transaction variant looks good.

The Transaction variant in BrokerMessage enum aligns with your other changes.


109-112: Clean design for StoreTransactionQuery struct.

The struct has a simple and clear design with just two fields: contract_addresses and calls, which aligns well with the requirement to store transaction calls.


127-127: QueryType enum addition looks good.

Adding the StoreTransaction variant to QueryType enum is a necessary and clean extension.


954-955: Transaction broker message publishing looks good.

You've correctly updated the send_broker_message function to handle Transaction messages consistently with other message types.

crates/torii/sqlite/src/lib.rs (4)

13-14: Good update to imports.

Adding StoreTransactionQuery to the imports is appropriate for the changes.


15-15: Clean up of import list.

Removing Transaction from the starknet imports since you're now using your own Transaction struct.


18-19: Nice additions to imports.

Adding ParsedCall and felts_to_sql_string shows good attention to dependencies.


552-574: Ohayo sensei! Transaction storage query looks solid but check the ID column.

The implementation for storing transactions is clean and follows a consistent pattern with other parts of the codebase. However, I noticed you're using the transaction_hash as both the ID and transaction_hash columns.

Make sure this is intentional - if the ID column is meant to be a unique identifier separate from the transaction hash, you might want to generate a different value. If using the transaction hash as ID is intentional (which is common and reasonable), then this is perfect.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
crates/torii/sqlite/src/cache.rs (1)

164-196: Consider locking strategy to avoid duplicate fetches, sensei.
Your double-checked locking approach is fine, but if multiple tasks request the same contract concurrently, you could overlap fetches. A small improvement would be to store an “in-progress” marker or use a future-based cache.

crates/torii/sqlite/src/executor/mod.rs (1)

463-469: Ohayo sensei! Verifying fetch with INSERT OR IGNORE.
While retrieving the newly inserted row with fetch_one can work, keep in mind that "INSERT OR IGNORE" might not insert a row if it already exists, potentially making fetch_one return zero rows. Consider handling the scenario where the record already exists to avoid runtime errors.

You could, for example, switch to a pattern like:

- INSERT OR IGNORE INTO transactions (...) VALUES (...) RETURNING *
+ INSERT INTO transactions (...) VALUES (...) ON CONFLICT(id) DO UPDATE SET ...
  RETURNING *;

Then proceed with fetch_one to reliably get the current row.

crates/torii/sqlite/src/lib.rs (1)

38-38: Ohayo sensei! #[allow(clippy::too_many_arguments)].
Acceptable if needed, though consider grouping arguments into a builder struct in the future to avoid passing so many parameters.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 58aed92 and 65d80a3.

📒 Files selected for processing (3)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (9 hunks)
  • crates/torii/sqlite/src/lib.rs (2 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
crates/torii/sqlite/src/lib.rs (3)
crates/torii/sqlite/src/executor/mod.rs (8) (8)
  • sqlx (321-321)
  • sqlx (520-520)
  • sqlx (611-611)
  • sqlx (651-651)
  • sqlx (720-720)
  • sqlx (803-803)
  • new (177-179)
  • new (252-278)
crates/torii/sqlite/src/model.rs (1) (1)
  • new (36-70)
crates/torii/sqlite/src/utils.rs (2) (2)
  • felts_to_sql_string (35-38)
  • utc_dt_string_from_timestamp (31-33)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (25)
crates/torii/sqlite/src/cache.rs (5)

2-2: Ohayo sensei, nice Arc usage!
This import is appropriate for synchronous concurrency, well done.


7-12: Ohayo sensei, these imports fit the new caching flow well.
They cleanly provide the types you need for contract interactions. Great job!


157-162: Ohayo sensei, good approach for caching contract classes!
The struct fields make sense. Storing both class hash and ContractClass together can help avoid repeated lookups.


225-225: Ohayo sensei, avoid .unwrap() for JSON deserialization.
This .unwrap() could panic on malformed JSON. Use a safe error-handling pattern (e.g. ?) instead.


242-242: Ohayo sensei, watch out for .unwrap() on get_selector_from_name.
If this function fails on an invalid name, the code will panic. Graceful error handling is preferable.

crates/torii/sqlite/src/executor/mod.rs (13)

1-1: Ohayo sensei! New import for HashSet.
This import enables using HashSet for handling unique collections, which is beneficial for avoiding duplicates in transaction-related logic.


11-11: Ohayo sensei! Introducing SqlxTransaction alias.
Renaming or aliasing the transaction type helps clarify the domain-specific usage here. Ensure that all references and doc comments are updated to reflect this new naming.


27-27: Ohayo sensei! ParsedCall import recognized.
Including ParsedCall in this import list indicates it will be used downstream for transaction calls. The addition is consistent with your expanded transaction processing logic.


29-29: Ohayo sensei! Utility imports look good.
Bringing in felts_to_sql_string and felt_to_sql_string is consistent with the new transaction calls logic.


54-54: Ohayo sensei! New message variant for transactions.
Adding Transaction(Transaction) to BrokerMessage ensures transaction data can be published across the system. LGTM!


108-112: Ohayo sensei! Introducing StoreTransactionQuery.
This struct captures the contract addresses and parsed calls for a transaction. Structuring them in a dedicated query object is clear and maintainable.


127-127: Ohayo sensei! Added StoreTransaction enum variant.
This is a natural extension to handle storing transactions. The naming is intuitive, matching the new struct.


152-152: Ohayo sensei! Transaction field uses SqlxTransaction.
Replacing the older naming with SqlxTransaction can help prevent confusion with different transaction abstractions.


462-462: Ohayo sensei! Implementing StoreTransaction branch.
The new branch in handle_query_message systematically processes stored transactions. This integration point looks consistent.


471-480: Ohayo sensei! transaction_contract insertion logic.
You're inserting contract addresses into the transaction_contract table for every relevant transaction. Check that ignoring duplicates is desired—if so, this is fine. Otherwise, consider UPDATE or REPLACE if data might change over time.

Would you like me to generate a script to check for existing code references that rely on updating existing rows in transaction_contract?


482-497: Ohayo sensei! transaction_calls insertion loop.
Storing each parsed call with INSERT OR IGNORE is consistent. If you want to track call updates (e.g., updated entrypoint or calldata after a re-run), consider an ON CONFLICT upsert strategy.


499-503: Ohayo sensei! Finalizing the transaction object.
Appending contract_addresses and calls to the transaction struct before publishing is a clear approach and ensures a consistent, enriched BrokerMessage::Transaction.


954-954: Ohayo sensei! Transaction variant broker publish.
Publishing the Transaction variant completes the pipeline for storing and broadcasting transaction data. Good addition!

crates/torii/sqlite/src/lib.rs (7)

13-13: Ohayo sensei! Adding StoreTransactionQuery import.
This import aligns with the new transaction-handling logic. Looks consistent with the rest of the file’s usage.


15-15: Ohayo sensei! Felt type usage.
We see Felt is central to StarkNet calls; bringing its symbol into scope here is consistent with the store logic.


18-18: Ohayo sensei! ParsedCall now in scope.
This newly imported type suggests you’ll parse calls individually. Appropriately used for capturing call details.


541-546: Ohayo sensei! Extended store_transaction parameters.
Adding these parameters (transaction_hash, sender_address, etc.) clarifies the transaction shape. This is a good step toward modularizing the logic.


549-549: Ohayo sensei! transaction_type parameter.
Allowing injection of the transaction type string might help differentiate behaviors if needed.


551-551: Ohayo sensei! Accepting calls slice.
This design paves the way for storing multiple function calls within a single transaction.


553-574: Ohayo sensei! Transaction insertion with new fields.
Storing the extra fields in transactions followed by a StoreTransactionQuery ensures the subsequent insertion of calls and contract addresses flows seamlessly in the executor.

@glihm glihm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A small comment that could help on performance and reducing the memory overhead of the classes that could be big. And a Dojo project may have bunch of classes.

Not mandatory though, can be optimized later by removing the byte code of classes and focusing only on selectors<>entrypoint name pairs.

},
};
let class = self.provider.get_class_at(block_id, contract_address).await?;
self.classes.write().await.insert(contract_address, (class_hash, class.clone()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If it mostly used for ABI lookup, storing the ABI only instead of the whole class with byte code could result in less memory being used.

Also, if the usage is mostly mapping a selector to a name, the mapping could be done the first time the class is fetched instead of storing the raw ABI.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh nice catch yes!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
crates/torii/sqlite/src/cache.rs (1)

244-252: ⚠️ Potential issue

Remove unwrap in Legacy ABI selector lookup, sensei.

The Legacy ABI lookup uses unwrap() on line 246 when getting a selector from a function name, which could panic.

-            LegacyContractAbiEntry::Function(function)
-                if get_selector_from_name(&function.name).unwrap() == selector =>
+            LegacyContractAbiEntry::Function(function)
+                if get_selector_from_name(&function.name).ok() == Some(selector) =>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 91e4bba and 65cb243.

📒 Files selected for processing (1)
  • crates/torii/sqlite/src/cache.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (7)
crates/torii/sqlite/src/cache.rs (7)

158-162: Ohayo, sensei! Clean enum definition for ClassAbi.

This enum provides a nice separation between Sierra and Legacy ABI types, making it easier to handle different contract classes consistently.


164-168: Good use of generics and thread-safe caching, sensei!

The ContractClassCache design with generics over a Provider type and RwLock for thread-safety is appropriate. The struct stores only the class hash and ABI data instead of the entire contract class, which is memory-efficient.


170-173: Simple and clean constructor implementation.

The new function properly initializes the cache with an empty HashMap and stores the provider reference.


175-197: Improved error handling for provider operations, sensei!

The implementation correctly handles the block not found error by falling back to the pending block. This is a good improvement from the previous implementation that was using unwrap().


206-206: Consider implementing cache size limits, sensei.

The cache currently grows unbounded as new contract classes are accessed. Consider adding a maximum size limit or an LRU eviction policy to prevent potential memory issues.


211-221: Nice recursive function to flatten ABI functions.

This helper function efficiently extracts function entries from nested ABI definitions, which is useful for searching across all function definitions later.


223-243: Good entrypoint lookup for Sierra ABI.

The implementation correctly handles the lookup of entrypoint names from Sierra ABIs by first finding the matching selector in the entrypoints list and then looking up the function details in the ABI.

Comment on lines +198 to +208
let class = match self.provider.get_class_at(block_id, contract_address).await? {
ContractClass::Sierra(sierra) => {
let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi).unwrap();
let functions: Vec<AbiEntry> = flatten_abi_funcs_recursive(&abi);
ClassAbi::Sierra((sierra.entry_points_by_type, functions))
}
ContractClass::Legacy(legacy) => ClassAbi::Legacy(legacy.abi.unwrap_or_default()),
};
self.classes.write().await.insert(contract_address, (class_hash, class.clone()));
Ok(class)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential panic with unwrap on JSON parsing, sensei.

There's still an unwrap() on line 200 when parsing the Sierra ABI JSON. This could panic if the ABI is malformed.

-                let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi).unwrap();
+                let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi)
+                    .map_err(|e| Error::ParseError(ParseError::FromJsonStr(e)))?;
📝 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 class = match self.provider.get_class_at(block_id, contract_address).await? {
ContractClass::Sierra(sierra) => {
let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi).unwrap();
let functions: Vec<AbiEntry> = flatten_abi_funcs_recursive(&abi);
ClassAbi::Sierra((sierra.entry_points_by_type, functions))
}
ContractClass::Legacy(legacy) => ClassAbi::Legacy(legacy.abi.unwrap_or_default()),
};
self.classes.write().await.insert(contract_address, (class_hash, class.clone()));
Ok(class)
}
let class = match self.provider.get_class_at(block_id, contract_address).await? {
ContractClass::Sierra(sierra) => {
- let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi).unwrap();
+ let abi: Vec<AbiEntry> = serde_json::from_str(&sierra.abi)
+ .map_err(|e| Error::ParseError(ParseError::FromJsonStr(e)))?;
let functions: Vec<AbiEntry> = flatten_abi_funcs_recursive(&abi);
ClassAbi::Sierra((sierra.entry_points_by_type, functions))
}
ContractClass::Legacy(legacy) => ClassAbi::Legacy(legacy.abi.unwrap_or_default()),
};
self.classes.write().await.insert(contract_address, (class_hash, class.clone()));
Ok(class)
}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants