Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(torii): indexing cartridge controllers #2959

Merged
merged 18 commits into from
Feb 1, 2025
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions crates/torii/cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@
)]
#[serde(default)]
pub world_block: u64,

/// Whether or not to index Cartridge controllers.
#[arg(
long = "indexing.cartridge",
default_value_t = false,
help = "Whether or not to index Cartridge controllers."
)]
#[serde(default)]
pub cartridge: bool,

Check warning on line 181 in crates/torii/cli/src/options.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/cli/src/options.rs#L181

Added line #L181 was not covered by tests
}

impl Default for IndexingOptions {
Expand All @@ -184,6 +193,7 @@
max_concurrent_tasks: DEFAULT_MAX_CONCURRENT_TASKS,
namespaces: vec![],
world_block: 0,
cartridge: false,

Check warning on line 196 in crates/torii/cli/src/options.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/cli/src/options.rs#L196

Added line #L196 was not covered by tests
}
}
}
Expand Down Expand Up @@ -226,6 +236,10 @@
if self.world_block == 0 {
self.world_block = other.world_block;
}

if !self.cartridge {
self.cartridge = other.cartridge;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/torii/indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ ipfs-api-backend-hyper.workspace = true
tokio-util.workspace = true
tracing.workspace = true
torii-sqlite.workspace = true
lazy_static.workspace = true

[dev-dependencies]
dojo-test-utils.workspace = true
Expand Down
5 changes: 4 additions & 1 deletion crates/torii/indexer/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use torii_sqlite::{Cursors, Sql};
use tracing::{debug, error, info, trace, warn};

use crate::constants::LOG_TARGET;
use crate::processors::controller::ControllerProcessor;
use crate::processors::erc20_legacy_transfer::Erc20LegacyTransferProcessor;
use crate::processors::erc20_transfer::Erc20TransferProcessor;
use crate::processors::erc721_legacy_transfer::Erc721LegacyTransferProcessor;
Expand All @@ -39,6 +40,7 @@ use crate::processors::register_event::RegisterEventProcessor;
use crate::processors::register_model::RegisterModelProcessor;
use crate::processors::store_del_record::StoreDelRecordProcessor;
use crate::processors::store_set_record::StoreSetRecordProcessor;
use crate::processors::store_transaction::StoreTransactionProcessor;
use crate::processors::store_update_member::StoreUpdateMemberProcessor;
use crate::processors::store_update_record::StoreUpdateRecordProcessor;
use crate::processors::upgrade_event::UpgradeEventProcessor;
Expand All @@ -62,7 +64,7 @@ impl<P: Provider + Send + Sync + std::fmt::Debug + 'static> Default for Processo
fn default() -> Self {
Self {
block: vec![],
transaction: vec![],
transaction: vec![Box::new(StoreTransactionProcessor)],
// We shouldn't have a catch all for now since the world doesn't forward raw events
// anymore.
catch_all_event: Box::new(RawEventProcessor) as Box<dyn EventProcessor<P>>,
Expand Down Expand Up @@ -105,6 +107,7 @@ impl<P: Provider + Send + Sync + std::fmt::Debug + 'static> Processors<P> {
Box::new(Erc721LegacyTransferProcessor) as Box<dyn EventProcessor<P>>,
],
),
(ContractType::UDC, vec![Box::new(ControllerProcessor) as Box<dyn EventProcessor<P>>]),
];

for (contract_type, processors) in event_processors {
Expand Down
124 changes: 124 additions & 0 deletions crates/torii/indexer/src/processors/controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use std::hash::{DefaultHasher, Hash, Hasher};

use anyhow::{Error, Result};
use async_trait::async_trait;
use dojo_world::contracts::world::WorldContractReader;
use lazy_static::lazy_static;
use starknet::core::types::Event;
use starknet::core::utils::parse_cairo_short_string;
use starknet::macros::felt;
use starknet::providers::Provider;
use starknet_crypto::Felt;
use torii_sqlite::Sql;
use tracing::info;

use super::{EventProcessor, EventProcessorConfig};
use crate::task_manager::{TaskId, TaskPriority};

pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::controller";

#[derive(Default, Debug)]
pub struct ControllerProcessor;

lazy_static! {
// https://x.cartridge.gg/
pub(crate) static ref CARTRIDGE_MAGIC: [Felt; 22] = [
felt!("0x68"),
felt!("0x74"),
felt!("0x74"),
felt!("0x70"),
felt!("0x73"),
felt!("0x3a"),
felt!("0x2f"),
felt!("0x2f"),
felt!("0x78"),
felt!("0x2e"),
felt!("0x63"),
felt!("0x61"),
felt!("0x72"),
felt!("0x74"),
felt!("0x72"),
felt!("0x69"),
felt!("0x64"),
felt!("0x67"),
felt!("0x65"),
felt!("0x2e"),
felt!("0x67"),
felt!("0x67"),
];
}

#[async_trait]
impl<P> EventProcessor<P> for ControllerProcessor
where
P: Provider + Send + Sync + std::fmt::Debug,
{
fn event_key(&self) -> String {
"ContractDeployed".to_string()
}

fn validate(&self, event: &Event) -> bool {
// ContractDeployed event has no keys and contains username in data
event.keys.len() == 1 && !event.data.is_empty()
}

Check warning on line 63 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L60-L63

Added lines #L60 - L63 were not covered by tests
Comment on lines +60 to +63
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider tightening bounds checks in validate, sensei!
You rely on event.data[5..] slicing later in process. If event.data has fewer than 5 items, slicing will silently panic. It may be safer to validate the minimal number of felts here or handle it gracefully in process.


fn task_priority(&self) -> TaskPriority {
3
}

Check warning on line 67 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L65-L67

Added lines #L65 - L67 were not covered by tests

fn task_identifier(&self, event: &Event) -> TaskId {
let mut hasher = DefaultHasher::new();
// the contract address is the first felt in data
event.data[0].hash(&mut hasher);
hasher.finish()
}

Check warning on line 74 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L69-L74

Added lines #L69 - L74 were not covered by tests

async fn process(
&self,
_world: &WorldContractReader<P>,
db: &mut Sql,
_block_number: u64,
block_timestamp: u64,
_event_id: &str,
event: &Event,
_config: &EventProcessorConfig,
) -> Result<(), Error> {

Check warning on line 85 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L85

Added line #L85 was not covered by tests
// Address is the first felt in data
let address = event.data[0];

let calldata = event.data[5..].to_vec();
// our calldata has to be more than 25 felts.
if calldata.len() < 25 {
return Ok(());
}
// check for this sequence of felts
let cartridge_magic_len = calldata[2];
// length has to be 22
if cartridge_magic_len != Felt::from(22) {
return Ok(());
}

// this should never fail if since our len is 22
let cartridge_magic: [Felt; 22] = calldata[3..25].try_into().unwrap();
Larkooo marked this conversation as resolved.
Show resolved Hide resolved

// has to match with https://x.cartridge.gg/
if !CARTRIDGE_MAGIC.eq(&cartridge_magic) {
return Ok(());
}

// Last felt in data is the salt which is the username encoded as short string
let username_felt = event.data[event.data.len() - 1];
let username = parse_cairo_short_string(&username_felt)?;

Check warning on line 111 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L87-L111

Added lines #L87 - L111 were not covered by tests
Larkooo marked this conversation as resolved.
Show resolved Hide resolved

info!(

Check warning on line 113 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L113

Added line #L113 was not covered by tests
target: LOG_TARGET,
username = %username,
address = %format!("{address:#x}"),
"Controller deployed."

Check warning on line 117 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L116-L117

Added lines #L116 - L117 were not covered by tests
);

db.add_controller(&username, &format!("{address:#x}"), block_timestamp).await?;

Check warning on line 120 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L120

Added line #L120 was not covered by tests

Ok(())
}

Check warning on line 123 in crates/torii/indexer/src/processors/controller.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/controller.rs#L122-L123

Added lines #L122 - L123 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
block_number,
)
.await?;
debug!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "Legacy ERC20 Transfer");
debug!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "Legacy ERC20 Transfer.");

Check warning on line 86 in crates/torii/indexer/src/processors/erc20_legacy_transfer.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc20_legacy_transfer.rs#L86

Added line #L86 was not covered by tests

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/torii/indexer/src/processors/erc20_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
block_number,
)
.await?;
debug!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "ERC20 Transfer");
debug!(target: LOG_TARGET,from = ?from, to = ?to, value = ?value, "ERC20 Transfer.");

Check warning on line 86 in crates/torii/indexer/src/processors/erc20_transfer.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc20_transfer.rs#L86

Added line #L86 was not covered by tests

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
block_number,
)
.await?;
debug!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer");
debug!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer.");

Check warning on line 93 in crates/torii/indexer/src/processors/erc721_legacy_transfer.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc721_legacy_transfer.rs#L93

Added line #L93 was not covered by tests

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/torii/indexer/src/processors/erc721_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
block_number,
)
.await?;
debug!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer");
debug!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, "ERC721 Transfer.");

Check warning on line 93 in crates/torii/indexer/src/processors/erc721_transfer.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc721_transfer.rs#L93

Added line #L93 was not covered by tests

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/torii/indexer/src/processors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use torii_sqlite::Sql;

use crate::task_manager::{TaskId, TaskPriority};

pub mod controller;
pub mod erc20_legacy_transfer;
pub mod erc20_transfer;
pub mod erc721_legacy_transfer;
Expand All @@ -25,7 +26,6 @@ pub mod store_update_member;
pub mod store_update_record;
pub mod upgrade_event;
pub mod upgrade_model;

#[derive(Clone, Debug, Default)]
pub struct EventProcessorConfig {
pub historical_events: HashSet<String>,
Expand Down
9 changes: 9 additions & 0 deletions crates/torii/migrations/20250128051146_controllers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Cartridge controllers
CREATE TABLE controllers (
id TEXT PRIMARY KEY NOT NULL, -- Username as primary key
username TEXT NOT NULL, -- Username
address TEXT NOT NULL, -- Wallet address
deployed_at TIMESTAMP NOT NULL -- Block timestamp of deployment
);

CREATE INDEX idx_controllers_address ON controllers (address);
7 changes: 7 additions & 0 deletions crates/torii/runner/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use starknet::macros::felt;
use starknet_crypto::Felt;

pub(crate) const LOG_TARGET: &str = "torii:runner";

pub(crate) const UDC_ADDRESS: Felt =
felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf");
18 changes: 12 additions & 6 deletions crates/torii/runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use std::time::Duration;

use camino::Utf8PathBuf;
use constants::UDC_ADDRESS;
use dojo_metrics::exporters::prometheus::PrometheusRecorder;
use dojo_world::contracts::world::WorldContractReader;
use sqlx::sqlite::{
Expand All @@ -31,7 +32,6 @@
use tokio_stream::StreamExt;
use torii_cli::ToriiArgs;
use torii_indexer::engine::{Engine, EngineConfig, IndexingFlags, Processors};
use torii_indexer::processors::store_transaction::StoreTransactionProcessor;
use torii_indexer::processors::EventProcessorConfig;
use torii_server::proxy::Proxy;
use torii_sqlite::cache::ModelCache;
Expand All @@ -43,7 +43,9 @@
use tracing_subscriber::{fmt, EnvFilter};
use url::form_urlencoded;

pub(crate) const LOG_TARGET: &str = "torii:runner";
mod constants;

use crate::constants::LOG_TARGET;

#[derive(Debug, Clone)]
pub struct Runner {
Expand All @@ -67,6 +69,13 @@
.contracts
.push(Contract { address: world_address, r#type: ContractType::WORLD });

if self.args.indexing.cartridge {
self.args
.indexing
.contracts
.push(Contract { address: UDC_ADDRESS, r#type: ContractType::UDC });
}

Check warning on line 77 in crates/torii/runner/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/runner/src/lib.rs#L72-L77

Added lines #L72 - L77 were not covered by tests

let filter_layer = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off"));

Expand Down Expand Up @@ -147,10 +156,7 @@
)
.await?;

let processors = Processors {
transaction: vec![Box::new(StoreTransactionProcessor)],
..Processors::default()
};
let processors = Processors::default();

Check warning on line 159 in crates/torii/runner/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/runner/src/lib.rs#L159

Added line #L159 was not covered by tests

let (block_tx, block_rx) = tokio::sync::mpsc::channel(100);

Expand Down
1 change: 1 addition & 0 deletions crates/torii/sqlite/src/executor/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
let id = id_str.split(SQL_FELT_DELIMITER).collect::<Vec<&str>>();
match contract_type {
ContractType::WORLD => unreachable!(),
ContractType::UDC => unreachable!(),

Check warning on line 59 in crates/torii/sqlite/src/executor/erc.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L59

Added line #L59 was not covered by tests
ContractType::ERC721 => {
// account_address/contract_address:id => ERC721
assert!(id.len() == 2);
Expand Down
27 changes: 27 additions & 0 deletions crates/torii/sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,33 @@
self.executor.send(rollback)?;
recv.await?
}

pub async fn add_controller(
&mut self,
username: &str,
address: &str,
block_timestamp: u64,
) -> Result<()> {
let insert_controller = "
INSERT INTO controllers (id, username, address, deployed_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
username=EXCLUDED.username,
address=EXCLUDED.address,
deployed_at=EXCLUDED.deployed_at
RETURNING *";

let arguments = vec![
Argument::String(username.to_string()),
Argument::String(username.to_string()),
Argument::String(address.to_string()),
Argument::String(utc_dt_string_from_timestamp(block_timestamp)),
];

self.executor.send(QueryMessage::other(insert_controller.to_string(), arguments))?;

Check warning on line 848 in crates/torii/sqlite/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/lib.rs#L826-L848

Added lines #L826 - L848 were not covered by tests

Ok(())
}

Check warning on line 851 in crates/torii/sqlite/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/lib.rs#L850-L851

Added lines #L850 - L851 were not covered by tests
}

fn add_columns_recursive(
Expand Down
3 changes: 3 additions & 0 deletions crates/torii/sqlite/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
WORLD,
ERC20,
ERC721,
UDC,
}

impl std::fmt::Display for Contract {
Expand All @@ -172,6 +173,7 @@
"world" => Ok(ContractType::WORLD),
"erc20" => Ok(ContractType::ERC20),
"erc721" => Ok(ContractType::ERC721),
"udc" => Ok(ContractType::UDC),

Check warning on line 176 in crates/torii/sqlite/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/types.rs#L176

Added line #L176 was not covered by tests
_ => Err(anyhow::anyhow!("Invalid ERC type: {}", input)),
}
}
Expand All @@ -183,6 +185,7 @@
ContractType::WORLD => write!(f, "WORLD"),
ContractType::ERC20 => write!(f, "ERC20"),
ContractType::ERC721 => write!(f, "ERC721"),
ContractType::UDC => write!(f, "UDC"),

Check warning on line 188 in crates/torii/sqlite/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/types.rs#L188

Added line #L188 was not covered by tests
}
}
}
Expand Down
Loading