From 0e4a37051b48facb2d6763988ad8e4d9be542c1b Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Sat, 3 Aug 2024 21:58:36 +0530 Subject: [PATCH 01/23] tmp --- bin/torii/src/main.rs | 16 ++- crates/torii/core/src/engine.rs | 102 +++++++++++++----- .../core/src/processors/erc20_approval.rs | 1 + .../core/src/processors/erc20_transfer.rs | 57 ++++++++++ crates/torii/core/src/processors/mod.rs | 2 + .../torii/migrations/20240803102207_erc20.sql | 1 + 6 files changed, 154 insertions(+), 25 deletions(-) create mode 100644 crates/torii/core/src/processors/erc20_approval.rs create mode 100644 crates/torii/core/src/processors/erc20_transfer.rs create mode 100644 crates/torii/migrations/20240803102207_erc20.sql diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 9c33114056..7f63f7c836 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -27,6 +27,7 @@ use tokio::sync::broadcast; use tokio::sync::broadcast::Sender; use tokio_stream::StreamExt; use torii_core::engine::{Engine, EngineConfig, Processors}; +use torii_core::processors::erc20_transfer::Erc20TransferProcessor; use torii_core::processors::event_message::EventMessageProcessor; use torii_core::processors::metadata_update::MetadataUpdateProcessor; use torii_core::processors::register_model::RegisterModelProcessor; @@ -120,6 +121,17 @@ struct Args { #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); + + // TODO: see where to get this addresses from, cli? config? + let addresses = [(Felt::from_str("0x123").unwrap(), 0), (Felt::from_str("0x345").unwrap(), 0)]; + let mut start_block = args.start_block; + + for address in &addresses { + if address.1 < start_block { + start_block = address.1; + } + } + let filter_layer = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off")); @@ -174,6 +186,7 @@ async fn main() -> anyhow::Result<()> { Box::new(EventMessageProcessor), Box::new(StoreUpdateRecordProcessor), Box::new(StoreUpdateMemberProcessor), + Box::new(Erc20TransferProcessor), ], transaction: vec![Box::new(StoreTransactionProcessor)], ..Processors::default() @@ -187,13 +200,14 @@ async fn main() -> anyhow::Result<()> { &provider, processors, EngineConfig { - start_block: args.start_block, + start_block, events_chunk_size: args.events_chunk_size, index_pending: args.index_pending, ..Default::default() }, shutdown_tx.clone(), Some(block_tx), + addresses.iter().map(|(address, _)| *address).collect(), ); let shutdown_rx = shutdown_tx.subscribe(); diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index b2913c41b0..bdd21c169c 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -4,13 +4,14 @@ use std::time::Duration; use anyhow::Result; use dojo_world::contracts::world::WorldContractReader; +use futures_util::future::join_all; use starknet::core::types::{ - BlockId, BlockTag, Event, EventFilter, Felt, MaybePendingBlockWithTxHashes, + BlockId, BlockTag, Event, EventFilter, EventsPage, Felt, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, ReceiptBlock, Transaction, TransactionReceipt, TransactionReceiptWithBlockInfo, }; use starknet::core::utils::get_selector_from_name; -use starknet::providers::Provider; +use starknet::providers::{Provider, SequencerGatewayProviderError}; use tokio::sync::broadcast::Sender; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::time::sleep; @@ -61,6 +62,8 @@ pub struct Engine { config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, + // ERC20 tokens to index + tokens: Vec, } struct UnprocessedEvent { @@ -77,8 +80,18 @@ impl Engine

{ config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, + tokens: Vec, ) -> Self { - Self { world, db, provider: Box::new(provider), processors, config, shutdown_tx, block_tx } + Self { + world, + db, + provider: Box::new(provider), + processors, + config, + shutdown_tx, + block_tx, + tokens, + } } pub async fn start(&mut self) -> Result<()> { @@ -211,29 +224,45 @@ impl Engine

{ to: u64, pending_block_tx: Option, ) -> Result> { - // Process all blocks from current to latest. - let get_events = |token: Option| { - self.provider.get_events( - EventFilter { - from_block: Some(BlockId::Number(from)), - to_block: Some(BlockId::Number(to)), - address: Some(self.world.address), - keys: None, - }, - token, - self.config.events_chunk_size, - ) - }; + // Fetch all events and collect them using `get_events` method on provider // handle next events pages - let mut events_pages = vec![get_events(None).await?]; + let mut fetch_all_events_tasks = vec![]; + + let events_filter = EventFilter { + from_block: Some(BlockId::Number(from)), + to_block: Some(BlockId::Number(to)), + address: Some(self.world.address), + keys: None, + }; - while let Some(token) = &events_pages.last().unwrap().continuation_token { - events_pages.push(get_events(Some(token.clone())).await?); + let world_events_pages = + get_all_events(&self.provider, events_filter, self.config.events_chunk_size); + fetch_all_events_tasks.push(world_events_pages); + + for token in &self.tokens { + let events_filter = EventFilter { + from_block: Some(BlockId::Number(from)), + to_block: Some(BlockId::Number(to)), + address: Some(*token), + keys: None, + }; + + let erc20_events_pages = + get_all_events(&self.provider, events_filter, self.config.events_chunk_size); + + fetch_all_events_tasks.push(erc20_events_pages); + } + + // wait for all events tasks to complete + let task_result = join_all(fetch_all_events_tasks).await; + + let mut events_pages = vec![]; + for result in task_result { + events_pages.extend(result?); } // Transactions & blocks to process - let mut last_block = 0_u64; let mut blocks = BTreeMap::new(); // Flatten events pages and events according to the pending block cursor @@ -267,12 +296,10 @@ impl Engine

{ } }; - // Keep track of last block number and fetch block timestamp - if block_number > last_block { + // Fetch block timestamp if not already fetched and inserts into the map + if !blocks.contains_key(&block_number) { let block_timestamp = self.get_block_timestamp(block_number).await?; blocks.insert(block_number, block_timestamp); - - last_block = block_number; } // Then we skip all transactions until we reach the last pending processed @@ -486,3 +513,30 @@ impl Engine

{ Ok(()) } } + +async fn get_all_events

( + provider: &P, + events_filter: EventFilter, + events_chunk_size: u64, +) -> Result> +where + P: Provider + Sync, +{ + let mut events_pages = Vec::new(); + let mut continuation_token = None; + + loop { + let events_page = provider + .get_events(events_filter.clone(), continuation_token.clone(), events_chunk_size) + .await?; + + continuation_token = events_page.continuation_token.clone(); + events_pages.push(events_page); + + if continuation_token.is_none() { + break; + } + } + + Ok(events_pages) +} diff --git a/crates/torii/core/src/processors/erc20_approval.rs b/crates/torii/core/src/processors/erc20_approval.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/torii/core/src/processors/erc20_approval.rs @@ -0,0 +1 @@ + diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs new file mode 100644 index 0000000000..cf3786cd51 --- /dev/null +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -0,0 +1,57 @@ +use anyhow::Error; +use async_trait::async_trait; +use cainome::cairo_serde::{CairoSerde, U256}; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::providers::Provider; +use tracing::info; + +use super::EventProcessor; +use crate::sql::Sql; + +pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; + +#[derive(Default, Debug)] +pub struct Erc20TransferProcessor; + +#[async_trait] +impl

EventProcessor

for Erc20TransferProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "Transfer".to_string() + } + + fn validate(&self, event: &Event) -> bool { + if event.keys.len() == 3 { + info!( + target: LOG_TARGET, + event_key = %>::event_key(self), + invalid_keys = %>::event_keys_as_string(self, event), + "Invalid event keys." + ); + return false; + } + true + } + + async fn process( + &self, + _world: &WorldContractReader

, + _db: &mut Sql, + _block_number: u64, + _block_timestamp: u64, + _transaction_receipt: &TransactionReceiptWithBlockInfo, + _event_id: &str, + event: &Event, + ) -> Result<(), Error> { + let from = event.keys[1]; + let to = event.keys[2]; + + let value = U256::cairo_deserialize(&event.data, 0)?; + println!("from: {:?}, to: {:?}, value: {:?}", from, to, value); + + Ok(()) + } +} diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index e2b22e4d75..9adfadd49c 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -6,6 +6,8 @@ use starknet::providers::Provider; use crate::sql::Sql; +pub mod erc20_approval; +pub mod erc20_transfer; pub mod event_message; pub mod metadata_update; pub mod register_model; diff --git a/crates/torii/migrations/20240803102207_erc20.sql b/crates/torii/migrations/20240803102207_erc20.sql new file mode 100644 index 0000000000..ca33e64e7e --- /dev/null +++ b/crates/torii/migrations/20240803102207_erc20.sql @@ -0,0 +1 @@ +-- Add migration script here \ No newline at end of file From 02ee4839ace6b2884d555d7511dddadfdf50c93c Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Sun, 4 Aug 2024 15:05:46 +0530 Subject: [PATCH 02/23] mvp that reads and prints events --- bin/torii/src/main.rs | 8 ++- crates/torii/core/src/engine.rs | 17 +++--- .../src/processors/erc20_legacy_transfer.rs | 53 +++++++++++++++++++ .../core/src/processors/erc20_transfer.rs | 10 +++- crates/torii/core/src/processors/mod.rs | 1 + 5 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 crates/torii/core/src/processors/erc20_legacy_transfer.rs diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 7f63f7c836..676aa067af 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -27,6 +27,7 @@ use tokio::sync::broadcast; use tokio::sync::broadcast::Sender; use tokio_stream::StreamExt; use torii_core::engine::{Engine, EngineConfig, Processors}; +use torii_core::processors::erc20_legacy_transfer::Erc20LegacyTransferProcessor; use torii_core::processors::erc20_transfer::Erc20TransferProcessor; use torii_core::processors::event_message::EventMessageProcessor; use torii_core::processors::metadata_update::MetadataUpdateProcessor; @@ -123,7 +124,11 @@ async fn main() -> anyhow::Result<()> { let args = Args::parse(); // TODO: see where to get this addresses from, cli? config? - let addresses = [(Felt::from_str("0x123").unwrap(), 0), (Felt::from_str("0x345").unwrap(), 0)]; + let addresses = [( + Felt::from_str("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7") + .unwrap(), + 0, + )]; let mut start_block = args.start_block; for address in &addresses { @@ -187,6 +192,7 @@ async fn main() -> anyhow::Result<()> { Box::new(StoreUpdateRecordProcessor), Box::new(StoreUpdateMemberProcessor), Box::new(Erc20TransferProcessor), + Box::new(Erc20LegacyTransferProcessor), ], transaction: vec![Box::new(StoreTransactionProcessor)], ..Processors::default() diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index bdd21c169c..add0b87db3 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; use std::time::Duration; @@ -11,7 +11,7 @@ use starknet::core::types::{ TransactionReceiptWithBlockInfo, }; use starknet::core::utils::get_selector_from_name; -use starknet::providers::{Provider, SequencerGatewayProviderError}; +use starknet::providers::Provider; use tokio::sync::broadcast::Sender; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::time::sleep; @@ -63,7 +63,7 @@ pub struct Engine { shutdown_tx: Sender<()>, block_tx: Option>, // ERC20 tokens to index - tokens: Vec, + tokens: HashSet, } struct UnprocessedEvent { @@ -80,7 +80,7 @@ impl Engine

{ config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, - tokens: Vec, + tokens: HashSet, ) -> Self { Self { world, @@ -236,9 +236,8 @@ impl Engine

{ keys: None, }; - let world_events_pages = - get_all_events(&self.provider, events_filter, self.config.events_chunk_size); - fetch_all_events_tasks.push(world_events_pages); + let _ = get_all_events(&self.provider, events_filter, self.config.events_chunk_size); + // fetch_all_events_tasks.push(world_events_pages); for token in &self.tokens { let events_filter = EventFilter { @@ -392,7 +391,9 @@ impl Engine

{ if let Some(events) = events { let mut world_event = false; for (event_idx, event) in events.iter().enumerate() { - if event.from_address != self.world.address { + if event.from_address != self.world.address + && !self.tokens.contains(&event.from_address) + { continue; } diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs new file mode 100644 index 0000000000..e098eeeb3c --- /dev/null +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -0,0 +1,53 @@ +use anyhow::Error; +use async_trait::async_trait; +use cainome::cairo_serde::{CairoSerde, U256}; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::providers::Provider; + +use super::EventProcessor; +use crate::sql::Sql; + +// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_legacy_transfer"; + +#[derive(Default, Debug)] +pub struct Erc20LegacyTransferProcessor; + +#[async_trait] +impl

EventProcessor

for Erc20LegacyTransferProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "Transfer".to_string() + } + + fn validate(&self, event: &Event) -> bool { + // key: [hash(Transfer)] + // data: [from, to, value.0, value.1] + if event.keys.len() == 1 && event.data.len() == 4 { + return true; + } + + false + } + + async fn process( + &self, + _world: &WorldContractReader

, + _db: &mut Sql, + _block_number: u64, + _block_timestamp: u64, + _transaction_receipt: &TransactionReceiptWithBlockInfo, + _event_id: &str, + event: &Event, + ) -> Result<(), Error> { + let from = event.data[0]; + let to = event.data[1]; + + let value = U256::cairo_deserialize(&event.data, 2)?; + println!("from: {:?}, to: {:?}, value: {:?}", from, to, value); + + Ok(()) + } +} diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index cf3786cd51..ef1a731067 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -24,7 +24,14 @@ where } fn validate(&self, event: &Event) -> bool { - if event.keys.len() == 3 { + if event.keys.len() == 3 && event.data.len() == 2 { + return true; + } else if event.keys.len() == 1 && event.data.len() == 4 { + // Legacy `Transfer` event which will be processed by `Erc20TransferLegacyProcessor` + // we only do this here only to avoid printing `info` trace more than once. + return false; + } else { + // If its neither print and error info!( target: LOG_TARGET, event_key = %>::event_key(self), @@ -33,7 +40,6 @@ where ); return false; } - true } async fn process( diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index 9adfadd49c..cbad6c4dfc 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -7,6 +7,7 @@ use starknet::providers::Provider; use crate::sql::Sql; pub mod erc20_approval; +pub mod erc20_legacy_transfer; pub mod erc20_transfer; pub mod event_message; pub mod metadata_update; From 1375b32addaa59aff92a060cf04244906db07cc0 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Sun, 4 Aug 2024 16:28:42 +0530 Subject: [PATCH 03/23] add erc721 and update comment --- bin/torii/src/main.rs | 2 + .../core/src/processors/erc20_approval.rs | 1 - .../src/processors/erc20_legacy_transfer.rs | 2 +- .../core/src/processors/erc20_transfer.rs | 22 +++----- .../core/src/processors/erc721_transfer.rs | 54 +++++++++++++++++++ crates/torii/core/src/processors/mod.rs | 2 +- .../torii/migrations/20240803102207_erc20.sql | 1 - 7 files changed, 64 insertions(+), 20 deletions(-) delete mode 100644 crates/torii/core/src/processors/erc20_approval.rs create mode 100644 crates/torii/core/src/processors/erc721_transfer.rs delete mode 100644 crates/torii/migrations/20240803102207_erc20.sql diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 676aa067af..0e1235fb8a 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -29,6 +29,7 @@ use tokio_stream::StreamExt; use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::processors::erc20_legacy_transfer::Erc20LegacyTransferProcessor; use torii_core::processors::erc20_transfer::Erc20TransferProcessor; +use torii_core::processors::erc721_transfer::Erc721TransferProcessor; use torii_core::processors::event_message::EventMessageProcessor; use torii_core::processors::metadata_update::MetadataUpdateProcessor; use torii_core::processors::register_model::RegisterModelProcessor; @@ -193,6 +194,7 @@ async fn main() -> anyhow::Result<()> { Box::new(StoreUpdateMemberProcessor), Box::new(Erc20TransferProcessor), Box::new(Erc20LegacyTransferProcessor), + Box::new(Erc721TransferProcessor), ], transaction: vec![Box::new(StoreTransactionProcessor)], ..Processors::default() diff --git a/crates/torii/core/src/processors/erc20_approval.rs b/crates/torii/core/src/processors/erc20_approval.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/torii/core/src/processors/erc20_approval.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs index e098eeeb3c..2e249bc62e 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -46,7 +46,7 @@ where let to = event.data[1]; let value = U256::cairo_deserialize(&event.data, 2)?; - println!("from: {:?}, to: {:?}, value: {:?}", from, to, value); + println!("ERC20 Legacy Transfer from: {:?}, to: {:?}, value: {:?}", from, to, value); Ok(()) } diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index ef1a731067..bc3e7caad8 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -4,12 +4,11 @@ use cainome::cairo_serde::{CairoSerde, U256}; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; use starknet::providers::Provider; -use tracing::info; use super::EventProcessor; use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; +// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; #[derive(Default, Debug)] pub struct Erc20TransferProcessor; @@ -24,22 +23,13 @@ where } fn validate(&self, event: &Event) -> bool { + // key: [hash(Transfer), from, to] + // data: [value.0, value.1] if event.keys.len() == 3 && event.data.len() == 2 { return true; - } else if event.keys.len() == 1 && event.data.len() == 4 { - // Legacy `Transfer` event which will be processed by `Erc20TransferLegacyProcessor` - // we only do this here only to avoid printing `info` trace more than once. - return false; - } else { - // If its neither print and error - info!( - target: LOG_TARGET, - event_key = %>::event_key(self), - invalid_keys = %>::event_keys_as_string(self, event), - "Invalid event keys." - ); - return false; } + + false } async fn process( @@ -56,7 +46,7 @@ where let to = event.keys[2]; let value = U256::cairo_deserialize(&event.data, 0)?; - println!("from: {:?}, to: {:?}, value: {:?}", from, to, value); + println!("ERC20 Transfer from: {:?}, to: {:?}, value: {:?}", from, to, value); Ok(()) } diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs new file mode 100644 index 0000000000..5bc1cd7871 --- /dev/null +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -0,0 +1,54 @@ +use anyhow::Error; +use async_trait::async_trait; +use cainome::cairo_serde::{CairoSerde, U256}; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::providers::Provider; + +use super::EventProcessor; +use crate::sql::Sql; + +// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_transfer"; + +#[derive(Default, Debug)] +pub struct Erc721TransferProcessor; + +#[async_trait] +impl

EventProcessor

for Erc721TransferProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "Transfer".to_string() + } + + fn validate(&self, event: &Event) -> bool { + // ref: https://github.com/OpenZeppelin/cairo-contracts/blob/eabfa029b7b681d9e83bf171f723081b07891016/packages/token/src/erc721/erc721.cairo#L44-L53 + // key: [hash(Transfer), from, to, token_id.low, token_id.high] + // data: [] + if event.keys.len() == 5 && event.data.len() == 0 { + return true; + } + + false + } + + async fn process( + &self, + _world: &WorldContractReader

, + _db: &mut Sql, + _block_number: u64, + _block_timestamp: u64, + _transaction_receipt: &TransactionReceiptWithBlockInfo, + _event_id: &str, + event: &Event, + ) -> Result<(), Error> { + let from = event.keys[1]; + let to = event.keys[2]; + + let token_id = U256::cairo_deserialize(&event.keys, 3)?; + println!("ERC721 Transfer from: {:?}, to: {:?}, value: {:?}", from, to, token_id); + + Ok(()) + } +} diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index cbad6c4dfc..9b79f226db 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -6,9 +6,9 @@ use starknet::providers::Provider; use crate::sql::Sql; -pub mod erc20_approval; pub mod erc20_legacy_transfer; pub mod erc20_transfer; +pub mod erc721_transfer; pub mod event_message; pub mod metadata_update; pub mod register_model; diff --git a/crates/torii/migrations/20240803102207_erc20.sql b/crates/torii/migrations/20240803102207_erc20.sql deleted file mode 100644 index ca33e64e7e..0000000000 --- a/crates/torii/migrations/20240803102207_erc20.sql +++ /dev/null @@ -1 +0,0 @@ --- Add migration script here \ No newline at end of file From be63326dd98bbe59e044a2f12b5f7f3586993b51 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 5 Aug 2024 12:52:44 +0530 Subject: [PATCH 04/23] add sql schema for erc --- .../migrations/20240803102207_add_erc.sql | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 crates/torii/migrations/20240803102207_add_erc.sql diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql new file mode 100644 index 0000000000..3f0576b961 --- /dev/null +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -0,0 +1,34 @@ +CREATE TABLE Erc20Balance ( + address TEXT NOT NULL, + token_address TEXT NOT NULL, + balance TEXT NOT NULL, + PRIMARY KEY (address, token_address) +); + +CREATE TABLE Erc721Balance ( + address TEXT NOT NULL, + token_address TEXT NOT NULL, + token_id TEXT NOT NULL, + token_uri TEXT NOT NULL, + PRIMARY KEY (address, token_address, token_id) +) + +-- these are metadata of the contracts which we would need to fetch from RPC separately +-- not part of events engine + +-- Do we need this? + +CREATE TABLE Erc20Contract ( + address TEXT NOT NULL PRIMARY KEY, + decimals INTEGER NOT NULL, + name TEXT NOT NULL, + symbol TEXT NOT NULL, + total_supply TEXT NOT NULL, +) + +CREATE TABLE Erc721Contract ( + address TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + symbol TEXT NOT NULL, + total_supply TEXT NOT NULL, +) \ No newline at end of file From e0f80bb7a247c968645ca6e261d13baddcf17a48 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 6 Aug 2024 21:32:03 +0530 Subject: [PATCH 05/23] add db logic --- .../src/processors/erc20_legacy_transfer.rs | 17 +- .../core/src/processors/erc20_transfer.rs | 17 +- .../core/src/processors/erc721_transfer.rs | 17 +- crates/torii/core/src/sql.rs | 222 +++++++++++++++++- .../migrations/20240803102207_add_erc.sql | 46 ++-- 5 files changed, 283 insertions(+), 36 deletions(-) diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs index 2e249bc62e..ed5303049a 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -1,14 +1,15 @@ use anyhow::Error; use async_trait::async_trait; -use cainome::cairo_serde::{CairoSerde, U256}; +use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; -use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo, U256}; use starknet::providers::Provider; +use tracing::info; use super::EventProcessor; use crate::sql::Sql; -// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_legacy_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_legacy_transfer"; #[derive(Default, Debug)] pub struct Erc20LegacyTransferProcessor; @@ -35,18 +36,22 @@ where async fn process( &self, _world: &WorldContractReader

, - _db: &mut Sql, + db: &mut Sql, _block_number: u64, _block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, ) -> Result<(), Error> { + let token_address = event.from_address; let from = event.data[0]; let to = event.data[1]; - let value = U256::cairo_deserialize(&event.data, 2)?; - println!("ERC20 Legacy Transfer from: {:?}, to: {:?}, value: {:?}", from, to, value); + let value = U256Cainome::cairo_deserialize(&event.data, 2)?; + let value = U256::from_words(value.low, value.high); + + db.handle_erc20_transfer(token_address, from, to, value).await?; + info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "Legacy ERC20 Transfer"); Ok(()) } diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index bc3e7caad8..133fb74bd7 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -1,14 +1,15 @@ use anyhow::Error; use async_trait::async_trait; -use cainome::cairo_serde::{CairoSerde, U256}; +use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; -use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo, U256}; use starknet::providers::Provider; +use tracing::info; use super::EventProcessor; use crate::sql::Sql; -// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; #[derive(Default, Debug)] pub struct Erc20TransferProcessor; @@ -35,18 +36,22 @@ where async fn process( &self, _world: &WorldContractReader

, - _db: &mut Sql, + db: &mut Sql, _block_number: u64, _block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, ) -> Result<(), Error> { + let token_address = event.from_address; let from = event.keys[1]; let to = event.keys[2]; - let value = U256::cairo_deserialize(&event.data, 0)?; - println!("ERC20 Transfer from: {:?}, to: {:?}, value: {:?}", from, to, value); + let value = U256Cainome::cairo_deserialize(&event.data, 0)?; + let value = U256::from_words(value.low, value.high); + + db.handle_erc20_transfer(token_address, from, to, value).await?; + info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "ERC20 Transfer"); Ok(()) } diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs index 5bc1cd7871..dc6f900c18 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -1,14 +1,15 @@ use anyhow::Error; use async_trait::async_trait; -use cainome::cairo_serde::{CairoSerde, U256}; +use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; -use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo, U256}; use starknet::providers::Provider; +use tracing::info; use super::EventProcessor; use crate::sql::Sql; -// pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_transfer"; #[derive(Default, Debug)] pub struct Erc721TransferProcessor; @@ -36,18 +37,22 @@ where async fn process( &self, _world: &WorldContractReader

, - _db: &mut Sql, + db: &mut Sql, _block_number: u64, _block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, ) -> Result<(), Error> { + let token_address = event.from_address; let from = event.keys[1]; let to = event.keys[2]; - let token_id = U256::cairo_deserialize(&event.keys, 3)?; - println!("ERC721 Transfer from: {:?}, to: {:?}, value: {:?}", from, to, token_id); + let token_id = U256Cainome::cairo_deserialize(&event.keys, 3)?; + let token_id = U256::from_words(token_id.low, token_id.high); + + db.handle_erc721_transfer(token_address, from, to, token_id).await?; + info!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer"); Ok(()) } diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 9be96211fc..cea685a072 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1,4 +1,5 @@ use std::convert::TryInto; +use std::ops::{Add, Sub}; use std::str::FromStr; use anyhow::{anyhow, Result}; @@ -8,9 +9,10 @@ use dojo_types::schema::{EnumOption, Member, Struct, Ty}; use dojo_world::contracts::abi::model::Layout; use dojo_world::contracts::naming::compute_selector_from_names; use dojo_world::metadata::WorldMetadata; +use num_traits::{FromPrimitive, ToPrimitive}; use sqlx::pool::PoolConnection; use sqlx::{Pool, Row, Sqlite}; -use starknet::core::types::{Event, Felt, InvokeTransaction, Transaction}; +use starknet::core::types::{Event, Felt, InvokeTransaction, Transaction, U256}; use starknet_crypto::poseidon_hash_many; use super::World; @@ -127,7 +129,7 @@ impl Sql { executed_at=EXCLUDED.executed_at RETURNING *"; let model_registered: ModelRegistered = sqlx::query_as(insert_models) // this is temporary until the model hash is precomputed - .bind(format!("{:#x}", selector)) + .bind(&format!("{:#x}", selector)) .bind(namespace) .bind(model.name()) .bind(format!("{class_hash:#x}")) @@ -710,7 +712,11 @@ impl Sql { Ty::Enum(e) => { if e.options.iter().all( |o| { - if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false } + if let Ty::Tuple(t) = &o.ty { + t.is_empty() + } else { + false + } }, ) { return; @@ -1130,9 +1136,219 @@ impl Sql { Ok(()) } + + // Registers a new ERC20 contract in erc20_contracts table + pub fn register_erc20( + &mut self, + address: Felt, + decimals: u8, + name: String, + symbol: String, + total_supply: U256, + ) -> Result<()> { + let insert_query = "INSERT INTO erc20_contracts (token_address, name, symbol, decimals \ + total_supply) VALUES (?, ?, ?, ?, ?)"; + + self.query_queue.enqueue( + insert_query, + vec![ + Argument::FieldElement(address), + Argument::String(name), + Argument::String(symbol), + Argument::Int(decimals.into()), + Argument::String(u256_to_sql_string(&total_supply)), + ], + ); + + Ok(()) + } + + pub fn register_erc721( + &mut self, + token_address: Felt, + name: String, + symbol: String, + total_supply: U256, + ) -> Result<()> { + let insert_query = "INSERT INTO erc721_contracts (token_address, name, symbol, \ + total_supply) VALUES (?, ?, ?, ?)"; + + self.query_queue.enqueue( + insert_query, + vec![ + Argument::FieldElement(token_address), + Argument::String(name), + Argument::String(symbol), + Argument::String(u256_to_sql_string(&total_supply)), + ], + ); + + Ok(()) + } + + pub async fn handle_erc20_transfer( + &mut self, + token_address: Felt, + from: Felt, + to: Felt, + amount: U256, + ) -> Result<()> { + // Insert transfer event to erc20_transfers table + { + let insert_query = + "INSERT INTO erc20_transfers (token_address, from, to, amount) VALUES (?, ?, ?, ?)"; + + self.query_queue.enqueue( + insert_query, + vec![ + Argument::FieldElement(token_address), + Argument::FieldElement(from), + Argument::FieldElement(to), + Argument::String(u256_to_sql_string(&amount)), + ], + ); + } + + // Update balances in erc20_balance table + { + // NOTE: formatting here should match the format we use for Argument type in QueryQueue + // TODO: abstract this so they cannot mismatch + + // Since balance are stored as TEXT in db, we cannot directly use INSERT OR UPDATE + // statements. + // Fetch balances for both `from` and `to` addresses, update them and write back to db + let query = sqlx::query_as::<_, (String, String)>( + "SELECT address, balance FROM erc20_balances WHERE token_address = ? AND address \ + IN (?, ?)", + ) + .bind(format!("{:#x}", token_address)) + .bind(format!("{:#x}", from)) + .bind(format!("{:#x}", to)); + + // (address, balance) + let balances: Vec<(String, String)> = query.fetch_all(&self.pool).await?; + // (address, balance) is primary key in DB, and we are fetching for 2 addresses so there + // should be at most 2 rows returned + assert!(balances.len() <= 2); + + let from_balance = balances + .iter() + .find(|(address, _)| address == &format!("{:#x}", from)) + .map(|(_, balance)| balance.clone()) + .unwrap_or_else(|| format!("0x0{}0x0", FELT_DELIMITER)); + + let to_balance = balances + .iter() + .find(|(address, _)| address == &format!("{:#x}", to)) + .map(|(_, balance)| balance.clone()) + .unwrap_or_else(|| format!("0x0{}0x0", FELT_DELIMITER)); + + let from_balance = sql_string_to_u256(&from_balance); + let to_balance = sql_string_to_u256(&to_balance); + + let new_from_balance = + if from != Felt::ZERO { from_balance.sub(amount) } else { from_balance }; + let new_to_balance = if to != Felt::ZERO { to_balance.add(amount) } else { to_balance }; + + let update_query = " + INSERT INTO erc20_balances (address, token_address, balance) + VALUES (?, ?, ?) + ON CONFLICT (address, token_address) + DO UPDATE SET balance = excluded.balance"; + + self.query_queue.enqueue( + update_query, + vec![ + Argument::FieldElement(from), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&new_from_balance)), + ], + ); + + self.query_queue.enqueue( + update_query, + vec![ + Argument::FieldElement(to), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&new_to_balance)), + ], + ); + } + self.query_queue.execute_all().await?; + + Ok(()) + } + + pub async fn handle_erc721_transfer( + &mut self, + token_address: Felt, + from: Felt, + to: Felt, + token_id: U256, + ) -> Result<()> { + // Insert transfer event to erc721_transfers table + { + let insert_query = "INSERT INTO erc721_transfers (token_address, from, to, token_id) \ + VALUES (?, ?, ?, ?)"; + + self.query_queue.enqueue( + insert_query, + vec![ + Argument::FieldElement(token_address), + Argument::FieldElement(from), + Argument::FieldElement(to), + Argument::String(u256_to_sql_string(&token_id)), + ], + ); + } + + // Update balances in erc721_balances table + { + if from != Felt::ZERO { + self.query_queue.enqueue( + "DELETE FROM erc721_balances WHERE address = ? AND token_address = ? AND token_id = ?", + vec![ + Argument::FieldElement(from), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&token_id)), + ], + ); + } + + if to != Felt::ZERO { + self.query_queue.enqueue( + "INSERT INTO erc721_balances (address, token_address, token_id) VALUES (?, ?, ?)", + vec![ + Argument::FieldElement(to), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&token_id)), + ], + ); + } + } + self.query_queue.execute_all().await?; + + Ok(()) + } } fn felts_sql_string(felts: &[Felt]) -> String { felts.iter().map(|k| format!("{:#x}", k)).collect::>().join(FELT_DELIMITER) + FELT_DELIMITER } + +fn u256_to_sql_string(u256: &U256) -> String { + let felts = [u256.low(), u256.high()].map(|i| Felt::from_u128(i).unwrap()); + felts_sql_string(&felts) +} + +fn sql_string_to_u256(sql_string: &str) -> U256 { + let low_high = + sql_string.split(FELT_DELIMITER).map(|s| Felt::from_str(s).unwrap()).collect::>(); + + assert!(low_high.len() == 2); + let low = low_high[0].to_u128().unwrap(); + let high = low_high[1].to_u128().unwrap(); + + U256::from_words(low, high) +} diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 3f0576b961..09482eff78 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -1,34 +1,50 @@ -CREATE TABLE Erc20Balance ( +CREATE TABLE erc20_balances ( address TEXT NOT NULL, token_address TEXT NOT NULL, balance TEXT NOT NULL, PRIMARY KEY (address, token_address) ); -CREATE TABLE Erc721Balance ( +CREATE TABLE erc721_balances ( address TEXT NOT NULL, token_address TEXT NOT NULL, token_id TEXT NOT NULL, - token_uri TEXT NOT NULL, + token_uri TEXT, PRIMARY KEY (address, token_address, token_id) -) +); + +CREATE TABLE erc20_transfers ( + address TEXT NOT NULL, + token_address TEXT NOT NULL, + from_address TEXT NOT NULL, + to_address TEXT NOT NULL, + amount TEXT NOT NULL, + PRIMARY KEY (address, token_address) +); + +CREATE TABLE erc721_transfers ( + address TEXT NOT NULL, + token_address TEXT NOT NULL, + from_address TEXT NOT NULL, + to_address TEXT NOT NULL, + token_id TEXT NOT NULL, + PRIMARY KEY (address, token_address, token_id) +); -- these are metadata of the contracts which we would need to fetch from RPC separately -- not part of events engine --- Do we need this? - -CREATE TABLE Erc20Contract ( - address TEXT NOT NULL PRIMARY KEY, - decimals INTEGER NOT NULL, +CREATE TABLE erc20_contracts ( + token_address TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, symbol TEXT NOT NULL, - total_supply TEXT NOT NULL, -) + decimals INTEGER NOT NULL, + total_supply TEXT NOT NULL +); -CREATE TABLE Erc721Contract ( - address TEXT NOT NULL PRIMARY KEY, +CREATE TABLE erc721_contracts ( + token_address TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, symbol TEXT NOT NULL, - total_supply TEXT NOT NULL, -) \ No newline at end of file + total_supply TEXT NOT NULL +); \ No newline at end of file From e46e8f6527beaf2584cd8b9de2c926284db3c355 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Wed, 7 Aug 2024 14:52:51 +0530 Subject: [PATCH 06/23] remove token_uri from balances --- crates/torii/migrations/20240803102207_add_erc.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 09482eff78..a4a5e44cf5 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -9,7 +9,6 @@ CREATE TABLE erc721_balances ( address TEXT NOT NULL, token_address TEXT NOT NULL, token_id TEXT NOT NULL, - token_uri TEXT, PRIMARY KEY (address, token_address, token_id) ); From 8c959e31f30970d4f7643f192d037e63bbc9c5f7 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Wed, 7 Aug 2024 18:52:43 +0530 Subject: [PATCH 07/23] add grpc endpoint (wip) --- crates/torii/graphql/src/constants.rs | 3 + crates/torii/graphql/src/mapping.rs | 10 + .../torii/graphql/src/object/erc20_balance.rs | 282 ++++++++++++++++++ crates/torii/graphql/src/object/mod.rs | 1 + crates/torii/graphql/src/schema.rs | 2 + 5 files changed, 298 insertions(+) create mode 100644 crates/torii/graphql/src/object/erc20_balance.rs diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index a01031e5e2..8f3ddc7c0e 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -20,6 +20,7 @@ pub const INTERNAL_ENTITY_ID_KEY: &str = "$entity_id$"; // objects namespaced to avoid conflicts with user models pub const ENTITY_TYPE_NAME: &str = "World__Entity"; +pub const ERC20_BALANCE_TYPE_NAME: &str = "World__Erc20Balance"; pub const EVENT_MESSAGE_TYPE_NAME: &str = "World__EventMessage"; pub const MODEL_TYPE_NAME: &str = "World__Model"; pub const EVENT_TYPE_NAME: &str = "World__Event"; @@ -43,6 +44,8 @@ pub const CONTENT_NAMES: (&str, &str) = ("content", "contents"); pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas"); pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions"); pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); +pub const ERC20_BALANCE_NAMES: (&str, &str) = ("erc20Balance", "erc20Balances"); +pub const ERC721_BALANCE_NAMES: (&str, &str) = ("erc721Balance", "erc721Balances"); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index a3839a9f4a..2292979c59 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -143,4 +143,14 @@ lazy_static! { TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())) ), ]); + pub static ref ERC20_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + ]); + pub static ref ERC721_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("tokenId"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + ]); } diff --git a/crates/torii/graphql/src/object/erc20_balance.rs b/crates/torii/graphql/src/object/erc20_balance.rs new file mode 100644 index 0000000000..2b67838b3e --- /dev/null +++ b/crates/torii/graphql/src/object/erc20_balance.rs @@ -0,0 +1,282 @@ +use async_graphql::dynamic::indexmap::IndexMap; +use async_graphql::dynamic::{ + Field, FieldFuture, FieldValue, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, +}; +use async_graphql::{Name, Value}; +use async_recursion::async_recursion; +use sqlx::pool::PoolConnection; +use sqlx::{Pool, Sqlite}; +use tokio_stream::StreamExt; +use torii_core::simple_broker::SimpleBroker; +use torii_core::types::Entity; + +use super::inputs::keys_input::keys_argument; +use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; +use crate::constants::{ + DATETIME_FORMAT, ENTITY_TABLE, ERC20_BALANCE_NAMES, ERC20_BALANCE_TYPE_NAME, EVENT_ID_COLUMN, + ID_COLUMN, +}; +use crate::mapping::ERC20_BALANCE_TYPE_MAPPING; +use crate::object::{resolve_many, resolve_one}; +use crate::query::{type_mapping_query, value_mapping_from_row}; +use crate::types::TypeData; +use crate::utils; + +#[derive(Debug)] +pub struct Erc20Balance; + +impl BasicObject for Erc20Balance { + fn name(&self) -> (&str, &str) { + ERC20_BALANCE_NAMES + } + + fn type_name(&self) -> &str { + ERC20_BALANCE_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC20_BALANCE_TYPE_MAPPING + } + + fn related_fields(&self) -> Option> { + Some(vec![]) + } +} + +impl ResolvableObject for Erc20Balance { + fn resolvers(&self) -> Vec { + let resolve_one = resolve_one( + ENTITY_TABLE, + ID_COLUMN, + self.name().0, + self.type_name(), + self.type_mapping(), + ); + + let mut resolve_many = resolve_many( + ENTITY_TABLE, + EVENT_ID_COLUMN, + self.name().1, + self.type_name(), + self.type_mapping(), + ); + resolve_many = keys_argument(resolve_many); + + vec![resolve_one, resolve_many] + } + + fn subscriptions(&self) -> Option> { + Some(vec![SubscriptionField::new( + "entityUpdated", + TypeRef::named_nn(self.type_name()), + |ctx| { + SubscriptionFieldFuture::new(async move { + let id = match ctx.args.get("id") { + Some(id) => Some(id.string()?.to_string()), + None => None, + }; + // if id is None, then subscribe to all entities + // if id is Some, then subscribe to only the entity with that id + Ok(SimpleBroker::::subscribe().filter_map(move |entity: Entity| { + if id.is_none() || id == Some(entity.id.clone()) { + Some(Ok(Value::Object(EntityObject::value_mapping(entity)))) + } else { + // id != entity.id , then don't send anything, still listening + None + } + })) + }) + }, + ) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) + } +} + +impl Erc20Balance { + pub fn value_mapping(entity: Entity) -> ValueMapping { + let keys: Vec<&str> = entity.keys.split('/').filter(|&k| !k.is_empty()).collect(); + IndexMap::from([ + (Name::new("id"), Value::from(entity.id)), + (Name::new("keys"), Value::from(keys)), + (Name::new("eventId"), Value::from(entity.event_id)), + ( + Name::new("createdAt"), + Value::from(entity.created_at.format(DATETIME_FORMAT).to_string()), + ), + ( + Name::new("updatedAt"), + Value::from(entity.updated_at.format(DATETIME_FORMAT).to_string()), + ), + ( + Name::new("executedAt"), + Value::from(entity.executed_at.format(DATETIME_FORMAT).to_string()), + ), + ]) + } +} + +fn model_union_field() -> Field { + Field::new("models", TypeRef::named_list("ModelUnion"), move |ctx| { + FieldFuture::new(async move { + match ctx.parent_value.try_to_value()? { + Value::Object(indexmap) => { + let mut conn = ctx.data::>()?.acquire().await?; + + let entity_id = utils::extract::(indexmap, "id")?; + // fetch name from the models table + // using the model id (hashed model name) + let model_ids: Vec<(String, String, String)> = sqlx::query_as( + "SELECT id, namespace, name + FROM models + WHERE id IN ( + SELECT model_id + FROM entity_model + WHERE entity_id = ? + )", + ) + .bind(&entity_id) + .fetch_all(&mut *conn) + .await?; + + let mut results: Vec> = Vec::new(); + for (id, namespace, name) in model_ids { + // the model id in the model mmeebrs table is the hashed model name (id) + let type_mapping = type_mapping_query(&mut conn, &id).await?; + + // but the table name for the model data is the unhashed model name + let data: ValueMapping = match model_data_recursive_query( + &mut conn, + vec![format!("{namespace}-{name}")], + &entity_id, + &[], + &type_mapping, + false, + ) + .await? + { + Value::Object(map) => map, + _ => unreachable!(), + }; + + results.push(FieldValue::with_type( + FieldValue::owned_any(data), + utils::type_name_from_names(&namespace, &name), + )) + } + + Ok(Some(FieldValue::list(results))) + } + _ => Err("incorrect value, requires Value::Object".into()), + } + }) + }) +} + +// TODO: flatten query +#[async_recursion] +pub async fn model_data_recursive_query( + conn: &mut PoolConnection, + path_array: Vec, + entity_id: &str, + indexes: &[i64], + type_mapping: &TypeMapping, + is_list: bool, +) -> sqlx::Result { + // For nested types, we need to remove prefix in path array + let namespace = format!("{}_", path_array[0]); + let table_name = &path_array.join("$").replace(&namespace, ""); + let mut query = format!("SELECT * FROM [{}] WHERE entity_id = '{}' ", table_name, entity_id); + for (column_idx, index) in indexes.iter().enumerate() { + query.push_str(&format!("AND idx_{} = {} ", column_idx, index)); + } + + let rows = sqlx::query(&query).fetch_all(conn.as_mut()).await?; + if rows.is_empty() { + return Ok(Value::List(vec![])); + } + + let value_mapping: Value; + let mut nested_value_mappings = Vec::new(); + + for (idx, row) in rows.iter().enumerate() { + let mut nested_value_mapping = value_mapping_from_row(row, type_mapping, true)?; + + for (field_name, type_data) in type_mapping { + if let TypeData::Nested((_, nested_mapping)) = type_data { + let mut nested_path = path_array.clone(); + nested_path.push(field_name.to_string()); + + let nested_values = model_data_recursive_query( + conn, + nested_path, + entity_id, + &if is_list { + let mut indexes = indexes.to_vec(); + indexes.push(idx as i64); + indexes + } else { + indexes.to_vec() + }, + nested_mapping, + false, + ) + .await?; + + nested_value_mapping.insert(Name::new(field_name), nested_values); + } else if let TypeData::List(inner) = type_data { + let mut nested_path = path_array.clone(); + nested_path.push(field_name.to_string()); + + let data = match model_data_recursive_query( + conn, + nested_path, + entity_id, + // this might need to be changed to support 2d+ arrays + &if is_list { + let mut indexes = indexes.to_vec(); + indexes.push(idx as i64); + indexes + } else { + indexes.to_vec() + }, + &IndexMap::from([(Name::new("data"), *inner.clone())]), + true, + ) + .await? + { + // map our list which uses a data field as a place holder + // for all elements to get the elemnt directly + Value::List(data) => data + .iter() + .map(|v| match v { + Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), + ty => unreachable!( + "Expected Value::Object for list \"data\" field, got {:?}", + ty + ), + }) + .collect(), + Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), + ty => { + unreachable!( + "Expected Value::List or Value::Object for list, got {:?}", + ty + ); + } + }; + + nested_value_mapping.insert(Name::new(field_name), data); + } + } + + nested_value_mappings.push(Value::Object(nested_value_mapping)); + } + + if is_list { + value_mapping = Value::List(nested_value_mappings); + } else { + value_mapping = nested_value_mappings.pop().unwrap(); + } + + Ok(value_mapping) +} diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index c1046ffbe4..92271456c8 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -1,5 +1,6 @@ pub mod connection; pub mod entity; +pub mod erc20_balance; pub mod event; pub mod event_message; pub mod inputs; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 48a915345b..6b91b5fd81 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -10,6 +10,7 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; +use crate::object::erc20_balance::Erc20Balance; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -112,6 +113,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Date: Thu, 8 Aug 2024 16:58:37 +0530 Subject: [PATCH 08/23] update schema + how we serialize u256 --- crates/torii/core/src/sql.rs | 18 ++++++------------ .../migrations/20240803102207_add_erc.sql | 16 ++++++++-------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index cea685a072..0769b5d67d 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -19,6 +19,7 @@ use super::World; use crate::model::ModelSQLReader; use crate::query_queue::{Argument, QueryQueue}; use crate::simple_broker::SimpleBroker; +use crate::sql; use crate::types::{ Entity as EntityUpdated, Event as EventEmitted, EventMessage as EventMessageUpdated, Model as ModelRegistered, @@ -1235,13 +1236,13 @@ impl Sql { .iter() .find(|(address, _)| address == &format!("{:#x}", from)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("0x0{}0x0", FELT_DELIMITER)); + .unwrap_or_else(|| format!("0x0")); let to_balance = balances .iter() .find(|(address, _)| address == &format!("{:#x}", to)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("0x0{}0x0", FELT_DELIMITER)); + .unwrap_or_else(|| format!("0x0")); let from_balance = sql_string_to_u256(&from_balance); let to_balance = sql_string_to_u256(&to_balance); @@ -1338,17 +1339,10 @@ fn felts_sql_string(felts: &[Felt]) -> String { } fn u256_to_sql_string(u256: &U256) -> String { - let felts = [u256.low(), u256.high()].map(|i| Felt::from_u128(i).unwrap()); - felts_sql_string(&felts) + format!("{:#x}", u256) } fn sql_string_to_u256(sql_string: &str) -> U256 { - let low_high = - sql_string.split(FELT_DELIMITER).map(|s| Felt::from_str(s).unwrap()).collect::>(); - - assert!(low_high.len() == 2); - let low = low_high[0].to_u128().unwrap(); - let high = low_high[1].to_u128().unwrap(); - - U256::from_words(low, high) + let sql_string = sql_string.strip_prefix("0x").unwrap_or(sql_string); + U256::from(crypto_bigint::U256::from_be_hex(sql_string)) } diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index a4a5e44cf5..c1805fc0f3 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -1,33 +1,33 @@ CREATE TABLE erc20_balances ( - address TEXT NOT NULL, + account_address TEXT NOT NULL, token_address TEXT NOT NULL, balance TEXT NOT NULL, - PRIMARY KEY (address, token_address) + PRIMARY KEY (account_address, token_address) ); CREATE TABLE erc721_balances ( - address TEXT NOT NULL, + account_address TEXT NOT NULL, token_address TEXT NOT NULL, token_id TEXT NOT NULL, - PRIMARY KEY (address, token_address, token_id) + PRIMARY KEY (account_address, token_address, token_id) ); CREATE TABLE erc20_transfers ( - address TEXT NOT NULL, + account_address TEXT NOT NULL, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, amount TEXT NOT NULL, - PRIMARY KEY (address, token_address) + PRIMARY KEY (account_address, token_address) ); CREATE TABLE erc721_transfers ( - address TEXT NOT NULL, + account_address TEXT NOT NULL, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, token_id TEXT NOT NULL, - PRIMARY KEY (address, token_address, token_id) + PRIMARY KEY (account_address, token_address, token_id) ); -- these are metadata of the contracts which we would need to fetch from RPC separately From 44bc339a012232ccbf8df552335e77edfe66bfa1 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Thu, 8 Aug 2024 16:59:22 +0530 Subject: [PATCH 09/23] comment out graphql changes --- crates/torii/graphql/src/object/mod.rs | 2 +- crates/torii/graphql/src/schema.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index 92271456c8..5c7c125458 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -1,6 +1,6 @@ pub mod connection; pub mod entity; -pub mod erc20_balance; +// pub mod erc20_balance; pub mod event; pub mod event_message; pub mod inputs; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 6b91b5fd81..ab823fe50a 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -10,7 +10,7 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; -use crate::object::erc20_balance::Erc20Balance; +// use crate::object::erc20_balance::Erc20Balance; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -113,7 +113,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Date: Fri, 9 Aug 2024 20:20:45 +0530 Subject: [PATCH 10/23] tmp grpc --- .../graphql/src/object/erc721_balance.rs | 0 crates/torii/grpc/proto/world.proto | 26 +++++++- crates/torii/grpc/src/server/mod.rs | 66 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 crates/torii/graphql/src/object/erc721_balance.rs diff --git a/crates/torii/graphql/src/object/erc721_balance.rs b/crates/torii/graphql/src/object/erc721_balance.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 8e8010fef1..e5baf4f576 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -36,8 +36,13 @@ service World { // Subscribe to events rpc SubscribeEvents (SubscribeEventsRequest) returns (stream SubscribeEventsResponse); -} + // Retrieve ERC20 balance + rpc Erc20Balance (Erc20BalanceRequest) returns (Erc20BalanceResponse); + + // Retrieve ERC721 balance + rpc Erc721Balance (Erc721BalanceRequest) returns (Erc721BalanceResponse); +} // A request to retrieve metadata for a specific world ID. message MetadataRequest { @@ -99,3 +104,22 @@ message SubscribeEventsRequest { message SubscribeEventsResponse { types.Event event = 1; } + +message Erc20BalanceRequest { + string account_address = 1; + string token_address = 2; +} + +message Erc20BalanceResponse { + string balance = 1; +} + +message Erc721BalanceRequest { + string account_address = 1; + string token_address = 2; + string token_id = 3; +} + +message Erc721BalanceResponse { + string balance = 1; +} \ No newline at end of file diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index db5aa6c2f2..12a1a3a3d2 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -16,6 +16,7 @@ use dojo_types::schema::Ty; use dojo_world::contracts::naming::compute_selector_from_names; use futures::Stream; use proto::world::{ + Erc20BalanceRequest, Erc20BalanceResponse, Erc721BalanceRequest, Erc721BalanceResponse, MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, RetrieveEventsRequest, RetrieveEventsResponse, SubscribeModelsRequest, SubscribeModelsResponse, UpdateEntitiesSubscriptionRequest, @@ -944,6 +945,38 @@ impl DojoWorld { ) -> Result>, Error> { self.event_manager.add_subscriber(clause.into()).await } + + async fn erc20_balance( + &self, + token_address: String, + account_address: String, + ) -> Result { + // how to handle case where the queried data is not found? + let balance: (String,) = sqlx::query_as(&format!( + "SELECT balance FROM erc20_balances WHERE token_address = '{}' AND account_address = '{}'", + token_address, account_address + )) + .fetch_one(&self.pool) + .await?; + + Ok(balance.0) + } + + async fn erc721_balance( + &self, + token_address: String, + account_address: String, + token_id: String, + ) -> Result { + let balance: (String,) = sqlx::query_as(&format!( + "SELECT balance FROM erc721_balances WHERE token_address = '{}' AND account_address = '{}' AND token_id = '{}'", + token_address, account_address, token_id + )) + .fetch_one(&self.pool) + .await?; + + Ok(balance.0) + } } fn process_event_field(data: &str) -> Result>, Error> { @@ -1164,6 +1197,39 @@ impl proto::world::world_server::World for DojoWorld { Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEventsStream)) } + + async fn erc20_balance( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let token_address = request.token_address; + let account_address = request.account_address; + + let balance = self + .erc20_balance(token_address, account_address) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(Erc20BalanceResponse { balance })) + } + + async fn erc721_balance( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let token_address = request.token_address; + let account_address = request.account_address; + let token_id = request.token_id; + + let balance = self + .erc721_balance(token_address, account_address, token_id) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(Erc721BalanceResponse { balance })) + } } pub async fn new( From d2298996f6bec05e14b5e311f3c75676a85c5954 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 13 Aug 2024 16:33:33 +0530 Subject: [PATCH 11/23] bunch of stuff --- bin/torii/src/main.rs | 2 +- crates/katana/scripts/deploy-erc20.sh | 1 + crates/torii/core/src/sql.rs | 17 +- crates/torii/graphql/src/constants.rs | 9 +- crates/torii/graphql/src/mapping.rs | 11 +- .../graphql/src/object/erc/erc20_balance.rs | 21 ++ .../graphql/src/object/erc/erc721_balance.rs | 21 ++ crates/torii/graphql/src/object/erc/mod.rs | 2 + .../torii/graphql/src/object/erc20_balance.rs | 282 ------------------ .../graphql/src/object/erc721_balance.rs | 0 .../torii/graphql/src/object/erc_balance.rs | 103 +++++++ .../torii/graphql/src/object/event_message.rs | 54 ++-- crates/torii/graphql/src/object/mod.rs | 3 +- crates/torii/graphql/src/schema.rs | 9 +- crates/torii/graphql/src/server.rs | 1 + crates/torii/grpc/src/server/mod.rs | 6 +- .../migrations/20240803102207_add_erc.sql | 27 ++ 17 files changed, 239 insertions(+), 330 deletions(-) create mode 100755 crates/katana/scripts/deploy-erc20.sh create mode 100644 crates/torii/graphql/src/object/erc/erc20_balance.rs create mode 100644 crates/torii/graphql/src/object/erc/erc721_balance.rs create mode 100644 crates/torii/graphql/src/object/erc/mod.rs delete mode 100644 crates/torii/graphql/src/object/erc20_balance.rs delete mode 100644 crates/torii/graphql/src/object/erc721_balance.rs create mode 100644 crates/torii/graphql/src/object/erc_balance.rs diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 0e1235fb8a..fee942592c 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -126,7 +126,7 @@ async fn main() -> anyhow::Result<()> { // TODO: see where to get this addresses from, cli? config? let addresses = [( - Felt::from_str("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7") + Felt::from_str("0x07b5cd6382e91444c3e8d62bf9ae17d9b0876f3a6d9c077c8ee2f7e6de458862") .unwrap(), 0, )]; diff --git a/crates/katana/scripts/deploy-erc20.sh b/crates/katana/scripts/deploy-erc20.sh new file mode 100755 index 0000000000..f207bcc86b --- /dev/null +++ b/crates/katana/scripts/deploy-erc20.sh @@ -0,0 +1 @@ +starkli deploy --account account.json --keystore signer.json --keystore-password "" 0x02a8846878b6ad1f54f6ba46f5f40e11cee755c677f130b2c4b60566c9003f1f 0x626c6f62 0x424c42 0x8 u256:10000000000 0xb3ff441a68610b30fd5e2abbf3a1548eb6ba6f3559f2862bf2dc757e5828c \ No newline at end of file diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 0769b5d67d..d8388bf7f4 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -9,7 +9,6 @@ use dojo_types::schema::{EnumOption, Member, Struct, Ty}; use dojo_world::contracts::abi::model::Layout; use dojo_world::contracts::naming::compute_selector_from_names; use dojo_world::metadata::WorldMetadata; -use num_traits::{FromPrimitive, ToPrimitive}; use sqlx::pool::PoolConnection; use sqlx::{Pool, Row, Sqlite}; use starknet::core::types::{Event, Felt, InvokeTransaction, Transaction, U256}; @@ -19,7 +18,6 @@ use super::World; use crate::model::ModelSQLReader; use crate::query_queue::{Argument, QueryQueue}; use crate::simple_broker::SimpleBroker; -use crate::sql; use crate::types::{ Entity as EntityUpdated, Event as EventEmitted, EventMessage as EventMessageUpdated, Model as ModelRegistered, @@ -1197,7 +1195,7 @@ impl Sql { // Insert transfer event to erc20_transfers table { let insert_query = - "INSERT INTO erc20_transfers (token_address, from, to, amount) VALUES (?, ?, ?, ?)"; + "INSERT INTO erc20_transfers (token_address, from_address, to_address, amount) VALUES (?, ?, ?, ?)"; self.query_queue.enqueue( insert_query, @@ -1219,7 +1217,7 @@ impl Sql { // statements. // Fetch balances for both `from` and `to` addresses, update them and write back to db let query = sqlx::query_as::<_, (String, String)>( - "SELECT address, balance FROM erc20_balances WHERE token_address = ? AND address \ + "SELECT address, balance FROM erc20_balances WHERE token_address = ? AND account_address \ IN (?, ?)", ) .bind(format!("{:#x}", token_address)) @@ -1252,9 +1250,9 @@ impl Sql { let new_to_balance = if to != Felt::ZERO { to_balance.add(amount) } else { to_balance }; let update_query = " - INSERT INTO erc20_balances (address, token_address, balance) + INSERT INTO erc20_balances (account_address, token_address, balance) VALUES (?, ?, ?) - ON CONFLICT (address, token_address) + ON CONFLICT (account_address, token_address) DO UPDATE SET balance = excluded.balance"; self.query_queue.enqueue( @@ -1289,7 +1287,8 @@ impl Sql { ) -> Result<()> { // Insert transfer event to erc721_transfers table { - let insert_query = "INSERT INTO erc721_transfers (token_address, from, to, token_id) \ + let insert_query = + "INSERT INTO erc721_transfers (token_address, from_address, to_address, token_id) \ VALUES (?, ?, ?, ?)"; self.query_queue.enqueue( @@ -1307,7 +1306,7 @@ impl Sql { { if from != Felt::ZERO { self.query_queue.enqueue( - "DELETE FROM erc721_balances WHERE address = ? AND token_address = ? AND token_id = ?", + "DELETE FROM erc721_balances WHERE account_address = ? AND token_address = ? AND token_id = ?", vec![ Argument::FieldElement(from), Argument::FieldElement(token_address), @@ -1318,7 +1317,7 @@ impl Sql { if to != Felt::ZERO { self.query_queue.enqueue( - "INSERT INTO erc721_balances (address, token_address, token_id) VALUES (?, ?, ?)", + "INSERT INTO erc721_balances (account_address, token_address, token_id) VALUES (?, ?, ?)", vec![ Argument::FieldElement(to), Argument::FieldElement(token_address), diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index 8f3ddc7c0e..9c8e00d9b1 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -9,6 +9,8 @@ pub const EVENT_MESSAGE_TABLE: &str = "event_messages"; pub const MODEL_TABLE: &str = "models"; pub const TRANSACTION_TABLE: &str = "transactions"; pub const METADATA_TABLE: &str = "metadata"; +pub const ERC20_BALANCE_TABLE: &str = "erc20_balances"; +pub const ERC721_BALANCE_TABLE: &str = "erc721_balances"; pub const ID_COLUMN: &str = "id"; pub const EVENT_ID_COLUMN: &str = "event_id"; @@ -21,6 +23,7 @@ pub const INTERNAL_ENTITY_ID_KEY: &str = "$entity_id$"; // objects namespaced to avoid conflicts with user models pub const ENTITY_TYPE_NAME: &str = "World__Entity"; pub const ERC20_BALANCE_TYPE_NAME: &str = "World__Erc20Balance"; +pub const ERC721_BALANCE_TYPE_NAME: &str = "World__Erc721Balance"; pub const EVENT_MESSAGE_TYPE_NAME: &str = "World__EventMessage"; pub const MODEL_TYPE_NAME: &str = "World__Model"; pub const EVENT_TYPE_NAME: &str = "World__Event"; @@ -33,6 +36,7 @@ pub const QUERY_TYPE_NAME: &str = "World__Query"; pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription"; pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder"; pub const MODEL_ORDER_FIELD_TYPE_NAME: &str = "World__ModelOrderField"; +pub const ERC_BALANCE_TYPE_NAME: &str = "World__ErcBalance"; // objects' single and plural names pub const ENTITY_NAMES: (&str, &str) = ("entity", "entities"); @@ -44,8 +48,9 @@ pub const CONTENT_NAMES: (&str, &str) = ("content", "contents"); pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas"); pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions"); pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); -pub const ERC20_BALANCE_NAMES: (&str, &str) = ("erc20Balance", "erc20Balances"); -pub const ERC721_BALANCE_NAMES: (&str, &str) = ("erc721Balance", "erc721Balances"); +pub const ERC_BALANCE_NAMES: (&str, &str) = ("ercBalance", ""); +pub const ERC20_BALANCE_NAMES: (&str, &str) = ("erc20Balance", ""); +pub const ERC721_BALANCE_NAMES: (&str, &str) = ("erc721Balance", ""); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 2292979c59..5d052a034a 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -4,7 +4,9 @@ use async_graphql::Name; use dojo_types::primitive::Primitive; use lazy_static::lazy_static; -use crate::constants::{CONTENT_TYPE_NAME, SOCIAL_TYPE_NAME}; +use crate::constants::{ + CONTENT_TYPE_NAME, ERC20_BALANCE_TYPE_NAME, ERC721_BALANCE_TYPE_NAME, SOCIAL_TYPE_NAME, +}; use crate::types::{GraphqlType, TypeData, TypeMapping}; lazy_static! { @@ -143,11 +145,18 @@ lazy_static! { TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())) ), ]); + pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("erc20"), TypeData::Simple(TypeRef::named_list(ERC20_BALANCE_TYPE_NAME))), + (Name::new("erc721"), TypeData::Simple(TypeRef::named_list(ERC721_BALANCE_TYPE_NAME))), + ]); + pub static ref ERC20_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); + pub static ref ERC721_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), diff --git a/crates/torii/graphql/src/object/erc/erc20_balance.rs b/crates/torii/graphql/src/object/erc/erc20_balance.rs new file mode 100644 index 0000000000..8ffa05b210 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/erc20_balance.rs @@ -0,0 +1,21 @@ +use crate::constants::{ERC20_BALANCE_NAMES, ERC20_BALANCE_TYPE_NAME}; +use crate::mapping::ERC20_BALANCE_TYPE_MAPPING; +use crate::object::BasicObject; +use crate::types::TypeMapping; + +#[derive(Debug)] +pub struct Erc20BalanceObject; + +impl BasicObject for Erc20BalanceObject { + fn name(&self) -> (&str, &str) { + ERC20_BALANCE_NAMES + } + + fn type_name(&self) -> &str { + ERC20_BALANCE_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC20_BALANCE_TYPE_MAPPING + } +} diff --git a/crates/torii/graphql/src/object/erc/erc721_balance.rs b/crates/torii/graphql/src/object/erc/erc721_balance.rs new file mode 100644 index 0000000000..5a770b58f2 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/erc721_balance.rs @@ -0,0 +1,21 @@ +use crate::constants::{ERC721_BALANCE_NAMES, ERC721_BALANCE_TYPE_NAME}; +use crate::mapping::ERC721_BALANCE_TYPE_MAPPING; +use crate::object::BasicObject; +use crate::types::TypeMapping; + +#[derive(Debug)] +pub struct Erc721BalanceObject; + +impl BasicObject for Erc721BalanceObject { + fn name(&self) -> (&str, &str) { + ERC721_BALANCE_NAMES + } + + fn type_name(&self) -> &str { + ERC721_BALANCE_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC721_BALANCE_TYPE_MAPPING + } +} diff --git a/crates/torii/graphql/src/object/erc/mod.rs b/crates/torii/graphql/src/object/erc/mod.rs new file mode 100644 index 0000000000..0fdd49b6f5 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/mod.rs @@ -0,0 +1,2 @@ +pub mod erc20_balance; +pub mod erc721_balance; diff --git a/crates/torii/graphql/src/object/erc20_balance.rs b/crates/torii/graphql/src/object/erc20_balance.rs deleted file mode 100644 index 2b67838b3e..0000000000 --- a/crates/torii/graphql/src/object/erc20_balance.rs +++ /dev/null @@ -1,282 +0,0 @@ -use async_graphql::dynamic::indexmap::IndexMap; -use async_graphql::dynamic::{ - Field, FieldFuture, FieldValue, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, -}; -use async_graphql::{Name, Value}; -use async_recursion::async_recursion; -use sqlx::pool::PoolConnection; -use sqlx::{Pool, Sqlite}; -use tokio_stream::StreamExt; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::Entity; - -use super::inputs::keys_input::keys_argument; -use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; -use crate::constants::{ - DATETIME_FORMAT, ENTITY_TABLE, ERC20_BALANCE_NAMES, ERC20_BALANCE_TYPE_NAME, EVENT_ID_COLUMN, - ID_COLUMN, -}; -use crate::mapping::ERC20_BALANCE_TYPE_MAPPING; -use crate::object::{resolve_many, resolve_one}; -use crate::query::{type_mapping_query, value_mapping_from_row}; -use crate::types::TypeData; -use crate::utils; - -#[derive(Debug)] -pub struct Erc20Balance; - -impl BasicObject for Erc20Balance { - fn name(&self) -> (&str, &str) { - ERC20_BALANCE_NAMES - } - - fn type_name(&self) -> &str { - ERC20_BALANCE_TYPE_NAME - } - - fn type_mapping(&self) -> &TypeMapping { - &ERC20_BALANCE_TYPE_MAPPING - } - - fn related_fields(&self) -> Option> { - Some(vec![]) - } -} - -impl ResolvableObject for Erc20Balance { - fn resolvers(&self) -> Vec { - let resolve_one = resolve_one( - ENTITY_TABLE, - ID_COLUMN, - self.name().0, - self.type_name(), - self.type_mapping(), - ); - - let mut resolve_many = resolve_many( - ENTITY_TABLE, - EVENT_ID_COLUMN, - self.name().1, - self.type_name(), - self.type_mapping(), - ); - resolve_many = keys_argument(resolve_many); - - vec![resolve_one, resolve_many] - } - - fn subscriptions(&self) -> Option> { - Some(vec![SubscriptionField::new( - "entityUpdated", - TypeRef::named_nn(self.type_name()), - |ctx| { - SubscriptionFieldFuture::new(async move { - let id = match ctx.args.get("id") { - Some(id) => Some(id.string()?.to_string()), - None => None, - }; - // if id is None, then subscribe to all entities - // if id is Some, then subscribe to only the entity with that id - Ok(SimpleBroker::::subscribe().filter_map(move |entity: Entity| { - if id.is_none() || id == Some(entity.id.clone()) { - Some(Ok(Value::Object(EntityObject::value_mapping(entity)))) - } else { - // id != entity.id , then don't send anything, still listening - None - } - })) - }) - }, - ) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) - } -} - -impl Erc20Balance { - pub fn value_mapping(entity: Entity) -> ValueMapping { - let keys: Vec<&str> = entity.keys.split('/').filter(|&k| !k.is_empty()).collect(); - IndexMap::from([ - (Name::new("id"), Value::from(entity.id)), - (Name::new("keys"), Value::from(keys)), - (Name::new("eventId"), Value::from(entity.event_id)), - ( - Name::new("createdAt"), - Value::from(entity.created_at.format(DATETIME_FORMAT).to_string()), - ), - ( - Name::new("updatedAt"), - Value::from(entity.updated_at.format(DATETIME_FORMAT).to_string()), - ), - ( - Name::new("executedAt"), - Value::from(entity.executed_at.format(DATETIME_FORMAT).to_string()), - ), - ]) - } -} - -fn model_union_field() -> Field { - Field::new("models", TypeRef::named_list("ModelUnion"), move |ctx| { - FieldFuture::new(async move { - match ctx.parent_value.try_to_value()? { - Value::Object(indexmap) => { - let mut conn = ctx.data::>()?.acquire().await?; - - let entity_id = utils::extract::(indexmap, "id")?; - // fetch name from the models table - // using the model id (hashed model name) - let model_ids: Vec<(String, String, String)> = sqlx::query_as( - "SELECT id, namespace, name - FROM models - WHERE id IN ( - SELECT model_id - FROM entity_model - WHERE entity_id = ? - )", - ) - .bind(&entity_id) - .fetch_all(&mut *conn) - .await?; - - let mut results: Vec> = Vec::new(); - for (id, namespace, name) in model_ids { - // the model id in the model mmeebrs table is the hashed model name (id) - let type_mapping = type_mapping_query(&mut conn, &id).await?; - - // but the table name for the model data is the unhashed model name - let data: ValueMapping = match model_data_recursive_query( - &mut conn, - vec![format!("{namespace}-{name}")], - &entity_id, - &[], - &type_mapping, - false, - ) - .await? - { - Value::Object(map) => map, - _ => unreachable!(), - }; - - results.push(FieldValue::with_type( - FieldValue::owned_any(data), - utils::type_name_from_names(&namespace, &name), - )) - } - - Ok(Some(FieldValue::list(results))) - } - _ => Err("incorrect value, requires Value::Object".into()), - } - }) - }) -} - -// TODO: flatten query -#[async_recursion] -pub async fn model_data_recursive_query( - conn: &mut PoolConnection, - path_array: Vec, - entity_id: &str, - indexes: &[i64], - type_mapping: &TypeMapping, - is_list: bool, -) -> sqlx::Result { - // For nested types, we need to remove prefix in path array - let namespace = format!("{}_", path_array[0]); - let table_name = &path_array.join("$").replace(&namespace, ""); - let mut query = format!("SELECT * FROM [{}] WHERE entity_id = '{}' ", table_name, entity_id); - for (column_idx, index) in indexes.iter().enumerate() { - query.push_str(&format!("AND idx_{} = {} ", column_idx, index)); - } - - let rows = sqlx::query(&query).fetch_all(conn.as_mut()).await?; - if rows.is_empty() { - return Ok(Value::List(vec![])); - } - - let value_mapping: Value; - let mut nested_value_mappings = Vec::new(); - - for (idx, row) in rows.iter().enumerate() { - let mut nested_value_mapping = value_mapping_from_row(row, type_mapping, true)?; - - for (field_name, type_data) in type_mapping { - if let TypeData::Nested((_, nested_mapping)) = type_data { - let mut nested_path = path_array.clone(); - nested_path.push(field_name.to_string()); - - let nested_values = model_data_recursive_query( - conn, - nested_path, - entity_id, - &if is_list { - let mut indexes = indexes.to_vec(); - indexes.push(idx as i64); - indexes - } else { - indexes.to_vec() - }, - nested_mapping, - false, - ) - .await?; - - nested_value_mapping.insert(Name::new(field_name), nested_values); - } else if let TypeData::List(inner) = type_data { - let mut nested_path = path_array.clone(); - nested_path.push(field_name.to_string()); - - let data = match model_data_recursive_query( - conn, - nested_path, - entity_id, - // this might need to be changed to support 2d+ arrays - &if is_list { - let mut indexes = indexes.to_vec(); - indexes.push(idx as i64); - indexes - } else { - indexes.to_vec() - }, - &IndexMap::from([(Name::new("data"), *inner.clone())]), - true, - ) - .await? - { - // map our list which uses a data field as a place holder - // for all elements to get the elemnt directly - Value::List(data) => data - .iter() - .map(|v| match v { - Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), - ty => unreachable!( - "Expected Value::Object for list \"data\" field, got {:?}", - ty - ), - }) - .collect(), - Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), - ty => { - unreachable!( - "Expected Value::List or Value::Object for list, got {:?}", - ty - ); - } - }; - - nested_value_mapping.insert(Name::new(field_name), data); - } - } - - nested_value_mappings.push(Value::Object(nested_value_mapping)); - } - - if is_list { - value_mapping = Value::List(nested_value_mappings); - } else { - value_mapping = nested_value_mappings.pop().unwrap(); - } - - Ok(value_mapping) -} diff --git a/crates/torii/graphql/src/object/erc721_balance.rs b/crates/torii/graphql/src/object/erc721_balance.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/torii/graphql/src/object/erc_balance.rs b/crates/torii/graphql/src/object/erc_balance.rs new file mode 100644 index 0000000000..ee8ab76fd4 --- /dev/null +++ b/crates/torii/graphql/src/object/erc_balance.rs @@ -0,0 +1,103 @@ +use crate::types::ValueMapping; +use crate::utils::extract; +use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; +use async_graphql::{Name, Value}; +use convert_case::{Case, Casing}; +use serde::Deserialize; +use sqlx::{FromRow, Pool, Sqlite, SqliteConnection}; + +use super::{BasicObject, ResolvableObject, TypeMapping}; +use crate::constants::{ERC20_BALANCE_TABLE, ERC_BALANCE_NAMES, ERC_BALANCE_TYPE_NAME}; +use crate::mapping::ERC_BALANCE_TYPE_MAPPING; + +#[derive(Debug)] +pub struct ErcBalanceObject; + +impl BasicObject for ErcBalanceObject { + fn name(&self) -> (&str, &str) { + ERC_BALANCE_NAMES + } + + fn type_name(&self) -> &str { + ERC_BALANCE_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC_BALANCE_TYPE_MAPPING + } +} + +impl ResolvableObject for ErcBalanceObject { + fn resolvers(&self) -> Vec { + let id_column = ""; + let field = Field::new(self.name().0, TypeRef::named(self.type_name()), move |ctx| { + FieldFuture::new(async move { + // read address to be queried + // query data from tables + // return as graphql object + let mut conn = ctx.data::>()?.acquire().await?; + let address: String = + extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; + + let erc20_balances = fetch_erc20_balances(&mut conn, &address).await?; + // let erc721_balances = fetch_erc721_balances(&mut conn, &address).await?; + + let result = ValueMapping::from([ + (Name::new("address"), Value::String(address)), + (Name::new("erc20_balances"), Value::List(erc20_balances)), + (Name::new("erc721_balances"), Value::List(vec![])), + ]); + + Ok(Some(Value::Object(result))) + }) + }); + vec![field] + } +} + +async fn fetch_erc721_balances( + conn: &mut SqliteConnection, + address: &str, +) -> sqlx::Result> { + todo!() +} + +// Collects data from erc20_balances table +// It doesn't contain contract metadata like name, symbol, decimals yet, they will be +// added to the value object later on +async fn fetch_erc20_balances( + conn: &mut SqliteConnection, + address: &str, +) -> sqlx::Result> { + let query = format!("SELECT * FROM {} WHERE address = ?", ERC20_BALANCE_TABLE); + let res = sqlx::query(&query).bind(address).fetch_all(conn).await?; + res.into_iter() + .map(|row| { + let erc20_balance = Erc20Balance::from_row(&row)?; + Ok(Value::Object(ValueMapping::from([ + (Name::new("address"), Value::String(erc20_balance.address)), + (Name::new("tokenAddress"), Value::String(erc20_balance.token_address)), + (Name::new("balance"), Value::String(erc20_balance.balance)), + ]))) + }) + .collect() +} + +// TODO: This would be required when subscriptions are needed +// impl ErcBalanceObject { +// pub fn value_mapping(entity: ErcBalance) -> ValueMapping { +// IndexMap::from([ +// ]) +// } +// } + +#[derive(FromRow, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Erc20Balance { + pub name: String, + pub symbol: String, + pub address: String, + pub decimals: u8, + pub token_address: String, + pub balance: String, +} diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index b1b815537c..8ad982fba5 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -65,35 +65,31 @@ impl ResolvableObject for EventMessageObject { } fn subscriptions(&self) -> Option> { - Some(vec![ - SubscriptionField::new( - "eventMessageUpdated", - TypeRef::named_nn(self.type_name()), - |ctx| { - SubscriptionFieldFuture::new(async move { - let id = match ctx.args.get("id") { - Some(id) => Some(id.string()?.to_string()), - None => None, - }; - // if id is None, then subscribe to all entities - // if id is Some, then subscribe to only the entity with that id - Ok(SimpleBroker::::subscribe().filter_map( - move |entity: EventMessage| { - if id.is_none() || id == Some(entity.id.clone()) { - Some(Ok(Value::Object(EventMessageObject::value_mapping( - entity, - )))) - } else { - // id != entity.id , then don't send anything, still listening - None - } - }, - )) - }) - }, - ) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), - ]) + Some(vec![SubscriptionField::new( + "eventMessageUpdated", + TypeRef::named_nn(self.type_name()), + |ctx| { + SubscriptionFieldFuture::new(async move { + let id = match ctx.args.get("id") { + Some(id) => Some(id.string()?.to_string()), + None => None, + }; + // if id is None, then subscribe to all entities + // if id is Some, then subscribe to only the entity with that id + Ok(SimpleBroker::::subscribe().filter_map( + move |entity: EventMessage| { + if id.is_none() || id == Some(entity.id.clone()) { + Some(Ok(Value::Object(EventMessageObject::value_mapping(entity)))) + } else { + // id != entity.id , then don't send anything, still listening + None + } + }, + )) + }) + }, + ) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) } } diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index 5c7c125458..f7f5e4c2af 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -1,6 +1,7 @@ pub mod connection; pub mod entity; -// pub mod erc20_balance; +pub mod erc; +pub mod erc_balance; pub mod event; pub mod event_message; pub mod inputs; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index ab823fe50a..c9f24d0837 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -10,7 +10,9 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; -// use crate::object::erc20_balance::Erc20Balance; +use crate::object::erc::erc20_balance::Erc20BalanceObject; +use crate::object::erc::erc721_balance::Erc721BalanceObject; +use crate::object::erc_balance::ErcBalanceObject; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -29,6 +31,7 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { let (objects, unions) = build_objects(pool).await?; let mut schema_builder = Schema::build(QUERY_TYPE_NAME, None, Some(SUBSCRIPTION_TYPE_NAME)); + //? why we need to provide QUERY_TYPE_NAME object here when its already passed to Schema? let mut query_root = Object::new(QUERY_TYPE_NAME); let mut subscription_root = Subscription::new(SUBSCRIPTION_TYPE_NAME); @@ -113,10 +116,12 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Date: Tue, 13 Aug 2024 17:27:27 +0530 Subject: [PATCH 12/23] update schema and try to debug indexing issue --- bin/torii/src/main.rs | 2 +- crates/torii/core/src/engine.rs | 1 + crates/torii/core/src/sql.rs | 42 ++++++++++--------- .../migrations/20240803102207_add_erc.sql | 6 +-- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index fee942592c..0e028c6d0f 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -126,7 +126,7 @@ async fn main() -> anyhow::Result<()> { // TODO: see where to get this addresses from, cli? config? let addresses = [( - Felt::from_str("0x07b5cd6382e91444c3e8d62bf9ae17d9b0876f3a6d9c077c8ee2f7e6de458862") + Felt::from_str("0x06a1de68506d3d5e6b5f3be79cd0072b393acf86872dab835e0013416a1491fd") .unwrap(), 0, )]; diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index add0b87db3..3ed7440823 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -483,6 +483,7 @@ impl Engine

{ || get_selector_from_name(&processor.event_key())? == event.keys[0]) && processor.validate(event) { + dbg!(processor.event_key()); if let Err(e) = processor .process( &self.world, diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index d8388bf7f4..5ee66187cb 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1217,7 +1217,7 @@ impl Sql { // statements. // Fetch balances for both `from` and `to` addresses, update them and write back to db let query = sqlx::query_as::<_, (String, String)>( - "SELECT address, balance FROM erc20_balances WHERE token_address = ? AND account_address \ + "SELECT account_address, balance FROM erc20_balances WHERE token_address = ? AND account_address \ IN (?, ?)", ) .bind(format!("{:#x}", token_address)) @@ -1234,13 +1234,13 @@ impl Sql { .iter() .find(|(address, _)| address == &format!("{:#x}", from)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("0x0")); + .unwrap_or_else(|| format!("{:#x}", crypto_bigint::U256::ZERO)); let to_balance = balances .iter() .find(|(address, _)| address == &format!("{:#x}", to)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("0x0")); + .unwrap_or_else(|| format!("{:#x}", crypto_bigint::U256::ZERO)); let from_balance = sql_string_to_u256(&from_balance); let to_balance = sql_string_to_u256(&to_balance); @@ -1255,23 +1255,27 @@ impl Sql { ON CONFLICT (account_address, token_address) DO UPDATE SET balance = excluded.balance"; - self.query_queue.enqueue( - update_query, - vec![ - Argument::FieldElement(from), - Argument::FieldElement(token_address), - Argument::String(u256_to_sql_string(&new_from_balance)), - ], - ); + if from != Felt::ZERO { + self.query_queue.enqueue( + update_query, + vec![ + Argument::FieldElement(from), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&new_from_balance)), + ], + ); + } - self.query_queue.enqueue( - update_query, - vec![ - Argument::FieldElement(to), - Argument::FieldElement(token_address), - Argument::String(u256_to_sql_string(&new_to_balance)), - ], - ); + if to != Felt::ZERO { + self.query_queue.enqueue( + update_query, + vec![ + Argument::FieldElement(to), + Argument::FieldElement(token_address), + Argument::String(u256_to_sql_string(&new_to_balance)), + ], + ); + } } self.query_queue.execute_all().await?; diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 54ab2a5205..20450aea6b 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -40,21 +40,19 @@ CREATE TABLE erc721_balances ( -- ); CREATE TABLE erc20_transfers ( - account_address TEXT NOT NULL, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, amount TEXT NOT NULL, - PRIMARY KEY (account_address, token_address) + PRIMARY KEY (from_address, to_address) ); CREATE TABLE erc721_transfers ( - account_address TEXT NOT NULL, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, token_id TEXT NOT NULL, - PRIMARY KEY (account_address, token_address, token_id) + PRIMARY KEY (from_address, to_address) ); -- these are metadata of the contracts which we would need to fetch from RPC separately From 16c100b190c062e722e7a5bc08f2298f703ae501 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 13 Aug 2024 17:43:20 +0530 Subject: [PATCH 13/23] reenable world events --- crates/torii/core/src/engine.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index 3ed7440823..8aab020e7f 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -236,8 +236,9 @@ impl Engine

{ keys: None, }; - let _ = get_all_events(&self.provider, events_filter, self.config.events_chunk_size); - // fetch_all_events_tasks.push(world_events_pages); + let world_events_pages = + get_all_events(&self.provider, events_filter, self.config.events_chunk_size); + fetch_all_events_tasks.push(world_events_pages); for token in &self.tokens { let events_filter = EventFilter { @@ -483,7 +484,6 @@ impl Engine

{ || get_selector_from_name(&processor.event_key())? == event.keys[0]) && processor.validate(event) { - dbg!(processor.event_key()); if let Err(e) = processor .process( &self.world, From 5728549c4dc798b8f9ca81da35b561d31770d197 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 13 Aug 2024 19:23:08 +0530 Subject: [PATCH 14/23] make raw data fetching work on graphql --- bin/torii/src/main.rs | 2 +- crates/torii/core/src/sql.rs | 6 +- crates/torii/graphql/src/mapping.rs | 10 +-- .../torii/graphql/src/object/erc_balance.rs | 67 +++++++++++++------ .../migrations/20240803102207_add_erc.sql | 25 ++++--- 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 0e028c6d0f..886a477bee 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -126,7 +126,7 @@ async fn main() -> anyhow::Result<()> { // TODO: see where to get this addresses from, cli? config? let addresses = [( - Felt::from_str("0x06a1de68506d3d5e6b5f3be79cd0072b393acf86872dab835e0013416a1491fd") + Felt::from_str("0x068cd056469117f9f57b216b9f5d09aabfbcaf0bad442c842eb2b141f6ec43df") .unwrap(), 0, )]; diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 5ee66187cb..b2e623ca2f 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1234,13 +1234,13 @@ impl Sql { .iter() .find(|(address, _)| address == &format!("{:#x}", from)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("{:#x}", crypto_bigint::U256::ZERO)); + .unwrap_or_else(|| format!("{:#63x}", crypto_bigint::U256::ZERO)); let to_balance = balances .iter() .find(|(address, _)| address == &format!("{:#x}", to)) .map(|(_, balance)| balance.clone()) - .unwrap_or_else(|| format!("{:#x}", crypto_bigint::U256::ZERO)); + .unwrap_or_else(|| format!("{:#64x}", crypto_bigint::U256::ZERO)); let from_balance = sql_string_to_u256(&from_balance); let to_balance = sql_string_to_u256(&to_balance); @@ -1342,7 +1342,7 @@ fn felts_sql_string(felts: &[Felt]) -> String { } fn u256_to_sql_string(u256: &U256) -> String { - format!("{:#x}", u256) + format!("{:#064x}", u256) } fn sql_string_to_u256(sql_string: &str) -> U256 { diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 5d052a034a..c7694cfd45 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -146,20 +146,20 @@ lazy_static! { ), ]); pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("erc20"), TypeData::Simple(TypeRef::named_list(ERC20_BALANCE_TYPE_NAME))), (Name::new("erc721"), TypeData::Simple(TypeRef::named_list(ERC721_BALANCE_TYPE_NAME))), ]); pub static ref ERC20_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); pub static ref ERC721_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("tokenId"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); } diff --git a/crates/torii/graphql/src/object/erc_balance.rs b/crates/torii/graphql/src/object/erc_balance.rs index ee8ab76fd4..3067aa2477 100644 --- a/crates/torii/graphql/src/object/erc_balance.rs +++ b/crates/torii/graphql/src/object/erc_balance.rs @@ -1,13 +1,15 @@ use crate::types::ValueMapping; use crate::utils::extract; -use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; +use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; use async_graphql::{Name, Value}; use convert_case::{Case, Casing}; use serde::Deserialize; use sqlx::{FromRow, Pool, Sqlite, SqliteConnection}; use super::{BasicObject, ResolvableObject, TypeMapping}; -use crate::constants::{ERC20_BALANCE_TABLE, ERC_BALANCE_NAMES, ERC_BALANCE_TYPE_NAME}; +use crate::constants::{ + ERC20_BALANCE_TABLE, ERC721_BALANCE_TABLE, ERC_BALANCE_NAMES, ERC_BALANCE_TYPE_NAME, +}; use crate::mapping::ERC_BALANCE_TYPE_MAPPING; #[derive(Debug)] @@ -29,28 +31,33 @@ impl BasicObject for ErcBalanceObject { impl ResolvableObject for ErcBalanceObject { fn resolvers(&self) -> Vec { - let id_column = ""; + let account_address = "account_address"; + let argument = InputValue::new( + account_address.to_case(Case::Camel), + TypeRef::named_nn(TypeRef::STRING), + ); + let field = Field::new(self.name().0, TypeRef::named(self.type_name()), move |ctx| { FieldFuture::new(async move { - // read address to be queried - // query data from tables - // return as graphql object let mut conn = ctx.data::>()?.acquire().await?; - let address: String = - extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; + let address: String = extract::( + ctx.args.as_index_map(), + &account_address.to_case(Case::Camel), + )?; let erc20_balances = fetch_erc20_balances(&mut conn, &address).await?; - // let erc721_balances = fetch_erc721_balances(&mut conn, &address).await?; + let erc721_balances = fetch_erc721_balances(&mut conn, &address).await?; let result = ValueMapping::from([ - (Name::new("address"), Value::String(address)), - (Name::new("erc20_balances"), Value::List(erc20_balances)), - (Name::new("erc721_balances"), Value::List(vec![])), + (Name::new("accountAddress"), Value::String(address)), + (Name::new("erc20"), Value::List(erc20_balances)), + (Name::new("erc721"), Value::List(erc721_balances)), ]); Ok(Some(Value::Object(result))) }) - }); + }) + .argument(argument); vec![field] } } @@ -59,7 +66,18 @@ async fn fetch_erc721_balances( conn: &mut SqliteConnection, address: &str, ) -> sqlx::Result> { - todo!() + let query = format!("SELECT * FROM {} WHERE account_address = ?", ERC721_BALANCE_TABLE); + let res = sqlx::query(&query).bind(address).fetch_all(conn).await?; + res.into_iter() + .map(|row| { + let erc721_balance = Erc721BalanceRaw::from_row(&row)?; + Ok(Value::Object(ValueMapping::from([ + (Name::new("accountAddress"), Value::String(erc721_balance.account_address)), + (Name::new("tokenAddress"), Value::String(erc721_balance.token_address)), + (Name::new("tokenId"), Value::String(erc721_balance.token_id)), + ]))) + }) + .collect() } // Collects data from erc20_balances table @@ -69,13 +87,13 @@ async fn fetch_erc20_balances( conn: &mut SqliteConnection, address: &str, ) -> sqlx::Result> { - let query = format!("SELECT * FROM {} WHERE address = ?", ERC20_BALANCE_TABLE); + let query = format!("SELECT * FROM {} WHERE account_address = ?", ERC20_BALANCE_TABLE); let res = sqlx::query(&query).bind(address).fetch_all(conn).await?; res.into_iter() .map(|row| { - let erc20_balance = Erc20Balance::from_row(&row)?; + let erc20_balance = Erc20BalanceRaw::from_row(&row)?; Ok(Value::Object(ValueMapping::from([ - (Name::new("address"), Value::String(erc20_balance.address)), + (Name::new("accountAddress"), Value::String(erc20_balance.account_address)), (Name::new("tokenAddress"), Value::String(erc20_balance.token_address)), (Name::new("balance"), Value::String(erc20_balance.balance)), ]))) @@ -93,11 +111,16 @@ async fn fetch_erc20_balances( #[derive(FromRow, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub struct Erc20Balance { - pub name: String, - pub symbol: String, - pub address: String, - pub decimals: u8, +pub struct Erc20BalanceRaw { + pub account_address: String, pub token_address: String, pub balance: String, } + +#[derive(FromRow, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Erc721BalanceRaw { + pub account_address: String, + pub token_address: String, + pub token_id: String, +} diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 20450aea6b..37385c0ba1 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -12,11 +12,17 @@ CREATE TABLE erc721_balances ( PRIMARY KEY (account_address, token_address, token_id) ); +-- -- query: get all balances for a given account +-- -- query(in future): get all balanves for a given token contract + -- -- one row represents a token contract on chain --- CREATE TABLE erc_contracts ( --- token_address TEXT NOT NULL PRIMARY KEY, --- -- "ERC20" or "ERC721" or "ERC1155" --- token_type TEXT NOT NULL, +-- CREATE TABLE contracts ( +-- contract_address TEXT NOT NULL PRIMARY KEY, +-- -- "ERC20" or "ERC721" or "ERC1155" or "WORLD" +-- contract_type TEXT NOT NULL, +-- ); + +-- CREATE TABLE token_id ( -- --! for ERC1155: both name and symbol are offchain (so would be null) -- name TEXT, -- symbol TEXT, @@ -24,7 +30,8 @@ CREATE TABLE erc721_balances ( -- decimals TEXT, -- --! total_supply would in erc1155 would need to be a map of token_id to balance -- total_supply TEXT --- ); + +-- ) -- CREATE TABLE erc_balances ( -- -- for ERC20, this would be (account_address:token_address:0x0) @@ -40,19 +47,19 @@ CREATE TABLE erc721_balances ( -- ); CREATE TABLE erc20_transfers ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, - amount TEXT NOT NULL, - PRIMARY KEY (from_address, to_address) + amount TEXT NOT NULL ); CREATE TABLE erc721_transfers ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, token_address TEXT NOT NULL, from_address TEXT NOT NULL, to_address TEXT NOT NULL, - token_id TEXT NOT NULL, - PRIMARY KEY (from_address, to_address) + token_id TEXT NOT NULL ); -- these are metadata of the contracts which we would need to fetch from RPC separately From 307917c55c4ae63813a408122669a5ee08c7540c Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Wed, 14 Aug 2024 01:17:50 +0530 Subject: [PATCH 15/23] update schema file (wip) --- .../migrations/20240803102207_add_erc.sql | 86 ++++++++++++------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 37385c0ba1..1e6aae4ca1 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -12,39 +12,39 @@ CREATE TABLE erc721_balances ( PRIMARY KEY (account_address, token_address, token_id) ); --- -- query: get all balances for a given account --- -- query(in future): get all balanves for a given token contract +-- query: get all balances for a given account +-- query(in future): get all balanves for a given token contract --- -- one row represents a token contract on chain --- CREATE TABLE contracts ( --- contract_address TEXT NOT NULL PRIMARY KEY, --- -- "ERC20" or "ERC721" or "ERC1155" or "WORLD" --- contract_type TEXT NOT NULL, --- ); +-- one row represents a token contract on chain +CREATE TABLE contracts ( + contract_address TEXT NOT NULL PRIMARY KEY, + -- "ERC20" or "ERC721" or "ERC1155" or "WORLD" + contract_type TEXT NOT NULL, +); --- CREATE TABLE token_id ( --- --! for ERC1155: both name and symbol are offchain (so would be null) --- name TEXT, --- symbol TEXT, --- --! Null for ERC721 and its part of metadata in ERC1155 (so needs to be fetched offchain) --- decimals TEXT, --- --! total_supply would in erc1155 would need to be a map of token_id to balance --- total_supply TEXT +CREATE TABLE token_id ( + --! for ERC1155: both name and symbol are offchain (so would be null) + name TEXT, + symbol TEXT, + --! Null for ERC721 and its part of metadata in ERC1155 (so needs to be fetched offchain) + decimals TEXT, + --! total_supply would in erc1155 would need to be a map of token_id to balance + total_supply TEXT --- ) +) --- CREATE TABLE erc_balances ( --- -- for ERC20, this would be (account_address:token_address:0x0) --- -- for ERC721 and ERC1155, this would be (account_address:token_address:token_id) --- id TEXT NOT NULL PRIMARY KEY, --- account_address TEXT NOT NULL, --- token_address TEXT NOT NULL, --- -- "ERC721" or "ERC1155" (null for "ERC20") --- token_id TEXT NOT NULL, --- balance TEXT NOT NULL, --- -- make token_address foreign key --- FOREIGN KEY (token_address) REFERENCES erc_contracts(token_address), --- ); +CREATE TABLE erc_balances ( + -- for ERC20, this would be (account_address:token_address:0x0) + -- for ERC721 and ERC1155, this would be (account_address:token_address:token_id) + id TEXT NOT NULL PRIMARY KEY, + account_address TEXT NOT NULL, + token_address TEXT NOT NULL, + balance TEXT NOT NULL, + -- "ERC721" or "ERC1155" (null for "ERC20") + FOREIGN KEY (token_id) REFERENCES token_id(token_id), + -- make token_address foreign key + FOREIGN KEY (token_address) REFERENCES erc_contracts(token_address), +); CREATE TABLE erc20_transfers ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, @@ -78,4 +78,30 @@ CREATE TABLE erc721_contracts ( name TEXT NOT NULL, symbol TEXT NOT NULL, total_supply TEXT NOT NULL -); \ No newline at end of file +); + +-- -- +-- CREATE TABLE contracts ( +-- id TEXT NOT NULL PRIMARY KEY, +-- contract_address TEXT NOT NULL, +-- contract_type TEXT NOT NULL, +-- head TEXT NOT NULL, +-- ) + +-- CREATE TABLE balances ( +-- id TEXT NOT NULL PRIMARY KEY, +-- balance TEXT NOT NULL, +-- account_address TEXT NOT NULL, +-- contract_address TEXT NOT NULL, +-- token_id TEXT, +-- FOREIGN KEY (token_id) REFERENCES tokens(id), +-- ) + +-- CREATE INDEX balances_account_address ON balances (account_address); + +-- CREATE TABLE tokens ( +-- id TEXT NOT NULL PRIMARY KEY, +-- uri TEXT NOT NULL, +-- contract_address TEXT NOT NULL, +-- FOREIGN KEY (contract_address) REFERENCES contracts(contract_address), +-- ) \ No newline at end of file From 705b55ae80028a759b01d18469abb1a8855631ea Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Wed, 14 Aug 2024 18:33:38 +0530 Subject: [PATCH 16/23] feat: take contract address from cli and config file --- Cargo.lock | 1 + bin/torii/src/main.rs | 66 +++++++++++++++---- bin/torii/torii.toml | 8 +++ crates/torii/core/Cargo.toml | 1 + crates/torii/core/src/engine.rs | 13 ++-- crates/torii/core/src/types.rs | 41 ++++++++++++ .../migrations/20240803102207_add_erc.sql | 34 ---------- 7 files changed, 113 insertions(+), 51 deletions(-) create mode 100644 bin/torii/torii.toml diff --git a/Cargo.lock b/Cargo.lock index f0c576394b..3571e57b5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14536,6 +14536,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", + "toml 0.8.15", "tracing", ] diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 886a477bee..2950d92ef1 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -11,6 +11,7 @@ //! for more info. use std::net::SocketAddr; +use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -40,7 +41,7 @@ use torii_core::processors::store_update_member::StoreUpdateMemberProcessor; use torii_core::processors::store_update_record::StoreUpdateRecordProcessor; use torii_core::simple_broker::SimpleBroker; use torii_core::sql::Sql; -use torii_core::types::Model; +use torii_core::types::{ErcContract, ErcType, Model, ToriiConfig}; use torii_server::proxy::Proxy; use tracing::{error, info}; use tracing_subscriber::{fmt, EnvFilter}; @@ -118,23 +119,36 @@ struct Args { /// Enable indexing pending blocks #[arg(long)] index_pending: bool, + + /// ERC contract addresses to index + #[arg(long, value_parser = parse_erc_contracts)] + #[arg(conflicts_with = "config")] + erc_contracts: Option>, + + /// Configuration file + #[arg(long)] + config: Option, } #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); - // TODO: see where to get this addresses from, cli? config? - let addresses = [( - Felt::from_str("0x068cd056469117f9f57b216b9f5d09aabfbcaf0bad442c842eb2b141f6ec43df") - .unwrap(), - 0, - )]; let mut start_block = args.start_block; - for address in &addresses { - if address.1 < start_block { - start_block = address.1; + let mut config = if let Some(path) = args.config { + ToriiConfig::load_from_path(&path)? + } else { + ToriiConfig::default() + }; + + if let Some(erc_contracts) = args.erc_contracts { + config.erc_contracts = erc_contracts; + } + + for address in &config.erc_contracts { + if address.start_block < start_block { + start_block = address.start_block; } } @@ -215,7 +229,11 @@ async fn main() -> anyhow::Result<()> { }, shutdown_tx.clone(), Some(block_tx), - addresses.iter().map(|(address, _)| *address).collect(), + config + .erc_contracts + .iter() + .map(|contract| (contract.contract_address, contract.clone())) + .collect(), ); let shutdown_rx = shutdown_tx.subscribe(); @@ -310,3 +328,29 @@ async fn spawn_rebuilding_graphql_server( } } } + +// Parses clap cli argument which is expected to be in the format: +// - erc_type:address:start_block +// - address:start_block (erc_type defaults to ERC20) +fn parse_erc_contracts(s: &str) -> anyhow::Result> { + let parts: Vec<&str> = s.split(',').collect(); + let mut contracts = Vec::new(); + for part in parts { + match part.split(':').collect::>().as_slice() { + [r#type, address, start_block] => { + let contract_address = Felt::from_str(address).unwrap(); + let start_block = start_block.parse::()?; + let r#type = r#type.parse::()?; + contracts.push(ErcContract { contract_address, start_block, r#type }); + } + [address, start_block] => { + let contract_address = Felt::from_str(address)?; + let start_block = start_block.parse::()?; + let r#type = ErcType::default(); + contracts.push(ErcContract { contract_address, start_block, r#type }); + } + _ => return Err(anyhow::anyhow!("Invalid ERC contract format")), + } + } + Ok(contracts) +} diff --git a/bin/torii/torii.toml b/bin/torii/torii.toml new file mode 100644 index 0000000000..702c4e8a88 --- /dev/null +++ b/bin/torii/torii.toml @@ -0,0 +1,8 @@ +# Example configuration file for Torii +# erc_contracts = [ +# { contract_address = "0x1234567890abcdef1234567890abcdef12345678", start_block = 0, type = "ERC20" }, +# { contract_address = "0xabcdef1234567890abcdef1234567890abcdef12", start_block = 1, type = "ERC721" }, +# ] +erc_contracts = [ + { type = "ERC20", contract_address = "0x07fc13cc1f43f0b0519f84df8bf13bea4d9fd5ce2d748c3baf27bf90a565f60a", start_block = 0 }, +] diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml index 8ca61922eb..4ac83736f7 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/core/Cargo.toml @@ -37,6 +37,7 @@ thiserror.workspace = true tokio = { version = "1.32.0", features = [ "sync" ], default-features = true } tokio-stream = "0.1.11" tokio-util = "0.7.7" +toml.workspace = true tracing.workspace = true [dev-dependencies] diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index 8aab020e7f..cf068bb20a 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::time::Duration; @@ -19,6 +19,7 @@ use tracing::{error, info, trace, warn}; use crate::processors::{BlockProcessor, EventProcessor, TransactionProcessor}; use crate::sql::Sql; +use crate::types::ErcContract; #[allow(missing_debug_implementations)] pub struct Processors { pub block: Vec>>, @@ -62,8 +63,8 @@ pub struct Engine { config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, - // ERC20 tokens to index - tokens: HashSet, + // ERC tokens to index + tokens: HashMap, } struct UnprocessedEvent { @@ -80,7 +81,7 @@ impl Engine

{ config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, - tokens: HashSet, + tokens: HashMap, ) -> Self { Self { world, @@ -244,7 +245,7 @@ impl Engine

{ let events_filter = EventFilter { from_block: Some(BlockId::Number(from)), to_block: Some(BlockId::Number(to)), - address: Some(*token), + address: Some(*token.0), keys: None, }; @@ -393,7 +394,7 @@ impl Engine

{ let mut world_event = false; for (event_idx, event) in events.iter().enumerate() { if event.from_address != self.world.address - && !self.tokens.contains(&event.from_address) + && !self.tokens.contains_key(&event.from_address) { continue; } diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index 00f2d47f11..0658ef98d8 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::{path::PathBuf, str::FromStr}; use chrono::{DateTime, Utc}; use dojo_types::schema::Ty; @@ -81,3 +82,43 @@ pub struct Event { pub executed_at: DateTime, pub created_at: DateTime, } + +#[derive(Default, Deserialize, Debug, Clone)] +pub struct ToriiConfig { + /// ERC contract addresses to index + pub erc_contracts: Vec, +} + +impl ToriiConfig { + pub fn load_from_path(path: &PathBuf) -> Result { + let config = std::fs::read_to_string(path)?; + let config: Self = toml::from_str(&config)?; + Ok(config) + } +} + +#[derive(Default, Deserialize, Debug, Clone)] +pub struct ErcContract { + pub contract_address: Felt, + pub start_block: u64, + pub r#type: ErcType, +} + +#[derive(Default, Deserialize, Debug, Clone)] +pub enum ErcType { + #[default] + ERC20, + ERC721, +} + +impl FromStr for ErcType { + type Err = anyhow::Error; + + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "erc20" | "Er20" | "ERC20" => Ok(ErcType::ERC20), + "erc721" | "Er721" | "ERC721" => Ok(ErcType::ERC721), + _ => Err(anyhow::anyhow!("Invalid ERC type: {}", input)), + } + } +} diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 1e6aae4ca1..173e1542fc 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -12,40 +12,6 @@ CREATE TABLE erc721_balances ( PRIMARY KEY (account_address, token_address, token_id) ); --- query: get all balances for a given account --- query(in future): get all balanves for a given token contract - --- one row represents a token contract on chain -CREATE TABLE contracts ( - contract_address TEXT NOT NULL PRIMARY KEY, - -- "ERC20" or "ERC721" or "ERC1155" or "WORLD" - contract_type TEXT NOT NULL, -); - -CREATE TABLE token_id ( - --! for ERC1155: both name and symbol are offchain (so would be null) - name TEXT, - symbol TEXT, - --! Null for ERC721 and its part of metadata in ERC1155 (so needs to be fetched offchain) - decimals TEXT, - --! total_supply would in erc1155 would need to be a map of token_id to balance - total_supply TEXT - -) - -CREATE TABLE erc_balances ( - -- for ERC20, this would be (account_address:token_address:0x0) - -- for ERC721 and ERC1155, this would be (account_address:token_address:token_id) - id TEXT NOT NULL PRIMARY KEY, - account_address TEXT NOT NULL, - token_address TEXT NOT NULL, - balance TEXT NOT NULL, - -- "ERC721" or "ERC1155" (null for "ERC20") - FOREIGN KEY (token_id) REFERENCES token_id(token_id), - -- make token_address foreign key - FOREIGN KEY (token_address) REFERENCES erc_contracts(token_address), -); - CREATE TABLE erc20_transfers ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, token_address TEXT NOT NULL, From 62a4f34aff3a7bb7647b66e279ac066bc5983a17 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 19 Aug 2024 01:35:40 +0530 Subject: [PATCH 17/23] change table schema and core implementation --- bin/torii/src/main.rs | 13 +- .../src/processors/erc20_legacy_transfer.rs | 4 +- .../core/src/processors/erc20_transfer.rs | 4 +- .../core/src/processors/erc721_transfer.rs | 4 +- crates/torii/core/src/sql.rs | 243 +++++++++++++++--- crates/torii/core/src/types.rs | 10 + .../migrations/20240803102207_add_erc.sql | 80 ++---- 7 files changed, 263 insertions(+), 95 deletions(-) diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 2950d92ef1..44e25deacc 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -194,9 +194,14 @@ async fn main() -> anyhow::Result<()> { // Get world address let world = WorldContractReader::new(args.world_address, &provider); + let erc_contracts = config + .erc_contracts + .iter() + .map(|contract| (contract.contract_address, contract.clone())) + .collect(); let class_hash = provider.get_class_hash_at(BlockId::Tag(BlockTag::Pending), args.world_address).await?; - let db = Sql::new(pool.clone(), args.world_address, class_hash).await?; + let db = Sql::new(pool.clone(), args.world_address, class_hash, &erc_contracts).await?; let processors = Processors { event: vec![ Box::new(RegisterModelProcessor), @@ -229,11 +234,7 @@ async fn main() -> anyhow::Result<()> { }, shutdown_tx.clone(), Some(block_tx), - config - .erc_contracts - .iter() - .map(|contract| (contract.contract_address, contract.clone())) - .collect(), + erc_contracts, ); let shutdown_rx = shutdown_tx.subscribe(); diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs index ed5303049a..81044c700b 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -35,7 +35,7 @@ where async fn process( &self, - _world: &WorldContractReader

, + world: &WorldContractReader

, db: &mut Sql, _block_number: u64, _block_timestamp: u64, @@ -50,7 +50,7 @@ where let value = U256Cainome::cairo_deserialize(&event.data, 2)?; let value = U256::from_words(value.low, value.high); - db.handle_erc20_transfer(token_address, from, to, value).await?; + db.handle_erc20_transfer(token_address, from, to, value, world.provider()).await?; info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "Legacy ERC20 Transfer"); Ok(()) diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index 133fb74bd7..db7aaab0d7 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -35,7 +35,7 @@ where async fn process( &self, - _world: &WorldContractReader

, + world: &WorldContractReader

, db: &mut Sql, _block_number: u64, _block_timestamp: u64, @@ -50,7 +50,7 @@ where let value = U256Cainome::cairo_deserialize(&event.data, 0)?; let value = U256::from_words(value.low, value.high); - db.handle_erc20_transfer(token_address, from, to, value).await?; + db.handle_erc20_transfer(token_address, from, to, value, world.provider()).await?; info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "ERC20 Transfer"); Ok(()) diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs index dc6f900c18..2faa09563d 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -36,7 +36,7 @@ where async fn process( &self, - _world: &WorldContractReader

, + world: &WorldContractReader

, db: &mut Sql, _block_number: u64, _block_timestamp: u64, @@ -51,7 +51,7 @@ where let token_id = U256Cainome::cairo_deserialize(&event.keys, 3)?; let token_id = U256::from_words(token_id.low, token_id.high); - db.handle_erc721_transfer(token_address, from, to, token_id).await?; + db.handle_erc721_transfer(token_address, from, to, token_id, world.provider()).await?; info!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer"); Ok(()) diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index b2e623ca2f..8d578c39ae 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1,8 +1,10 @@ +use std::collections::HashMap; use std::convert::TryInto; use std::ops::{Add, Sub}; use std::str::FromStr; use anyhow::{anyhow, Result}; +use cainome::cairo_serde::{ByteArray, CairoSerde}; use chrono::Utc; use dojo_types::primitive::Primitive; use dojo_types::schema::{EnumOption, Member, Struct, Ty}; @@ -11,7 +13,12 @@ use dojo_world::contracts::naming::compute_selector_from_names; use dojo_world::metadata::WorldMetadata; use sqlx::pool::PoolConnection; use sqlx::{Pool, Row, Sqlite}; -use starknet::core::types::{Event, Felt, InvokeTransaction, Transaction, U256}; +use starknet::core::types::BlockTag; +use starknet::core::types::{ + BlockId, Event, Felt, FunctionCall, InvokeTransaction, Transaction, U256, +}; +use starknet::core::utils::{get_selector_from_name, parse_cairo_short_string}; +use starknet::providers::Provider; use starknet_crypto::poseidon_hash_many; use super::World; @@ -19,8 +26,8 @@ use crate::model::ModelSQLReader; use crate::query_queue::{Argument, QueryQueue}; use crate::simple_broker::SimpleBroker; use crate::types::{ - Entity as EntityUpdated, Event as EventEmitted, EventMessage as EventMessageUpdated, - Model as ModelRegistered, + Entity as EntityUpdated, ErcContract, Event as EventEmitted, + EventMessage as EventMessageUpdated, Model as ModelRegistered, }; use crate::utils::{must_utc_datetime_from_timestamp, utc_dt_string_from_timestamp}; @@ -45,6 +52,7 @@ impl Sql { pool: Pool, world_address: Felt, world_class_hash: Felt, + erc_contracts: &HashMap, ) -> Result { let mut query_queue = QueryQueue::new(pool.clone()); @@ -61,6 +69,17 @@ impl Sql { ], ); + for erc_contract in erc_contracts.values() { + query_queue.enqueue( + "INSERT OR IGNORE INTO contracts (id, contract_address, contract_type) VALUES (?, ?, ?)", + vec![ + Argument::FieldElement(erc_contract.contract_address), + Argument::FieldElement(erc_contract.contract_address), + Argument::String(erc_contract.r#type.to_string()), + ], + ); + } + query_queue.execute_all().await?; Ok(Self { pool, world_address, query_queue }) @@ -1185,13 +1204,89 @@ impl Sql { Ok(()) } - pub async fn handle_erc20_transfer( + pub async fn handle_erc20_transfer( &mut self, - token_address: Felt, + contract_address: Felt, from: Felt, to: Felt, amount: U256, + provider: &P, ) -> Result<()> { + let token_exists: bool = + sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tokens WHERE id = ?)") + .bind(format!("{:#x}", contract_address)) + .fetch_one(&self.pool) + .await?; + + if !token_exists { + // Fetch token information from the chain + let name = provider + .call( + FunctionCall { + contract_address, + entry_point_selector: get_selector_from_name("name").unwrap(), + calldata: vec![], + }, + BlockId::Tag(BlockTag::Pending), + ) + .await?; + + // len = 1 => return value felt (i.e. legacy erc20 token) + // len > 1 => return value ByteArray (i.e. new erc20 token) + let name = if name.len() == 1 { + parse_cairo_short_string(&name[0]).unwrap() + } else { + ByteArray::cairo_deserialize(&name, 0) + .expect("Return value not ByteArray") + .to_string() + .expect("Return value not String") + }; + + let symbol = provider + .call( + FunctionCall { + contract_address, + entry_point_selector: get_selector_from_name("symbol").unwrap(), + calldata: vec![], + }, + BlockId::Tag(BlockTag::Pending), + ) + .await?; + let symbol = if symbol.len() == 1 { + parse_cairo_short_string(&symbol[0]).unwrap() + } else { + ByteArray::cairo_deserialize(&symbol, 0) + .expect("Return value not ByteArray") + .to_string() + .expect("Return value not String") + }; + + let decimals = provider + .call( + FunctionCall { + contract_address, + entry_point_selector: get_selector_from_name("decimals").unwrap(), + calldata: vec![], + }, + BlockId::Tag(BlockTag::Pending), + ) + .await?; + let decimals = u8::cairo_deserialize(&decimals, 0).expect("Return value not u8"); + + // Insert the token into the tokens table + self.query_queue.enqueue( + "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)", + vec![ + Argument::String(format!("{:#x}", contract_address)), + Argument::FieldElement(contract_address), + Argument::String(name), + Argument::String(symbol), + Argument::Int(decimals.into()), + ], + ); + } + + // Now proceed with the transfer handling // Insert transfer event to erc20_transfers table { let insert_query = @@ -1200,7 +1295,7 @@ impl Sql { self.query_queue.enqueue( insert_query, vec![ - Argument::FieldElement(token_address), + Argument::FieldElement(contract_address), Argument::FieldElement(from), Argument::FieldElement(to), Argument::String(u256_to_sql_string(&amount)), @@ -1217,10 +1312,10 @@ impl Sql { // statements. // Fetch balances for both `from` and `to` addresses, update them and write back to db let query = sqlx::query_as::<_, (String, String)>( - "SELECT account_address, balance FROM erc20_balances WHERE token_address = ? AND account_address \ + "SELECT account_address, balance FROM balances WHERE contract_address = ? AND account_address \ IN (?, ?)", ) - .bind(format!("{:#x}", token_address)) + .bind(format!("{:#x}", contract_address)) .bind(format!("{:#x}", from)) .bind(format!("{:#x}", to)); @@ -1250,18 +1345,20 @@ impl Sql { let new_to_balance = if to != Felt::ZERO { to_balance.add(amount) } else { to_balance }; let update_query = " - INSERT INTO erc20_balances (account_address, token_address, balance) - VALUES (?, ?, ?) - ON CONFLICT (account_address, token_address) + INSERT INTO balances (id, balance, account_address, contract_address, token_id) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (id) DO UPDATE SET balance = excluded.balance"; if from != Felt::ZERO { self.query_queue.enqueue( update_query, vec![ - Argument::FieldElement(from), - Argument::FieldElement(token_address), + Argument::String(format!("{:#x}:{:#x}", from, contract_address)), Argument::String(u256_to_sql_string(&new_from_balance)), + Argument::FieldElement(from), + Argument::FieldElement(contract_address), + Argument::String(format!("{:#x}", contract_address)), ], ); } @@ -1270,9 +1367,11 @@ impl Sql { self.query_queue.enqueue( update_query, vec![ - Argument::FieldElement(to), - Argument::FieldElement(token_address), + Argument::String(format!("{:#x}:{:#x}", to, contract_address)), Argument::String(u256_to_sql_string(&new_to_balance)), + Argument::FieldElement(to), + Argument::FieldElement(contract_address), + Argument::String(format!("{:#x}", contract_address)), ], ); } @@ -1282,13 +1381,79 @@ impl Sql { Ok(()) } - pub async fn handle_erc721_transfer( + pub async fn handle_erc721_transfer( &mut self, - token_address: Felt, + contract_address: Felt, from: Felt, to: Felt, token_id: U256, + provider: &P, ) -> Result<()> { + let balance_token_id = format!("{:#x}:{}", contract_address, u256_to_sql_string(&token_id)); + let token_exists: bool = + sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tokens WHERE id = ?)") + .bind(balance_token_id.clone()) + .fetch_one(&self.pool) + .await?; + + if !token_exists { + // Fetch token information from the chain + let name = provider + .call( + FunctionCall { + contract_address, + entry_point_selector: get_selector_from_name("name").unwrap(), + calldata: vec![], + }, + BlockId::Tag(BlockTag::Pending), + ) + .await?; + + // len = 1 => return value felt (i.e. legacy erc721 token) + // len > 1 => return value ByteArray (i.e. new erc721 token) + let name = if name.len() == 1 { + parse_cairo_short_string(&name[0]).unwrap() + } else { + ByteArray::cairo_deserialize(&name, 0) + .expect("Return value not ByteArray") + .to_string() + .expect("Return value not String") + }; + + let symbol = provider + .call( + FunctionCall { + contract_address, + entry_point_selector: get_selector_from_name("symbol").unwrap(), + calldata: vec![], + }, + BlockId::Tag(BlockTag::Pending), + ) + .await?; + let symbol = if symbol.len() == 1 { + parse_cairo_short_string(&symbol[0]).unwrap() + } else { + ByteArray::cairo_deserialize(&symbol, 0) + .expect("Return value not ByteArray") + .to_string() + .expect("Return value not String") + }; + + let decimals = 0; + + // Insert the token into the tokens table + self.query_queue.enqueue( + "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)", + vec![ + Argument::String(balance_token_id.clone()), + Argument::FieldElement(contract_address), + Argument::String(name), + Argument::String(symbol), + Argument::Int(decimals.into()), + ], + ); + } + // Insert transfer event to erc721_transfers table { let insert_query = @@ -1298,7 +1463,7 @@ impl Sql { self.query_queue.enqueue( insert_query, vec![ - Argument::FieldElement(token_address), + Argument::FieldElement(contract_address), Argument::FieldElement(from), Argument::FieldElement(to), Argument::String(u256_to_sql_string(&token_id)), @@ -1308,25 +1473,43 @@ impl Sql { // Update balances in erc721_balances table { + let update_query = " + INSERT INTO balances (id, balance, account_address, contract_address, token_id) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (account_address, contract_address, token_id) + DO UPDATE SET balance = excluded.balance"; + let balance_token_id = + format!("{:#x}:{}", contract_address, u256_to_sql_string(&token_id)); + if from != Felt::ZERO { self.query_queue.enqueue( - "DELETE FROM erc721_balances WHERE account_address = ? AND token_address = ? AND token_id = ?", - vec![ - Argument::FieldElement(from), - Argument::FieldElement(token_address), - Argument::String(u256_to_sql_string(&token_id)), - ], + update_query, + vec![ + Argument::String(format!( + "{:#x}:{:#x}:{:#x}", + from, contract_address, token_id + )), + Argument::FieldElement(from), + Argument::FieldElement(contract_address), + Argument::String(u256_to_sql_string(&U256::from(1u8))), + Argument::String(balance_token_id.clone()), + ], ); } if to != Felt::ZERO { self.query_queue.enqueue( - "INSERT INTO erc721_balances (account_address, token_address, token_id) VALUES (?, ?, ?)", - vec![ - Argument::FieldElement(to), - Argument::FieldElement(token_address), - Argument::String(u256_to_sql_string(&token_id)), - ], + update_query, + vec![ + Argument::String(format!( + "{:#x}:{:#x}:{:#x}", + to, contract_address, token_id + )), + Argument::FieldElement(to), + Argument::FieldElement(contract_address), + Argument::String(u256_to_sql_string(&U256::from(0u8))), + Argument::String(balance_token_id.clone()), + ], ); } } diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index 0658ef98d8..d01941a485 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -122,3 +122,13 @@ impl FromStr for ErcType { } } } + +impl ToString for ErcType { + fn to_string(&self) -> String { + match self { + ErcType::ERC20 => "ERC20", + ErcType::ERC721 => "ERC721", + } + .to_string() + } +} diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index 173e1542fc..b36d7b5b28 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -1,15 +1,33 @@ -CREATE TABLE erc20_balances ( - account_address TEXT NOT NULL, - token_address TEXT NOT NULL, - balance TEXT NOT NULL, - PRIMARY KEY (account_address, token_address) +CREATE TABLE contracts ( + -- contract_address + id TEXT NOT NULL PRIMARY KEY, + contract_address TEXT NOT NULL, + contract_type TEXT NOT NULL, + head TEXT ); -CREATE TABLE erc721_balances ( +CREATE TABLE balances ( + -- account_address:contract_address:token_id + id TEXT NOT NULL PRIMARY KEY, + balance TEXT NOT NULL, account_address TEXT NOT NULL, - token_address TEXT NOT NULL, + contract_address TEXT NOT NULL, + -- contract_address:token_id token_id TEXT NOT NULL, - PRIMARY KEY (account_address, token_address, token_id) + FOREIGN KEY (token_id) REFERENCES tokens(id) +); + +CREATE INDEX balances_account_address ON balances (account_address); +CREATE INDEX balances_contract_address ON balances (contract_address); + +CREATE TABLE tokens ( + -- contract_address:token_id + id TEXT NOT NULL PRIMARY KEY, + contract_address TEXT NOT NULL, + name TEXT NOT NULL, + symbol TEXT NOT NULL, + decimals INTEGER NOT NULL + -- FOREIGN KEY (contract_address) REFERENCES contracts(id) ); CREATE TABLE erc20_transfers ( @@ -26,48 +44,4 @@ CREATE TABLE erc721_transfers ( from_address TEXT NOT NULL, to_address TEXT NOT NULL, token_id TEXT NOT NULL -); - --- these are metadata of the contracts which we would need to fetch from RPC separately --- not part of events engine - -CREATE TABLE erc20_contracts ( - token_address TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - symbol TEXT NOT NULL, - decimals INTEGER NOT NULL, - total_supply TEXT NOT NULL -); - -CREATE TABLE erc721_contracts ( - token_address TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - symbol TEXT NOT NULL, - total_supply TEXT NOT NULL -); - --- -- --- CREATE TABLE contracts ( --- id TEXT NOT NULL PRIMARY KEY, --- contract_address TEXT NOT NULL, --- contract_type TEXT NOT NULL, --- head TEXT NOT NULL, --- ) - --- CREATE TABLE balances ( --- id TEXT NOT NULL PRIMARY KEY, --- balance TEXT NOT NULL, --- account_address TEXT NOT NULL, --- contract_address TEXT NOT NULL, --- token_id TEXT, --- FOREIGN KEY (token_id) REFERENCES tokens(id), --- ) - --- CREATE INDEX balances_account_address ON balances (account_address); - --- CREATE TABLE tokens ( --- id TEXT NOT NULL PRIMARY KEY, --- uri TEXT NOT NULL, --- contract_address TEXT NOT NULL, --- FOREIGN KEY (contract_address) REFERENCES contracts(contract_address), --- ) \ No newline at end of file +); \ No newline at end of file From 13f183e29cbaa302b4f20d5197faf2d576c96927 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 19 Aug 2024 01:47:11 +0530 Subject: [PATCH 18/23] remove grpc changes --- crates/torii/grpc/proto/world.proto | 26 +---------- crates/torii/grpc/src/server/mod.rs | 70 +---------------------------- 2 files changed, 3 insertions(+), 93 deletions(-) diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index e5baf4f576..8e8010fef1 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -36,14 +36,9 @@ service World { // Subscribe to events rpc SubscribeEvents (SubscribeEventsRequest) returns (stream SubscribeEventsResponse); - - // Retrieve ERC20 balance - rpc Erc20Balance (Erc20BalanceRequest) returns (Erc20BalanceResponse); - - // Retrieve ERC721 balance - rpc Erc721Balance (Erc721BalanceRequest) returns (Erc721BalanceResponse); } + // A request to retrieve metadata for a specific world ID. message MetadataRequest { @@ -104,22 +99,3 @@ message SubscribeEventsRequest { message SubscribeEventsResponse { types.Event event = 1; } - -message Erc20BalanceRequest { - string account_address = 1; - string token_address = 2; -} - -message Erc20BalanceResponse { - string balance = 1; -} - -message Erc721BalanceRequest { - string account_address = 1; - string token_address = 2; - string token_id = 3; -} - -message Erc721BalanceResponse { - string balance = 1; -} \ No newline at end of file diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index c0c6e5bc81..14c30f3f30 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -17,9 +17,8 @@ use dojo_types::schema::Ty; use dojo_world::contracts::naming::compute_selector_from_names; use futures::Stream; use proto::world::{ - Erc20BalanceResponse, Erc721BalanceResponse, MetadataRequest, MetadataResponse, - RetrieveEntitiesRequest, RetrieveEntitiesResponse, RetrieveEventsRequest, - RetrieveEventsResponse, SubscribeModelsRequest, SubscribeModelsResponse, + MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, + RetrieveEventsRequest, RetrieveEventsResponse, SubscribeModelsRequest, SubscribeModelsResponse, UpdateEntitiesSubscriptionRequest, }; use sqlx::prelude::FromRow; @@ -967,38 +966,6 @@ impl DojoWorld { ) -> Result>, Error> { self.event_manager.add_subscriber(clause.into()).await } - - async fn erc20_balance( - &self, - token_address: String, - account_address: String, - ) -> Result { - // how to handle case where the queried data is not found? - let balance: (String,) = sqlx::query_as(&format!( - "SELECT balance FROM erc20_balances WHERE token_address = '{}' AND account_address = '{}'", - token_address, account_address - )) - .fetch_one(&self.pool) - .await?; - - Ok(balance.0) - } - - async fn erc721_balance( - &self, - token_address: String, - account_address: String, - token_id: String, - ) -> Result { - let balance: (String,) = sqlx::query_as(&format!( - "SELECT balance FROM erc721_balances WHERE token_address = '{}' AND account_address = '{}' AND token_id = '{}'", - token_address, account_address, token_id - )) - .fetch_one(&self.pool) - .await?; - - Ok(balance.0) - } } fn process_event_field(data: &str) -> Result>, Error> { @@ -1207,39 +1174,6 @@ impl proto::world::world_server::World for DojoWorld { Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEventsStream)) } - - async fn erc20_balance( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - let token_address = request.token_address; - let account_address = request.account_address; - - let balance = self - .erc20_balance(token_address, account_address) - .await - .map_err(|e| Status::internal(e.to_string()))?; - - Ok(Response::new(Erc20BalanceResponse { balance })) - } - - async fn erc721_balance( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - let token_address = request.token_address; - let account_address = request.account_address; - let token_id = request.token_id; - - let balance = self - .erc721_balance(token_address, account_address, token_id) - .await - .map_err(|e| Status::internal(e.to_string()))?; - - Ok(Response::new(Erc721BalanceResponse { balance })) - } } pub async fn new( From 97defc4877cd92cb54a7f2c013d05d874cc86ae0 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 19 Aug 2024 01:47:33 +0530 Subject: [PATCH 19/23] update graphql implementation for new schema --- crates/torii/graphql/src/constants.rs | 13 +- crates/torii/graphql/src/mapping.rs | 23 ++-- .../graphql/src/object/erc/erc20_balance.rs | 21 --- .../graphql/src/object/erc/erc721_balance.rs | 21 --- .../graphql/src/object/erc/erc_balance.rs | 119 +++++++++++++++++ .../torii/graphql/src/object/erc/erc_token.rs | 21 +++ crates/torii/graphql/src/object/erc/mod.rs | 4 +- .../torii/graphql/src/object/erc_balance.rs | 126 ------------------ crates/torii/graphql/src/object/mod.rs | 1 - crates/torii/graphql/src/schema.rs | 8 +- 10 files changed, 158 insertions(+), 199 deletions(-) delete mode 100644 crates/torii/graphql/src/object/erc/erc20_balance.rs delete mode 100644 crates/torii/graphql/src/object/erc/erc721_balance.rs create mode 100644 crates/torii/graphql/src/object/erc/erc_balance.rs create mode 100644 crates/torii/graphql/src/object/erc/erc_token.rs delete mode 100644 crates/torii/graphql/src/object/erc_balance.rs diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index c4320eac9b..8b6b0b3e88 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -9,8 +9,6 @@ pub const EVENT_MESSAGE_TABLE: &str = "event_messages"; pub const MODEL_TABLE: &str = "models"; pub const TRANSACTION_TABLE: &str = "transactions"; pub const METADATA_TABLE: &str = "metadata"; -pub const ERC20_BALANCE_TABLE: &str = "erc20_balances"; -pub const ERC721_BALANCE_TABLE: &str = "erc721_balances"; pub const ID_COLUMN: &str = "id"; pub const EVENT_ID_COLUMN: &str = "event_id"; @@ -23,8 +21,6 @@ pub const INTERNAL_ENTITY_ID_KEY: &str = "$entity_id$"; // objects namespaced to avoid conflicts with user models pub const ENTITY_TYPE_NAME: &str = "World__Entity"; -pub const ERC20_BALANCE_TYPE_NAME: &str = "World__Erc20Balance"; -pub const ERC721_BALANCE_TYPE_NAME: &str = "World__Erc721Balance"; pub const EVENT_MESSAGE_TYPE_NAME: &str = "World__EventMessage"; pub const MODEL_TYPE_NAME: &str = "World__Model"; pub const EVENT_TYPE_NAME: &str = "World__Event"; @@ -37,7 +33,8 @@ pub const QUERY_TYPE_NAME: &str = "World__Query"; pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription"; pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder"; pub const MODEL_ORDER_FIELD_TYPE_NAME: &str = "World__ModelOrderField"; -pub const ERC_BALANCE_TYPE_NAME: &str = "World__ErcBalance"; +pub const ERC_BALANCE_TYPE_NAME: &str = "ERC__Balance"; +pub const ERC_TOKEN_TYPE_NAME: &str = "ERC__Token"; // objects' single and plural names pub const ENTITY_NAMES: (&str, &str) = ("entity", "entities"); @@ -49,9 +46,9 @@ pub const CONTENT_NAMES: (&str, &str) = ("content", "contents"); pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas"); pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions"); pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); -pub const ERC_BALANCE_NAMES: (&str, &str) = ("ercBalance", ""); -pub const ERC20_BALANCE_NAMES: (&str, &str) = ("erc20Balance", ""); -pub const ERC721_BALANCE_NAMES: (&str, &str) = ("erc721Balance", ""); + +pub const ERC_BALANCE_NAME: (&str, &str) = ("ercBalance", ""); +pub const ERC_TOKEN_NAME: (&str, &str) = ("ercToken", ""); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index c7694cfd45..f5007f6af3 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -4,9 +4,7 @@ use async_graphql::Name; use dojo_types::primitive::Primitive; use lazy_static::lazy_static; -use crate::constants::{ - CONTENT_TYPE_NAME, ERC20_BALANCE_TYPE_NAME, ERC721_BALANCE_TYPE_NAME, SOCIAL_TYPE_NAME, -}; +use crate::constants::{CONTENT_TYPE_NAME, ERC_TOKEN_TYPE_NAME, SOCIAL_TYPE_NAME}; use crate::types::{GraphqlType, TypeData, TypeMapping}; lazy_static! { @@ -146,20 +144,15 @@ lazy_static! { ), ]); pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("erc20"), TypeData::Simple(TypeRef::named_list(ERC20_BALANCE_TYPE_NAME))), - (Name::new("erc721"), TypeData::Simple(TypeRef::named_list(ERC721_BALANCE_TYPE_NAME))), - ]); - - pub static ref ERC20_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("type"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("token_metadata"), TypeData::Simple(TypeRef::named(ERC_TOKEN_TYPE_NAME))), ]); - pub static ref ERC721_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("accountAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("tokenAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("tokenId"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + pub static ref ERC_TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("symbol"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("decimals"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("contract_address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); } diff --git a/crates/torii/graphql/src/object/erc/erc20_balance.rs b/crates/torii/graphql/src/object/erc/erc20_balance.rs deleted file mode 100644 index 8ffa05b210..0000000000 --- a/crates/torii/graphql/src/object/erc/erc20_balance.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::constants::{ERC20_BALANCE_NAMES, ERC20_BALANCE_TYPE_NAME}; -use crate::mapping::ERC20_BALANCE_TYPE_MAPPING; -use crate::object::BasicObject; -use crate::types::TypeMapping; - -#[derive(Debug)] -pub struct Erc20BalanceObject; - -impl BasicObject for Erc20BalanceObject { - fn name(&self) -> (&str, &str) { - ERC20_BALANCE_NAMES - } - - fn type_name(&self) -> &str { - ERC20_BALANCE_TYPE_NAME - } - - fn type_mapping(&self) -> &TypeMapping { - &ERC20_BALANCE_TYPE_MAPPING - } -} diff --git a/crates/torii/graphql/src/object/erc/erc721_balance.rs b/crates/torii/graphql/src/object/erc/erc721_balance.rs deleted file mode 100644 index 5a770b58f2..0000000000 --- a/crates/torii/graphql/src/object/erc/erc721_balance.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::constants::{ERC721_BALANCE_NAMES, ERC721_BALANCE_TYPE_NAME}; -use crate::mapping::ERC721_BALANCE_TYPE_MAPPING; -use crate::object::BasicObject; -use crate::types::TypeMapping; - -#[derive(Debug)] -pub struct Erc721BalanceObject; - -impl BasicObject for Erc721BalanceObject { - fn name(&self) -> (&str, &str) { - ERC721_BALANCE_NAMES - } - - fn type_name(&self) -> &str { - ERC721_BALANCE_TYPE_NAME - } - - fn type_mapping(&self) -> &TypeMapping { - &ERC721_BALANCE_TYPE_MAPPING - } -} diff --git a/crates/torii/graphql/src/object/erc/erc_balance.rs b/crates/torii/graphql/src/object/erc/erc_balance.rs new file mode 100644 index 0000000000..3294577e24 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/erc_balance.rs @@ -0,0 +1,119 @@ +use crate::types::ValueMapping; +use crate::utils::extract; +use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; +use async_graphql::{Name, Value}; +use convert_case::{Case, Casing}; +use serde::Deserialize; +use sqlx::{FromRow, Pool, Sqlite, SqliteConnection}; +use tracing::warn; + +use crate::constants::{ERC_BALANCE_NAME, ERC_BALANCE_TYPE_NAME}; +use crate::mapping::ERC_BALANCE_TYPE_MAPPING; +use crate::object::{BasicObject, ResolvableObject}; +use crate::types::TypeMapping; + +#[derive(Debug)] +pub struct ErcBalanceObject; + +impl BasicObject for ErcBalanceObject { + fn name(&self) -> (&str, &str) { + ERC_BALANCE_NAME + } + + fn type_name(&self) -> &str { + ERC_BALANCE_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC_BALANCE_TYPE_MAPPING + } +} + +impl ResolvableObject for ErcBalanceObject { + fn resolvers(&self) -> Vec { + let account_address = "account_address"; + let argument = InputValue::new( + account_address.to_case(Case::Camel), + TypeRef::named_nn(TypeRef::STRING), + ); + + let field = Field::new(self.name().0, TypeRef::named_list(self.type_name()), move |ctx| { + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let address: String = extract::( + ctx.args.as_index_map(), + &account_address.to_case(Case::Camel), + )?; + + let erc_balances = fetch_erc_balances(&mut conn, &address).await?; + + Ok(Some(Value::List(erc_balances))) + }) + }) + .argument(argument); + vec![field] + } +} + +async fn fetch_erc_balances( + conn: &mut SqliteConnection, + address: &str, +) -> sqlx::Result> { + let query = "SELECT t.contract_address, t.name, t.symbol, t.decimals, b.balance, b.token_id, c.contract_type + FROM balances b + JOIN tokens t ON b.token_id = t.id + JOIN contracts c ON t.contract_address = c.contract_address + WHERE b.account_address = ?"; + + let rows = sqlx::query(query).bind(address).fetch_all(conn).await?; + + let mut erc_balances = Vec::new(); + + for row in rows { + let row = BalanceQueryResultRaw::from_row(&row)?; + + let balance_value = match row.contract_type.as_str() { + "ERC20" | "Erc20" | "erc20" | "ERC721" | "Erc721" | "erc721" => { + let token_metadata = Value::Object(ValueMapping::from([ + (Name::new("contract_address"), Value::String(row.contract_address.clone())), + (Name::new("name"), Value::String(row.name)), + (Name::new("symbol"), Value::String(row.symbol)), + (Name::new("decimals"), Value::String(row.decimals.to_string())), + ])); + + Value::Object(ValueMapping::from([ + (Name::new("balance"), Value::String(row.balance)), + (Name::new("type"), Value::String(row.contract_type)), + (Name::new("token_metadata"), token_metadata), + ])) + } + _ => { + warn!("Unknown contract type: {}", row.contract_type); + continue; + } + }; + + erc_balances.push(balance_value); + } + + Ok(erc_balances) +} + +// TODO: This would be required when subscriptions are needed +// impl ErcBalanceObject { +// pub fn value_mapping(entity: ErcBalance) -> ValueMapping { +// IndexMap::from([ +// ]) +// } +// } + +#[derive(FromRow, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +struct BalanceQueryResultRaw { + pub contract_address: String, + pub name: String, + pub symbol: String, + pub decimals: u8, + pub balance: String, + pub contract_type: String, +} diff --git a/crates/torii/graphql/src/object/erc/erc_token.rs b/crates/torii/graphql/src/object/erc/erc_token.rs new file mode 100644 index 0000000000..14b8de7877 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/erc_token.rs @@ -0,0 +1,21 @@ +use crate::constants::{ERC_TOKEN_NAME, ERC_TOKEN_TYPE_NAME}; +use crate::mapping::ERC_TOKEN_TYPE_MAPPING; +use crate::object::BasicObject; +use crate::types::TypeMapping; + +#[derive(Debug)] +pub struct ErcTokenObject; + +impl BasicObject for ErcTokenObject { + fn name(&self) -> (&str, &str) { + ERC_TOKEN_NAME + } + + fn type_name(&self) -> &str { + ERC_TOKEN_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC_TOKEN_TYPE_MAPPING + } +} diff --git a/crates/torii/graphql/src/object/erc/mod.rs b/crates/torii/graphql/src/object/erc/mod.rs index 0fdd49b6f5..4476bd97f5 100644 --- a/crates/torii/graphql/src/object/erc/mod.rs +++ b/crates/torii/graphql/src/object/erc/mod.rs @@ -1,2 +1,2 @@ -pub mod erc20_balance; -pub mod erc721_balance; +pub mod erc_balance; +pub mod erc_token; diff --git a/crates/torii/graphql/src/object/erc_balance.rs b/crates/torii/graphql/src/object/erc_balance.rs deleted file mode 100644 index 3067aa2477..0000000000 --- a/crates/torii/graphql/src/object/erc_balance.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::types::ValueMapping; -use crate::utils::extract; -use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; -use async_graphql::{Name, Value}; -use convert_case::{Case, Casing}; -use serde::Deserialize; -use sqlx::{FromRow, Pool, Sqlite, SqliteConnection}; - -use super::{BasicObject, ResolvableObject, TypeMapping}; -use crate::constants::{ - ERC20_BALANCE_TABLE, ERC721_BALANCE_TABLE, ERC_BALANCE_NAMES, ERC_BALANCE_TYPE_NAME, -}; -use crate::mapping::ERC_BALANCE_TYPE_MAPPING; - -#[derive(Debug)] -pub struct ErcBalanceObject; - -impl BasicObject for ErcBalanceObject { - fn name(&self) -> (&str, &str) { - ERC_BALANCE_NAMES - } - - fn type_name(&self) -> &str { - ERC_BALANCE_TYPE_NAME - } - - fn type_mapping(&self) -> &TypeMapping { - &ERC_BALANCE_TYPE_MAPPING - } -} - -impl ResolvableObject for ErcBalanceObject { - fn resolvers(&self) -> Vec { - let account_address = "account_address"; - let argument = InputValue::new( - account_address.to_case(Case::Camel), - TypeRef::named_nn(TypeRef::STRING), - ); - - let field = Field::new(self.name().0, TypeRef::named(self.type_name()), move |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let address: String = extract::( - ctx.args.as_index_map(), - &account_address.to_case(Case::Camel), - )?; - - let erc20_balances = fetch_erc20_balances(&mut conn, &address).await?; - let erc721_balances = fetch_erc721_balances(&mut conn, &address).await?; - - let result = ValueMapping::from([ - (Name::new("accountAddress"), Value::String(address)), - (Name::new("erc20"), Value::List(erc20_balances)), - (Name::new("erc721"), Value::List(erc721_balances)), - ]); - - Ok(Some(Value::Object(result))) - }) - }) - .argument(argument); - vec![field] - } -} - -async fn fetch_erc721_balances( - conn: &mut SqliteConnection, - address: &str, -) -> sqlx::Result> { - let query = format!("SELECT * FROM {} WHERE account_address = ?", ERC721_BALANCE_TABLE); - let res = sqlx::query(&query).bind(address).fetch_all(conn).await?; - res.into_iter() - .map(|row| { - let erc721_balance = Erc721BalanceRaw::from_row(&row)?; - Ok(Value::Object(ValueMapping::from([ - (Name::new("accountAddress"), Value::String(erc721_balance.account_address)), - (Name::new("tokenAddress"), Value::String(erc721_balance.token_address)), - (Name::new("tokenId"), Value::String(erc721_balance.token_id)), - ]))) - }) - .collect() -} - -// Collects data from erc20_balances table -// It doesn't contain contract metadata like name, symbol, decimals yet, they will be -// added to the value object later on -async fn fetch_erc20_balances( - conn: &mut SqliteConnection, - address: &str, -) -> sqlx::Result> { - let query = format!("SELECT * FROM {} WHERE account_address = ?", ERC20_BALANCE_TABLE); - let res = sqlx::query(&query).bind(address).fetch_all(conn).await?; - res.into_iter() - .map(|row| { - let erc20_balance = Erc20BalanceRaw::from_row(&row)?; - Ok(Value::Object(ValueMapping::from([ - (Name::new("accountAddress"), Value::String(erc20_balance.account_address)), - (Name::new("tokenAddress"), Value::String(erc20_balance.token_address)), - (Name::new("balance"), Value::String(erc20_balance.balance)), - ]))) - }) - .collect() -} - -// TODO: This would be required when subscriptions are needed -// impl ErcBalanceObject { -// pub fn value_mapping(entity: ErcBalance) -> ValueMapping { -// IndexMap::from([ -// ]) -// } -// } - -#[derive(FromRow, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Erc20BalanceRaw { - pub account_address: String, - pub token_address: String, - pub balance: String, -} - -#[derive(FromRow, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Erc721BalanceRaw { - pub account_address: String, - pub token_address: String, - pub token_id: String, -} diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index f7f5e4c2af..8997cdabe3 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -1,7 +1,6 @@ pub mod connection; pub mod entity; pub mod erc; -pub mod erc_balance; pub mod event; pub mod event_message; pub mod inputs; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index c9f24d0837..baaa823c56 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -10,9 +10,8 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; -use crate::object::erc::erc20_balance::Erc20BalanceObject; -use crate::object::erc::erc721_balance::Erc721BalanceObject; -use crate::object::erc_balance::ErcBalanceObject; +use crate::object::erc::erc_balance::ErcBalanceObject; +use crate::object::erc::erc_token::ErcTokenObject; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -120,8 +119,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Date: Mon, 19 Aug 2024 15:39:02 +0530 Subject: [PATCH 20/23] add ability to query erc transfer in graphql --- .../src/processors/erc20_legacy_transfer.rs | 5 +- .../core/src/processors/erc20_transfer.rs | 5 +- .../core/src/processors/erc721_transfer.rs | 12 +- crates/torii/core/src/sql.rs | 49 ++--- crates/torii/graphql/src/constants.rs | 2 + crates/torii/graphql/src/mapping.rs | 11 ++ .../graphql/src/object/erc/erc_balance.rs | 24 ++- .../graphql/src/object/erc/erc_transfer.rs | 179 ++++++++++++++++++ crates/torii/graphql/src/object/erc/mod.rs | 1 + crates/torii/graphql/src/schema.rs | 2 + .../migrations/20240803102207_add_erc.sql | 18 +- 11 files changed, 266 insertions(+), 42 deletions(-) create mode 100644 crates/torii/graphql/src/object/erc/erc_transfer.rs diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs index 81044c700b..bce9c0524c 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -38,7 +38,7 @@ where world: &WorldContractReader

, db: &mut Sql, _block_number: u64, - _block_timestamp: u64, + block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, @@ -50,7 +50,8 @@ where let value = U256Cainome::cairo_deserialize(&event.data, 2)?; let value = U256::from_words(value.low, value.high); - db.handle_erc20_transfer(token_address, from, to, value, world.provider()).await?; + db.handle_erc20_transfer(token_address, from, to, value, world.provider(), block_timestamp) + .await?; info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "Legacy ERC20 Transfer"); Ok(()) diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index db7aaab0d7..0111e0c118 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -38,7 +38,7 @@ where world: &WorldContractReader

, db: &mut Sql, _block_number: u64, - _block_timestamp: u64, + block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, @@ -50,7 +50,8 @@ where let value = U256Cainome::cairo_deserialize(&event.data, 0)?; let value = U256::from_words(value.low, value.high); - db.handle_erc20_transfer(token_address, from, to, value, world.provider()).await?; + db.handle_erc20_transfer(token_address, from, to, value, world.provider(), block_timestamp) + .await?; info!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "ERC20 Transfer"); Ok(()) diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs index 2faa09563d..91573b3994 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -39,7 +39,7 @@ where world: &WorldContractReader

, db: &mut Sql, _block_number: u64, - _block_timestamp: u64, + block_timestamp: u64, _transaction_receipt: &TransactionReceiptWithBlockInfo, _event_id: &str, event: &Event, @@ -51,7 +51,15 @@ where let token_id = U256Cainome::cairo_deserialize(&event.keys, 3)?; let token_id = U256::from_words(token_id.low, token_id.high); - db.handle_erc721_transfer(token_address, from, to, token_id, world.provider()).await?; + db.handle_erc721_transfer( + token_address, + from, + to, + token_id, + world.provider(), + block_timestamp, + ) + .await?; info!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer"); Ok(()) diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index f143b5e266..26349a77c7 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1259,10 +1259,14 @@ impl Sql { to: Felt, amount: U256, provider: &P, + block_timestamp: u64, ) -> Result<()> { + // unique token identifier in DB + let token_id = format!("{:#x}", contract_address); + let token_exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tokens WHERE id = ?)") - .bind(format!("{:#x}", contract_address)) + .bind(token_id.clone()) .fetch_one(&self.pool) .await?; @@ -1338,7 +1342,8 @@ impl Sql { // Insert transfer event to erc20_transfers table { let insert_query = - "INSERT INTO erc20_transfers (token_address, from_address, to_address, amount) VALUES (?, ?, ?, ?)"; + "INSERT INTO erc_transfers (contract_address, from_address, to_address, amount, token_id, \ + executed_at) VALUES (?, ?, ?, ?, ?, ?)"; self.query_queue.enqueue( insert_query, @@ -1347,6 +1352,8 @@ impl Sql { Argument::FieldElement(from), Argument::FieldElement(to), Argument::String(u256_to_sql_string(&amount)), + Argument::String(token_id.clone()), + Argument::String(utc_dt_string_from_timestamp(block_timestamp)), ], ); } @@ -1406,7 +1413,7 @@ impl Sql { Argument::String(u256_to_sql_string(&new_from_balance)), Argument::FieldElement(from), Argument::FieldElement(contract_address), - Argument::String(format!("{:#x}", contract_address)), + Argument::String(token_id.clone()), ], ); } @@ -1419,7 +1426,7 @@ impl Sql { Argument::String(u256_to_sql_string(&new_to_balance)), Argument::FieldElement(to), Argument::FieldElement(contract_address), - Argument::String(format!("{:#x}", contract_address)), + Argument::String(token_id.clone()), ], ); } @@ -1436,11 +1443,12 @@ impl Sql { to: Felt, token_id: U256, provider: &P, + block_timestamp: u64, ) -> Result<()> { - let balance_token_id = format!("{:#x}:{}", contract_address, u256_to_sql_string(&token_id)); + let token_id = format!("{:#x}:{}", contract_address, u256_to_sql_string(&token_id)); let token_exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tokens WHERE id = ?)") - .bind(balance_token_id.clone()) + .bind(token_id.clone()) .fetch_one(&self.pool) .await?; @@ -1493,7 +1501,7 @@ impl Sql { self.query_queue.enqueue( "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)", vec![ - Argument::String(balance_token_id.clone()), + Argument::String(token_id.clone()), Argument::FieldElement(contract_address), Argument::String(name), Argument::String(symbol), @@ -1505,8 +1513,8 @@ impl Sql { // Insert transfer event to erc721_transfers table { let insert_query = - "INSERT INTO erc721_transfers (token_address, from_address, to_address, token_id) \ - VALUES (?, ?, ?, ?)"; + "INSERT INTO erc721_transfers (contract_address, from_address, to_address, token_id, \ + executed_at) VALUES (?, ?, ?, ?, ?)"; self.query_queue.enqueue( insert_query, @@ -1514,7 +1522,8 @@ impl Sql { Argument::FieldElement(contract_address), Argument::FieldElement(from), Argument::FieldElement(to), - Argument::String(u256_to_sql_string(&token_id)), + Argument::String(token_id.clone()), + Argument::String(utc_dt_string_from_timestamp(block_timestamp)), ], ); } @@ -1526,21 +1535,16 @@ impl Sql { VALUES (?, ?, ?, ?, ?) ON CONFLICT (account_address, contract_address, token_id) DO UPDATE SET balance = excluded.balance"; - let balance_token_id = - format!("{:#x}:{}", contract_address, u256_to_sql_string(&token_id)); if from != Felt::ZERO { self.query_queue.enqueue( update_query, vec![ - Argument::String(format!( - "{:#x}:{:#x}:{:#x}", - from, contract_address, token_id - )), + Argument::String(format!("{:#x}:{}", from, &token_id)), Argument::FieldElement(from), Argument::FieldElement(contract_address), - Argument::String(u256_to_sql_string(&U256::from(1u8))), - Argument::String(balance_token_id.clone()), + Argument::String(u256_to_sql_string(&U256::from(0u8))), + Argument::String(token_id.clone()), ], ); } @@ -1549,14 +1553,11 @@ impl Sql { self.query_queue.enqueue( update_query, vec![ - Argument::String(format!( - "{:#x}:{:#x}:{:#x}", - to, contract_address, token_id - )), + Argument::String(format!("{:#x}:{}", to, &token_id)), Argument::FieldElement(to), Argument::FieldElement(contract_address), - Argument::String(u256_to_sql_string(&U256::from(0u8))), - Argument::String(balance_token_id.clone()), + Argument::String(u256_to_sql_string(&U256::from(1u8))), + Argument::String(token_id.clone()), ], ); } diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index 8b6b0b3e88..2d851f07b1 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -34,6 +34,7 @@ pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription"; pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder"; pub const MODEL_ORDER_FIELD_TYPE_NAME: &str = "World__ModelOrderField"; pub const ERC_BALANCE_TYPE_NAME: &str = "ERC__Balance"; +pub const ERC_TRANSFER_TYPE_NAME: &str = "ERC__Transfer"; pub const ERC_TOKEN_TYPE_NAME: &str = "ERC__Token"; // objects' single and plural names @@ -49,6 +50,7 @@ pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); pub const ERC_BALANCE_NAME: (&str, &str) = ("ercBalance", ""); pub const ERC_TOKEN_NAME: (&str, &str) = ("ercToken", ""); +pub const ERC_TRANSFER_NAME: (&str, &str) = ("ercTransfer", ""); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index f5007f6af3..d9b9764e22 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -143,15 +143,26 @@ lazy_static! { TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())) ), ]); + pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("type"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("token_metadata"), TypeData::Simple(TypeRef::named(ERC_TOKEN_TYPE_NAME))), ]); + pub static ref ERC_TRANSFER_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("from"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("to"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("amount"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("type"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("executed_at"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("token_metadata"), TypeData::Simple(TypeRef::named(ERC_TOKEN_TYPE_NAME))), + ]); + pub static ref ERC_TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("symbol"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("token_id"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("decimals"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("contract_address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); diff --git a/crates/torii/graphql/src/object/erc/erc_balance.rs b/crates/torii/graphql/src/object/erc/erc_balance.rs index 3294577e24..923dd12364 100644 --- a/crates/torii/graphql/src/object/erc/erc_balance.rs +++ b/crates/torii/graphql/src/object/erc/erc_balance.rs @@ -73,11 +73,32 @@ async fn fetch_erc_balances( let row = BalanceQueryResultRaw::from_row(&row)?; let balance_value = match row.contract_type.as_str() { - "ERC20" | "Erc20" | "erc20" | "ERC721" | "Erc721" | "erc721" => { + "ERC20" | "Erc20" | "erc20" => { + let token_metadata = Value::Object(ValueMapping::from([ + (Name::new("name"), Value::String(row.name)), + (Name::new("symbol"), Value::String(row.symbol)), + // for erc20 there is no token_id + (Name::new("token_id"), Value::Null), + (Name::new("decimals"), Value::String(row.decimals.to_string())), + (Name::new("contract_address"), Value::String(row.contract_address.clone())), + ])); + + Value::Object(ValueMapping::from([ + (Name::new("balance"), Value::String(row.balance)), + (Name::new("type"), Value::String(row.contract_type)), + (Name::new("token_metadata"), token_metadata), + ])) + } + "ERC721" | "Erc721" | "erc721" => { + // contract_address:token_id + let token_id = row.token_id.split(':').collect::>(); + assert!(token_id.len() == 2); + let token_metadata = Value::Object(ValueMapping::from([ (Name::new("contract_address"), Value::String(row.contract_address.clone())), (Name::new("name"), Value::String(row.name)), (Name::new("symbol"), Value::String(row.symbol)), + (Name::new("token_id"), Value::String(row.token_id)), (Name::new("decimals"), Value::String(row.decimals.to_string())), ])); @@ -114,6 +135,7 @@ struct BalanceQueryResultRaw { pub name: String, pub symbol: String, pub decimals: u8, + pub token_id: String, pub balance: String, pub contract_type: String, } diff --git a/crates/torii/graphql/src/object/erc/erc_transfer.rs b/crates/torii/graphql/src/object/erc/erc_transfer.rs new file mode 100644 index 0000000000..10ff0ba507 --- /dev/null +++ b/crates/torii/graphql/src/object/erc/erc_transfer.rs @@ -0,0 +1,179 @@ +use crate::types::ValueMapping; +use crate::utils::extract; +use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; +use async_graphql::{Name, Value}; +use convert_case::{Case, Casing}; +use serde::Deserialize; +use sqlx::{FromRow, Pool, Sqlite, SqliteConnection}; +use tracing::warn; + +use crate::constants::{ERC_TRANSFER_NAME, ERC_TRANSFER_TYPE_NAME}; +use crate::mapping::ERC_TRANSFER_TYPE_MAPPING; +use crate::object::{BasicObject, ResolvableObject}; +use crate::types::TypeMapping; + +#[derive(Debug)] +pub struct ErcTransferObject; + +impl BasicObject for ErcTransferObject { + fn name(&self) -> (&str, &str) { + ERC_TRANSFER_NAME + } + + fn type_name(&self) -> &str { + ERC_TRANSFER_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC_TRANSFER_TYPE_MAPPING + } +} + +impl ResolvableObject for ErcTransferObject { + fn resolvers(&self) -> Vec { + let account_address = "account_address"; + let limit = "limit"; + let arg_addr = InputValue::new( + account_address.to_case(Case::Camel), + TypeRef::named_nn(TypeRef::STRING), + ); + let arg_limit = + InputValue::new(limit.to_case(Case::Camel), TypeRef::named_nn(TypeRef::INT)); + + let field = Field::new(self.name().0, TypeRef::named_list(self.type_name()), move |ctx| { + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let address: String = extract::( + ctx.args.as_index_map(), + &account_address.to_case(Case::Camel), + )?; + let limit = extract::(ctx.args.as_index_map(), &limit.to_case(Case::Camel))?; + let limit: u32 = limit.try_into()?; + + let erc_transfers = fetch_erc_transfers(&mut conn, &address, limit).await?; + + Ok(Some(Value::List(erc_transfers))) + }) + }) + .argument(arg_addr) + .argument(arg_limit); + vec![field] + } +} + +async fn fetch_erc_transfers( + conn: &mut SqliteConnection, + address: &str, + limit: u32, +) -> sqlx::Result> { + let query = format!( + r#" +SELECT + 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 +FROM + erc_transfers et +JOIN + tokens t ON et.token_id = t.id +JOIN + contracts c ON t.contract_address = c.contract_address +WHERE + et.from_address = ? OR et.to_address = ? +ORDER BY + et.executed_at DESC +LIMIT {}; +"#, + limit + ); + + let rows = sqlx::query(&query).bind(address).bind(address).fetch_all(conn).await?; + + let mut erc_balances = Vec::new(); + + for row in rows { + let row = TransferQueryResultRaw::from_row(&row)?; + + let transfer_value = match row.contract_type.as_str() { + "ERC20" | "Erc20" | "erc20" => { + let token_metadata = Value::Object(ValueMapping::from([ + (Name::new("name"), Value::String(row.name)), + (Name::new("symbol"), Value::String(row.symbol)), + // for erc20 there is no token_id + (Name::new("token_id"), Value::Null), + (Name::new("decimals"), Value::String(row.decimals.to_string())), + (Name::new("contract_address"), Value::String(row.contract_address.clone())), + ])); + + Value::Object(ValueMapping::from([ + (Name::new("from"), Value::String(row.from_address)), + (Name::new("to"), Value::String(row.to_address)), + (Name::new("amount"), Value::String(row.amount)), + (Name::new("type"), Value::String(row.contract_type)), + (Name::new("executed_at"), Value::String(row.executed_at)), + (Name::new("token_metadata"), token_metadata), + ])) + } + "ERC721" | "Erc721" | "erc721" => { + // contract_address:token_id + let token_id = row.token_id.split(':').collect::>(); + assert!(token_id.len() == 2); + + let token_metadata = Value::Object(ValueMapping::from([ + (Name::new("name"), Value::String(row.name)), + (Name::new("symbol"), Value::String(row.symbol)), + (Name::new("token_id"), Value::String(token_id[1].to_string())), + (Name::new("decimals"), Value::String(row.decimals.to_string())), + (Name::new("contract_address"), Value::String(row.contract_address.clone())), + ])); + + Value::Object(ValueMapping::from([ + (Name::new("from"), Value::String(row.from_address)), + (Name::new("to"), Value::String(row.to_address)), + (Name::new("amount"), Value::String(row.amount)), + (Name::new("type"), Value::String(row.contract_type)), + (Name::new("executed_at"), Value::String(row.executed_at)), + (Name::new("token_metadata"), token_metadata), + ])) + } + _ => { + warn!("Unknown contract type: {}", row.contract_type); + continue; + } + }; + + erc_balances.push(transfer_value); + } + + Ok(erc_balances) +} + +// TODO: This would be required when subscriptions are needed +// impl ErcTransferObject { +// pub fn value_mapping(entity: ErcBalance) -> ValueMapping { +// IndexMap::from([ +// ]) +// } +// } + +#[derive(FromRow, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +struct TransferQueryResultRaw { + pub contract_address: String, + pub from_address: String, + pub to_address: String, + pub token_id: String, + pub amount: String, + pub executed_at: String, + pub name: String, + pub symbol: String, + pub decimals: u8, + pub contract_type: String, +} diff --git a/crates/torii/graphql/src/object/erc/mod.rs b/crates/torii/graphql/src/object/erc/mod.rs index 4476bd97f5..eac2c5510b 100644 --- a/crates/torii/graphql/src/object/erc/mod.rs +++ b/crates/torii/graphql/src/object/erc/mod.rs @@ -1,2 +1,3 @@ pub mod erc_balance; pub mod erc_token; +pub mod erc_transfer; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index baaa823c56..5f70c49908 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -12,6 +12,7 @@ use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; use crate::object::erc::erc_balance::ErcBalanceObject; use crate::object::erc::erc_token::ErcTokenObject; +use crate::object::erc::erc_transfer::ErcTransferObject; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -116,6 +117,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Date: Mon, 19 Aug 2024 15:39:48 +0530 Subject: [PATCH 21/23] fix formatting --- crates/torii/core/src/sql.rs | 33 ++++++------ crates/torii/core/src/types.rs | 3 +- .../graphql/src/object/erc/erc_balance.rs | 8 +-- .../graphql/src/object/erc/erc_transfer.rs | 5 +- .../torii/graphql/src/object/event_message.rs | 54 ++++++++++--------- crates/torii/graphql/src/server.rs | 1 - 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 26349a77c7..38608c2069 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -13,9 +13,8 @@ use dojo_world::contracts::naming::{compute_selector_from_names, compute_selecto use dojo_world::metadata::WorldMetadata; use sqlx::pool::PoolConnection; use sqlx::{Pool, Row, Sqlite}; -use starknet::core::types::BlockTag; use starknet::core::types::{ - BlockId, Event, Felt, FunctionCall, InvokeTransaction, Transaction, U256, + BlockId, BlockTag, Event, Felt, FunctionCall, InvokeTransaction, Transaction, U256, }; use starknet::core::utils::{get_selector_from_name, parse_cairo_short_string}; use starknet::providers::Provider; @@ -66,7 +65,8 @@ impl Sql { for erc_contract in erc_contracts.values() { query_queue.enqueue( - "INSERT OR IGNORE INTO contracts (id, contract_address, contract_type) VALUES (?, ?, ?)", + "INSERT OR IGNORE INTO contracts (id, contract_address, contract_type) VALUES (?, \ + ?, ?)", vec![ Argument::FieldElement(erc_contract.contract_address), Argument::FieldElement(erc_contract.contract_address), @@ -778,11 +778,7 @@ impl Sql { Ty::Enum(e) => { if e.options.iter().all( |o| { - if let Ty::Tuple(t) = &o.ty { - t.is_empty() - } else { - false - } + if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false } }, ) { return; @@ -1327,7 +1323,8 @@ impl Sql { // Insert the token into the tokens table self.query_queue.enqueue( - "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)", + "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, \ + ?, ?, ?)", vec![ Argument::String(format!("{:#x}", contract_address)), Argument::FieldElement(contract_address), @@ -1341,9 +1338,9 @@ impl Sql { // Now proceed with the transfer handling // Insert transfer event to erc20_transfers table { - let insert_query = - "INSERT INTO erc_transfers (contract_address, from_address, to_address, amount, token_id, \ - executed_at) VALUES (?, ?, ?, ?, ?, ?)"; + let insert_query = "INSERT INTO erc_transfers (contract_address, from_address, \ + to_address, amount, token_id, executed_at) VALUES (?, ?, ?, ?, ?, \ + ?)"; self.query_queue.enqueue( insert_query, @@ -1367,8 +1364,8 @@ impl Sql { // statements. // Fetch balances for both `from` and `to` addresses, update them and write back to db let query = sqlx::query_as::<_, (String, String)>( - "SELECT account_address, balance FROM balances WHERE contract_address = ? AND account_address \ - IN (?, ?)", + "SELECT account_address, balance FROM balances WHERE contract_address = ? AND \ + account_address IN (?, ?)", ) .bind(format!("{:#x}", contract_address)) .bind(format!("{:#x}", from)) @@ -1499,7 +1496,8 @@ impl Sql { // Insert the token into the tokens table self.query_queue.enqueue( - "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)", + "INSERT INTO tokens (id, contract_address, name, symbol, decimals) VALUES (?, ?, \ + ?, ?, ?)", vec![ Argument::String(token_id.clone()), Argument::FieldElement(contract_address), @@ -1512,9 +1510,8 @@ impl Sql { // Insert transfer event to erc721_transfers table { - let insert_query = - "INSERT INTO erc721_transfers (contract_address, from_address, to_address, token_id, \ - executed_at) VALUES (?, ?, ?, ?, ?)"; + let insert_query = "INSERT INTO erc721_transfers (contract_address, from_address, \ + to_address, token_id, executed_at) VALUES (?, ?, ?, ?, ?)"; self.query_queue.enqueue( insert_query, diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index 089a3bba75..c8fa802854 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -1,5 +1,6 @@ use core::fmt; -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; +use std::str::FromStr; use chrono::{DateTime, Utc}; use dojo_types::schema::Ty; diff --git a/crates/torii/graphql/src/object/erc/erc_balance.rs b/crates/torii/graphql/src/object/erc/erc_balance.rs index 923dd12364..de99ad496f 100644 --- a/crates/torii/graphql/src/object/erc/erc_balance.rs +++ b/crates/torii/graphql/src/object/erc/erc_balance.rs @@ -1,5 +1,3 @@ -use crate::types::ValueMapping; -use crate::utils::extract; use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; use async_graphql::{Name, Value}; use convert_case::{Case, Casing}; @@ -10,7 +8,8 @@ use tracing::warn; use crate::constants::{ERC_BALANCE_NAME, ERC_BALANCE_TYPE_NAME}; use crate::mapping::ERC_BALANCE_TYPE_MAPPING; use crate::object::{BasicObject, ResolvableObject}; -use crate::types::TypeMapping; +use crate::types::{TypeMapping, ValueMapping}; +use crate::utils::extract; #[derive(Debug)] pub struct ErcBalanceObject; @@ -59,7 +58,8 @@ async fn fetch_erc_balances( conn: &mut SqliteConnection, address: &str, ) -> sqlx::Result> { - let query = "SELECT t.contract_address, t.name, t.symbol, t.decimals, b.balance, b.token_id, c.contract_type + let query = "SELECT t.contract_address, t.name, t.symbol, t.decimals, b.balance, b.token_id, \ + c.contract_type FROM balances b JOIN tokens t ON b.token_id = t.id JOIN contracts c ON t.contract_address = c.contract_address diff --git a/crates/torii/graphql/src/object/erc/erc_transfer.rs b/crates/torii/graphql/src/object/erc/erc_transfer.rs index 10ff0ba507..8d406007cd 100644 --- a/crates/torii/graphql/src/object/erc/erc_transfer.rs +++ b/crates/torii/graphql/src/object/erc/erc_transfer.rs @@ -1,5 +1,3 @@ -use crate::types::ValueMapping; -use crate::utils::extract; use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; use async_graphql::{Name, Value}; use convert_case::{Case, Casing}; @@ -10,7 +8,8 @@ use tracing::warn; use crate::constants::{ERC_TRANSFER_NAME, ERC_TRANSFER_TYPE_NAME}; use crate::mapping::ERC_TRANSFER_TYPE_MAPPING; use crate::object::{BasicObject, ResolvableObject}; -use crate::types::TypeMapping; +use crate::types::{TypeMapping, ValueMapping}; +use crate::utils::extract; #[derive(Debug)] pub struct ErcTransferObject; diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 29734a0b7e..0e2cec609e 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -64,31 +64,35 @@ impl ResolvableObject for EventMessageObject { } fn subscriptions(&self) -> Option> { - Some(vec![SubscriptionField::new( - "eventMessageUpdated", - TypeRef::named_nn(self.type_name()), - |ctx| { - SubscriptionFieldFuture::new(async move { - let id = match ctx.args.get("id") { - Some(id) => Some(id.string()?.to_string()), - None => None, - }; - // if id is None, then subscribe to all entities - // if id is Some, then subscribe to only the entity with that id - Ok(SimpleBroker::::subscribe().filter_map( - move |entity: EventMessage| { - if id.is_none() || id == Some(entity.id.clone()) { - Some(Ok(Value::Object(EventMessageObject::value_mapping(entity)))) - } else { - // id != entity.id , then don't send anything, still listening - None - } - }, - )) - }) - }, - ) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) + Some(vec![ + SubscriptionField::new( + "eventMessageUpdated", + TypeRef::named_nn(self.type_name()), + |ctx| { + SubscriptionFieldFuture::new(async move { + let id = match ctx.args.get("id") { + Some(id) => Some(id.string()?.to_string()), + None => None, + }; + // if id is None, then subscribe to all entities + // if id is Some, then subscribe to only the entity with that id + Ok(SimpleBroker::::subscribe().filter_map( + move |entity: EventMessage| { + if id.is_none() || id == Some(entity.id.clone()) { + Some(Ok(Value::Object(EventMessageObject::value_mapping( + entity, + )))) + } else { + // id != entity.id , then don't send anything, still listening + None + } + }, + )) + }) + }, + ) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), + ]) } } diff --git a/crates/torii/graphql/src/server.rs b/crates/torii/graphql/src/server.rs index 7dcd492e1c..4ac5f3dd78 100644 --- a/crates/torii/graphql/src/server.rs +++ b/crates/torii/graphql/src/server.rs @@ -12,7 +12,6 @@ use url::Url; use warp::{Filter, Rejection, Reply}; use super::schema::build_schema; - use crate::constants::MODEL_TABLE; use crate::query::data::count_rows; From ef16b65f8145718ea2c0048fcc151bbca1988b49 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 19 Aug 2024 16:06:27 +0530 Subject: [PATCH 22/23] fix lints --- crates/torii/core/src/engine.rs | 6 ++++-- crates/torii/core/src/processors/erc721_transfer.rs | 2 +- crates/torii/core/src/sql.rs | 2 +- crates/torii/core/src/sql_test.rs | 8 +++++--- crates/torii/core/src/types.rs | 9 ++++----- crates/torii/graphql/src/tests/metadata_test.rs | 6 ++++-- crates/torii/graphql/src/tests/mod.rs | 4 +++- crates/torii/graphql/src/tests/subscription_test.rs | 11 ++++++----- crates/torii/grpc/src/server/tests/entities_test.rs | 4 +++- crates/torii/libp2p/src/tests.rs | 3 ++- 10 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index e2c938fa2a..1be6c966c0 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -1,3 +1,4 @@ +use std::collections::btree_map::Entry; use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::time::Duration; @@ -74,6 +75,7 @@ struct UnprocessedEvent { } impl Engine

{ + #[allow(clippy::too_many_arguments)] pub fn new( world: WorldContractReader

, db: Sql, @@ -308,9 +310,9 @@ impl Engine

{ }; // Fetch block timestamp if not already fetched and inserts into the map - if !blocks.contains_key(&block_number) { + if let Entry::Vacant(e) = blocks.entry(block_number) { let block_timestamp = self.get_block_timestamp(block_number).await?; - blocks.insert(block_number, block_timestamp); + e.insert(block_timestamp); } // Then we skip all transactions until we reach the last pending processed diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs index 91573b3994..b09cf66b34 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -27,7 +27,7 @@ where // ref: https://github.com/OpenZeppelin/cairo-contracts/blob/eabfa029b7b681d9e83bf171f723081b07891016/packages/token/src/erc721/erc721.cairo#L44-L53 // key: [hash(Transfer), from, to, token_id.low, token_id.high] // data: [] - if event.keys.len() == 5 && event.data.len() == 0 { + if event.keys.len() == 5 && event.data.is_empty() { return true; } diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 38608c2069..f233e59230 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -142,7 +142,7 @@ impl Sql { executed_at=EXCLUDED.executed_at RETURNING *"; let model_registered: ModelRegistered = sqlx::query_as(insert_models) // this is temporary until the model hash is precomputed - .bind(&format!("{:#x}", selector)) + .bind(format!("{:#x}", selector)) .bind(namespace) .bind(model.name()) .bind(format!("{class_hash:#x}")) diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index e6db7f5d3a..db22ca1fbb 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::str::FromStr; use cainome::cairo_serde::ContractAddress; @@ -48,6 +49,7 @@ where EngineConfig::default(), shutdown_tx, None, + HashMap::default(), ); let _ = engine.sync_to_head(0, None).await?; @@ -117,7 +119,7 @@ async fn test_load_from_remote() { let world_reader = WorldContractReader::new(strat.world_address, account.provider()); - let mut db = Sql::new(pool.clone(), world_reader.address).await.unwrap(); + let mut db = Sql::new(pool.clone(), world_reader.address, &HashMap::default()).await.unwrap(); let _ = bootstrap_engine(world_reader, db.clone(), account.provider()).await; @@ -276,7 +278,7 @@ async fn test_load_from_remote_del() { let world_reader = WorldContractReader::new(strat.world_address, account.provider()); - let mut db = Sql::new(pool.clone(), world_reader.address).await.unwrap(); + let mut db = Sql::new(pool.clone(), world_reader.address, &HashMap::default()).await.unwrap(); let _ = bootstrap_engine(world_reader, db.clone(), account.provider()).await; @@ -352,7 +354,7 @@ async fn test_get_entity_keys() { let world_reader = WorldContractReader::new(strat.world_address, account.provider()); - let mut db = Sql::new(pool.clone(), world_reader.address).await.unwrap(); + let mut db = Sql::new(pool.clone(), world_reader.address, &HashMap::default()).await.unwrap(); let _ = bootstrap_engine(world_reader, db.clone(), account.provider()).await; diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index c8fa802854..88a307d59f 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -127,12 +127,11 @@ impl FromStr for ErcType { } } -impl ToString for ErcType { - fn to_string(&self) -> String { +impl std::fmt::Display for ErcType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ErcType::ERC20 => "ERC20", - ErcType::ERC721 => "ERC721", + ErcType::ERC20 => write!(f, "ERC20"), + ErcType::ERC721 => write!(f, "ERC721"), } - .to_string() } } diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index 7383a36b08..8cf2965629 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + use std::collections::HashMap; + use dojo_world::config::ProfileConfig; use dojo_world::metadata::WorldMetadata; use sqlx::SqlitePool; @@ -48,7 +50,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] async fn test_metadata(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); let schema = build_schema(&pool).await.unwrap(); let cover_img = "QWxsIHlvdXIgYmFzZSBiZWxvbmcgdG8gdXM="; @@ -100,7 +102,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] async fn test_empty_content(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); let schema = build_schema(&pool).await.unwrap(); db.set_metadata(&RESOURCE, URI, BLOCK_TIMESTAMP); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 6dacff739f..60bf973d14 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::str::FromStr; use anyhow::Result; @@ -344,7 +345,7 @@ pub async fn spinup_types_test() -> Result { let world = WorldContractReader::new(strat.world_address, account.provider()); - let db = Sql::new(pool.clone(), strat.world_address).await.unwrap(); + let db = Sql::new(pool.clone(), strat.world_address, &HashMap::default()).await.unwrap(); let (shutdown_tx, _) = broadcast::channel(1); let mut engine = Engine::new( @@ -362,6 +363,7 @@ pub async fn spinup_types_test() -> Result { EngineConfig::default(), shutdown_tx, None, + HashMap::default(), ); let _ = engine.sync_to_head(0, None).await?; diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index 015df8789c..b2dcfb746b 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; @@ -21,7 +22,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] #[serial] async fn test_entity_subscription(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); model_fixtures(&mut db).await; // 0. Preprocess expected entity value @@ -147,7 +148,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] #[serial] async fn test_entity_subscription_with_id(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); model_fixtures(&mut db).await; // 0. Preprocess expected entity value @@ -252,7 +253,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] #[serial] async fn test_model_subscription(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); // 0. Preprocess model value let namespace = "types_test".to_string(); let model_name = "Subrecord".to_string(); @@ -316,7 +317,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] #[serial] async fn test_model_subscription_with_id(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); // 0. Preprocess model value let namespace = "types_test".to_string(); let model_name = "Subrecord".to_string(); @@ -381,7 +382,7 @@ mod tests { #[sqlx::test(migrations = "../migrations")] #[serial] async fn test_event_emitted(pool: SqlitePool) { - let mut db = Sql::new(pool.clone(), Felt::ZERO).await.unwrap(); + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await.unwrap(); let block_timestamp: u64 = 1710754478_u64; let (tx, mut rx) = mpsc::channel(7); tokio::spawn(async move { diff --git a/crates/torii/grpc/src/server/tests/entities_test.rs b/crates/torii/grpc/src/server/tests/entities_test.rs index 96503034cb..66d6e51164 100644 --- a/crates/torii/grpc/src/server/tests/entities_test.rs +++ b/crates/torii/grpc/src/server/tests/entities_test.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; @@ -94,7 +95,7 @@ async fn test_entities_queries() { TransactionWaiter::new(tx.transaction_hash, &provider).await.unwrap(); - let db = Sql::new(pool.clone(), strat.world_address).await.unwrap(); + let db = Sql::new(pool.clone(), strat.world_address, &HashMap::default()).await.unwrap(); let (shutdown_tx, _) = broadcast::channel(1); let mut engine = Engine::new( @@ -108,6 +109,7 @@ async fn test_entities_queries() { EngineConfig::default(), shutdown_tx, None, + HashMap::default(), ); let _ = engine.sync_to_head(0, None).await.unwrap(); diff --git a/crates/torii/libp2p/src/tests.rs b/crates/torii/libp2p/src/tests.rs index eeaca36005..e7a7c5d508 100644 --- a/crates/torii/libp2p/src/tests.rs +++ b/crates/torii/libp2p/src/tests.rs @@ -524,6 +524,7 @@ mod test { #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_client_messaging() -> Result<(), Box> { + use std::collections::HashMap; use std::time::Duration; use dojo_types::schema::{Member, Struct, Ty}; @@ -559,7 +560,7 @@ mod test { let account = sequencer.account_data(0); - let mut db = Sql::new(pool.clone(), Felt::ZERO).await?; + let mut db = Sql::new(pool.clone(), Felt::ZERO, &HashMap::default()).await?; // Register the model of our Message db.register_model( From 1240bdf73110ffc16fc917a57e4202b6239bb852 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 20 Aug 2024 16:42:26 +0530 Subject: [PATCH 23/23] update schema and enable foreign key constraint --- crates/torii/migrations/20240803102207_add_erc.sql | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/torii/migrations/20240803102207_add_erc.sql b/crates/torii/migrations/20240803102207_add_erc.sql index ada0416499..9951207f19 100644 --- a/crates/torii/migrations/20240803102207_add_erc.sql +++ b/crates/torii/migrations/20240803102207_add_erc.sql @@ -3,7 +3,8 @@ CREATE TABLE contracts ( id TEXT NOT NULL PRIMARY KEY, contract_address TEXT NOT NULL, contract_type TEXT NOT NULL, - head TEXT + head BIGINT NOT NULL DEFAULT 0, + pending_block_tx TEXT NULL DEFAULT NULL ); CREATE TABLE balances ( @@ -26,8 +27,8 @@ CREATE TABLE tokens ( contract_address TEXT NOT NULL, name TEXT NOT NULL, symbol TEXT NOT NULL, - decimals INTEGER NOT NULL - -- FOREIGN KEY (contract_address) REFERENCES contracts(id) + decimals INTEGER NOT NULL, + FOREIGN KEY (contract_address) REFERENCES contracts(id) ); CREATE TABLE erc_transfers (