Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(torii-graphql): subscriptions for tokens and token balances #2999

Merged
merged 9 commits into from
Feb 11, 2025

Conversation

Larkooo
Copy link
Collaborator

@Larkooo Larkooo commented Feb 7, 2025

Summary by CodeRabbit

  • Refactor

    • Updated token typing by renaming and consolidating token constants.
    • Adjusted mappings and schema components to reflect the new naming convention.
  • New Features

    • Introduced a new token object that supports real-time subscription updates for token events.
    • Added subscription capabilities for token balance changes, enabling enhanced live client notifications.
    • Enhanced error handling and messaging for token registration processes.

@Larkooo Larkooo force-pushed the torii-graphql-tokens branch from 4de044d to 4263ab9 Compare February 10, 2025 06:33
Copy link

codecov bot commented Feb 10, 2025

Codecov Report

Attention: Patch coverage is 12.08791% with 400 lines in your changes missing coverage. Please review.

Project coverage is 56.62%. Comparing base (d9227db) to head (05433b6).
Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
crates/torii/graphql/src/object/erc/erc_token.rs 12.03% 285 Missing ⚠️
...ates/torii/graphql/src/object/erc/token_balance.rs 9.56% 104 Missing ⚠️
crates/torii/sqlite/src/executor/erc.rs 0.00% 7 Missing ⚠️
crates/torii/sqlite/src/executor/mod.rs 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2999      +/-   ##
==========================================
- Coverage   56.96%   56.62%   -0.34%     
==========================================
  Files         430      431       +1     
  Lines       57052    57637     +585     
==========================================
+ Hits        32500    32638     +138     
- Misses      24552    24999     +447     

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

@Larkooo Larkooo marked this pull request as ready for review February 10, 2025 12:23
Copy link

coderabbitai bot commented Feb 10, 2025

Walkthrough

Ohayo, sensei! This pull request renames a token-related constant from TOKEN_TYPE_NAME to TOKEN_UNION_TYPE_NAME across multiple GraphQL modules. The change propagates through mappings and the schema to align with updated naming. New subscription features were added to support real-time updates for token information and token balances, including a new TokenObject and corresponding subscription logic that queries a SQLite database.

Changes

File(s) Change Summary
crates/torii/graphql/src/constants.rs
crates/torii/graphql/src/schema.rs
Renamed constant from TOKEN_TYPE_NAME to TOKEN_UNION_TYPE_NAME; in schema, updated union object creation and added TokenObject in the resolvable objects.
crates/torii/graphql/src/mapping.rs Replaced usages of TOKEN_TYPE_NAME with TOKEN_UNION_TYPE_NAME in mappings and added a new mapping TOKEN_TYPE_MAPPING with several token fields.
crates/torii/graphql/src/object/erc/erc_token.rs Added new TokenObject struct implementing BasicObject and ResolvableObject traits; includes methods for name, type, mapping, and a subscription field (tokenUpdated) that queries a SQLite database to provide token updates based on contract type.
crates/torii/graphql/src/object/erc/token_balance.rs Added a subscriptions method to ErcBalanceObject defining a subscription (tokenBalanceUpdated) that listens for token balance changes filtered by accountAddress, executing a SQL query to fetch relevant token data.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant GE as GraphQL Engine
    participant TO as TokenObject Resolver
    participant DB as SQLite DB

    C->>GE: Subscribe to tokenUpdated
    GE->>TO: Invoke tokenUpdated subscription
    TO->>DB: Query token data (contract type)
    DB->>TO: Return token data (Erc20/Erc721 details)
    TO->>GE: Stream token update response
    GE->>C: Deliver token update
Loading
sequenceDiagram
    participant C as Client
    participant GE as GraphQL Engine
    participant EB as ErcBalanceObject Resolver
    participant DB as SQLite DB

    C->>GE: Subscribe to tokenBalanceUpdated with accountAddress
    GE->>EB: Invoke tokenBalanceUpdated subscription
    EB->>DB: Execute SQL query for token balance
    DB->>EB: Return token balance data
    EB->>GE: Create subscription output (edge update)
    GE->>C: Stream token balance update
Loading

Possibly related issues

  • [Torii] gRPC to support Tokens subscription #2819: This issue requests support for tokens subscription via gRPC. The added subscription features for token updates and token balance changes in this PR align with the objective of providing a subscription-based update mechanism.

Possibly related PRs

Suggested reviewers

  • glihm (sensei)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
crates/torii/graphql/src/object/erc/erc_token.rs (2)

117-132: Ohayo sensei, consider unifying the naming convention with your union type.
Right now, type_name(&self) -> "Token" can be confusing when you've also introduced "ERC__Token" in the constants. Harmonizing both layers would reduce potential ambiguity in your schema.


134-253: Ohayo sensei, add some logging for dropped errors to improve observability.
When your DB query fails (line 161) or you encounter an unrecognized contract_type (line 229), the code currently discards the event by returning None. Consider the following diff to log these cases before returning:

- Err(_) => return None,
+ Err(err) => {
+     tracing::warn!("Failed to fetch row for token {}: {:?}", token.id, err);
+     return None;
+ }

- _ => return None,
+ _ => {
+     tracing::warn!("Encountered unknown contract type: {}", contract_type);
+     return None;
+ }

This helps with debugging, especially when subscription updates fail unexpectedly.

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

93-162: Ohayo sensei, handle subscription query errors more gracefully.
If the sqlx query fails (line 137), the code silently drops the update. Logging this error will make troubleshooting much easier, as shown here:

- Err(_) => return None,
+ Err(err) => {
+     tracing::warn!("Failed to fetch row for token balance {}: {:?}", token_balance.id, err);
+     return None;
+ }

Also, if this subscription logic duplicates parts of tokenUpdated, consider refactoring for better reuse.

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

194-201: Well-defined token type mapping!

The new TOKEN_TYPE_MAPPING includes all essential fields with appropriate non-nullable types. Consider adding documentation comments to describe the purpose and usage of this mapping, similar to the TODO comment on the metadata mapping.

Add a documentation comment like this:

+    // Mapping for token types, supporting both ERC20 and ERC721 tokens through a union type
     pub static ref TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d9227db and 1061b46.

📒 Files selected for processing (5)
  • crates/torii/graphql/src/constants.rs (1 hunks)
  • crates/torii/graphql/src/mapping.rs (3 hunks)
  • crates/torii/graphql/src/object/erc/erc_token.rs (2 hunks)
  • crates/torii/graphql/src/object/erc/token_balance.rs (2 hunks)
  • crates/torii/graphql/src/schema.rs (2 hunks)
🔇 Additional comments (7)
crates/torii/graphql/src/object/erc/erc_token.rs (1)

1-3: Ohayo sensei, these imports nicely set up your subscriptions and DB interactions!
Everything looks consistent for handling GraphQL dynamic fields, subscription fields, and SQLx usage.

Also applies to: 5-8, 11-12

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

2-3: Ohayo sensei, the newly added subscription/broker imports fit right in.
They look good and set the stage for your real-time token balance updates.

Also applies to: 10-10, 12-13

crates/torii/graphql/src/constants.rs (1)

39-39: Ohayo sensei, the new TOKEN_UNION_TYPE_NAME constant is well introduced!
This consistent naming across your schema clarifies the union type for tokens.

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

15-15: LGTM! Import changes align with token subscriptions feature.

The renaming to TOKEN_UNION_TYPE_NAME and addition of TokenObject support the new token subscriptions functionality.

Also applies to: 19-19


134-134: Ohayo! Nice addition of TokenObject as a Resolvable object, sensei!

The TokenObject is correctly registered as a Resolvable object, which is essential for implementing the subscription functionality.


142-144: Well-structured union type definition!

The union type correctly includes both ERC20 and ERC721 token types, providing a flexible type system for token subscriptions.

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

149-149: Consistent type mapping updates, sensei!

The token balance and transfer mappings have been correctly updated to use TOKEN_UNION_TYPE_NAME, maintaining consistency across the schema.

Also applies to: 156-156

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

141-144: Consider enhancing error handling, sensei!

The error handling for BalanceQueryResultRaw::from_row could be more informative.

Consider adding context to the error:

-                                    let row = match BalanceQueryResultRaw::from_row(&row) {
-                                        Ok(row) => row,
-                                        Err(_) => return None,
-                                    };
+                                    let row = match BalanceQueryResultRaw::from_row(&row) {
+                                        Ok(row) => row,
+                                        Err(e) => {
+                                            tracing::error!("Failed to parse balance query result: {}", e);
+                                            return None;
+                                        }
+                                    };

94-228: Consider subscription performance implications, sensei!

The subscription implementation queries the database for each token balance update. This could lead to performance issues with many subscribers.

Consider implementing:

  1. Caching frequently accessed token data
  2. Batching database queries
  3. Rate limiting for subscriptions
crates/torii/graphql/src/object/erc/erc_token.rs (1)

358-461: Consider subscription performance, sensei!

The subscription implementation could benefit from some optimizations.

Consider implementing:

  1. Connection pooling for database queries
  2. Caching token metadata
  3. Rate limiting for subscriptions
  4. Batching database queries when multiple tokens are updated simultaneously
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1061b46 and 05433b6.

📒 Files selected for processing (7)
  • crates/torii/graphql/src/mapping.rs (3 hunks)
  • crates/torii/graphql/src/object/connection/mod.rs (1 hunks)
  • crates/torii/graphql/src/object/erc/erc_token.rs (2 hunks)
  • crates/torii/graphql/src/object/erc/token_balance.rs (2 hunks)
  • crates/torii/graphql/src/schema.rs (3 hunks)
  • crates/torii/sqlite/src/executor/erc.rs (3 hunks)
  • crates/torii/sqlite/src/executor/mod.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/torii/graphql/src/mapping.rs
  • crates/torii/graphql/src/schema.rs
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: ensure-wasm
  • GitHub Check: build
🔇 Additional comments (8)
crates/torii/sqlite/src/executor/erc.rs (2)

15-15: Ohayo, sensei! The imports look good!

The new imports for BrokerMessage and Token types are correctly added to support the token registration and subscription functionality.

Also applies to: 19-19


339-364: Ohayo, sensei! Nice improvements to token registration!

The changes enhance type safety and add subscription support:

  • Using sqlx::query_as::<_, Token> for type-safe queries.
  • Properly handling optional token fetch.
  • Adding token registration message for real-time updates.
crates/torii/sqlite/src/executor/mod.rs (4)

26-26: Ohayo, sensei! The import looks good!

The new import for the Token type is correctly added to support the token registration functionality.


51-51: Ohayo, sensei! The broker message looks good!

The new TokenRegistered(Token) variant is correctly added to support token registration subscriptions.


704-722: Ohayo, sensei! Nice improvements to ERC20 token registration!

The changes enhance type safety and add subscription support:

  • Using sqlx::query_as::<_, Token> for type-safe queries.
  • Adding token registration message for real-time updates.

842-842: Ohayo, sensei! The broker message handling looks good!

The new TokenRegistered variant is correctly handled to publish token registration events.

crates/torii/graphql/src/object/connection/mod.rs (1)

74-74: Ohayo! Nice type annotation addition, sensei!

The explicit type annotation Option<u64> improves code clarity and maintainability.

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

94-228: ⚠️ Potential issue

Ohayo! Let's secure this subscription implementation, sensei!

The SQL query in the subscription is vulnerable to SQL injection as it directly interpolates the token balance ID into the query string.

Replace string interpolation with parameterized query:

-                                    let query = format!(
-                                        "SELECT b.id, t.contract_address, t.name, t.symbol, \
-                                         t.decimals, b.balance, b.token_id, t.metadata, \
-                                         c.contract_type
-                                        FROM {} b
-                                        JOIN tokens t ON b.token_id = t.id
-                                        JOIN contracts c ON t.contract_address = \
-                                         c.contract_address
-                                        WHERE b.id = ?",
-                                        TOKEN_BALANCE_TABLE
-                                    );
+                                    let query = format!(
+                                        "SELECT b.id, t.contract_address, t.name, t.symbol, \
+                                         t.decimals, b.balance, b.token_id, t.metadata, \
+                                         c.contract_type
+                                        FROM {table} b
+                                        JOIN tokens t ON b.token_id = t.id
+                                        JOIN contracts c ON t.contract_address = \
+                                         c.contract_address
+                                        WHERE b.id = ?",
+                                        table = TOKEN_BALANCE_TABLE
+                                    );

Likely an incorrect or invalid review comment.


let mut conditions = Vec::new();
if let Some(addr) = contract_address {
conditions.push(format!("t.contract_address = '{}'", addr));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ohayo! Let's secure this query, sensei!

The SQL query is vulnerable to SQL injection as it directly interpolates the contract address.

Use parameterized query instead:

-        conditions.push(format!("t.contract_address = '{}'", addr));
+        conditions.push("t.contract_address = ?".to_string());

And update the query execution:

let mut query = sqlx::query(&query);
if let Some(addr) = contract_address {
    query = query.bind(addr);
}

Comment on lines +465 to +525
fn create_token_metadata_from_row(row: &SqliteRow) -> sqlx::Result<ErcTokenType> {
let contract_type: String = row.get("contract_type");

Ok(match contract_type.to_lowercase().as_str() {
"erc20" => {
let token = Erc20Token {
contract_address: row.get("contract_address"),
name: row.get("name"),
symbol: row.get("symbol"),
decimals: row.get("decimals"),
amount: "0".to_string(),
};
ErcTokenType::Erc20(token)
}
"erc721" => {
// contract_address:token_id
let id = row.get::<String, _>("id");
let token_id = id.split(':').collect::<Vec<&str>>()[1].to_string();

let metadata_str: String = row.get("metadata");
let (
metadata_str,
metadata_name,
metadata_description,
metadata_attributes,
image_path,
) = if metadata_str.is_empty() {
(String::new(), None, None, None, String::new())
} else {
let metadata: serde_json::Value =
serde_json::from_str(&metadata_str).expect("metadata is always json");
let metadata_name =
metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string());
let metadata_description = metadata
.get("description")
.map(|v| v.to_string().trim_matches('"').to_string());
let metadata_attributes =
metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string());

let contract_address: String = row.get("contract_address");
let image_path = format!("{}/image", contract_address);

(metadata_str, metadata_name, metadata_description, metadata_attributes, image_path)
};

let token = Erc721Token {
name: row.get("name"),
metadata: metadata_str,
contract_address: row.get("contract_address"),
symbol: row.get("symbol"),
token_id,
metadata_name,
metadata_description,
metadata_attributes,
image_path,
};
ErcTokenType::Erc721(token)
}
_ => return Err(sqlx::Error::RowNotFound),
})
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Let's reduce code duplication, sensei!

The token metadata creation logic is duplicated between create_token_metadata_from_row and the subscription handler.

Extract the metadata parsing logic into a separate function:

fn parse_metadata(metadata_str: &str, contract_address: &str) -> (String, Option<String>, Option<String>, Option<String>, String) {
    if metadata_str.is_empty() {
        return (String::new(), None, None, None, String::new());
    }

    let metadata: serde_json::Value = serde_json::from_str(metadata_str).expect("metadata is always json");
    let metadata_name = metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string());
    let metadata_description = metadata.get("description").map(|v| v.to_string().trim_matches('"').to_string());
    let metadata_attributes = metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string());
    let image_path = format!("{}/image", contract_address);

    (metadata_str.to_string(), metadata_name, metadata_description, metadata_attributes, image_path)
}

Comment on lines +411 to +413
serde_json::from_str(&metadata_str)
.expect("metadata is always json");
let metadata_name = metadata.get("name").map(|v| {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle JSON parsing errors gracefully, sensei!

Using expect for JSON parsing could cause panic in production.

Replace with proper error handling:

-                                            let metadata: serde_json::Value =
-                                                serde_json::from_str(&metadata_str)
-                                                    .expect("metadata is always json");
+                                            let metadata: serde_json::Value = match serde_json::from_str(&metadata_str) {
+                                                Ok(m) => m,
+                                                Err(e) => {
+                                                    tracing::error!("Failed to parse metadata JSON: {}", e);
+                                                    return None;
+                                                }
+                                            };
📝 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
serde_json::from_str(&metadata_str)
.expect("metadata is always json");
let metadata_name = metadata.get("name").map(|v| {
let metadata: serde_json::Value = match serde_json::from_str(&metadata_str) {
Ok(m) => m,
Err(e) => {
tracing::error!("Failed to parse metadata JSON: {}", e);
return None;
}
};
let metadata_name = metadata.get("name").map(|v| {

@Larkooo Larkooo enabled auto-merge (squash) February 11, 2025 07:30
@Larkooo Larkooo merged commit b8e5ebb into dojoengine:main Feb 11, 2025
13 checks passed
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