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-indexer): eip 4906 update metadata processor #2984

Merged
merged 20 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions crates/torii/indexer/src/processors/erc4906_metadata_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::hash::{DefaultHasher, Hash, Hasher};

use anyhow::Error;
use async_trait::async_trait;
use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome};
use dojo_world::contracts::world::WorldContractReader;
use starknet::core::types::{Event, U256};
use starknet::providers::Provider;
use torii_sqlite::Sql;
use tracing::debug;

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

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

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

#[async_trait]
impl<P> EventProcessor<P> for Erc4906MetadataUpdateProcessor
where
P: Provider + Send + Sync + std::fmt::Debug,
{
fn event_key(&self) -> String {
// We'll handle both event types in validate()
"MetadataUpdate".to_string()
}

Check warning on line 28 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L25-L28

Added lines #L25 - L28 were not covered by tests

fn validate(&self, event: &Event) -> bool {
// Single token metadata update: [hash(MetadataUpdate), token_id.low, token_id.high]
if event.keys.len() == 3 && event.data.is_empty() {
return true;
}

// Batch metadata update: [hash(BatchMetadataUpdate), from_token_id.low, from_token_id.high,
// to_token_id.low, to_token_id.high]
if event.keys.len() == 5 && event.data.is_empty() {
return true;
}

false
}

Check warning on line 43 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L30-L43

Added lines #L30 - L43 were not covered by tests

fn task_priority(&self) -> TaskPriority {
2 // Lower priority than transfers
}

Check warning on line 47 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L45-L47

Added lines #L45 - L47 were not covered by tests

fn task_identifier(&self, event: &Event) -> TaskId {
let mut hasher = DefaultHasher::new();
event.from_address.hash(&mut hasher); // Hash the contract address

// For single token updates
if event.keys.len() == 3 {
event.keys[1].hash(&mut hasher); // token_id.low
event.keys[2].hash(&mut hasher); // token_id.high
} else {
// For batch updates, we need to be more conservative
// Hash just the contract address to serialize all batch updates for the same contract
// This prevents race conditions with overlapping ranges
}

Check warning on line 61 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L49-L61

Added lines #L49 - L61 were not covered by tests

hasher.finish()
}

Check warning on line 64 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L63-L64

Added lines #L63 - L64 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> {
let token_address = event.from_address;

if event.keys.len() == 3 {

Check warning on line 78 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L75-L78

Added lines #L75 - L78 were not covered by tests
// Single token metadata update
let token_id = U256Cainome::cairo_deserialize(&event.keys, 1)?;
let token_id = U256::from_words(token_id.low, token_id.high);

db.update_erc721_metadata(token_address, token_id).await?;

Check warning on line 83 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L80-L83

Added lines #L80 - L83 were not covered by tests

debug!(

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L85 was not covered by tests
target: LOG_TARGET,
token_address = ?token_address,
token_id = ?token_id,
"ERC721 metadata updated for single token"

Check warning on line 89 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L89

Added line #L89 was not covered by tests
);
} else {
// Batch metadata update
let from_token_id = U256Cainome::cairo_deserialize(&event.keys, 1)?;
let from_token_id = U256::from_words(from_token_id.low, from_token_id.high);

Check warning on line 94 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L93-L94

Added lines #L93 - L94 were not covered by tests

let to_token_id = U256Cainome::cairo_deserialize(&event.keys, 3)?;
let to_token_id = U256::from_words(to_token_id.low, to_token_id.high);

let mut token_id = from_token_id;
while token_id <= to_token_id {
db.update_erc721_metadata(token_address, token_id).await?;
token_id += U256::from(1u8);

Check warning on line 102 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L96-L102

Added lines #L96 - L102 were not covered by tests
}

debug!(

Check warning on line 105 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L105

Added line #L105 was not covered by tests
target: LOG_TARGET,
token_address = ?token_address,
from_token_id = ?from_token_id,
to_token_id = ?to_token_id,
"ERC721 metadata updated for token range"

Check warning on line 110 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L110

Added line #L110 was not covered by tests
);
}

Ok(())
}

Check warning on line 115 in crates/torii/indexer/src/processors/erc4906_metadata_update.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/indexer/src/processors/erc4906_metadata_update.rs#L114-L115

Added lines #L114 - L115 were not covered by tests
Larkooo marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 2 additions & 0 deletions crates/torii/indexer/src/processors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::task_manager::{TaskId, TaskPriority};
pub mod controller;
pub mod erc20_legacy_transfer;
pub mod erc20_transfer;
pub mod erc4906_metadata_update;
pub mod erc721_legacy_transfer;
pub mod erc721_transfer;
pub mod event_message;
Expand All @@ -26,6 +27,7 @@ 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
18 changes: 18 additions & 0 deletions crates/torii/sqlite/src/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use super::utils::{u256_to_sql_string, I256};
use super::{Sql, SQL_FELT_DELIMITER};
use crate::constants::TOKEN_TRANSFER_TABLE;
use crate::executor::erc::UpdateErc721MetadataQuery;
use crate::executor::{
ApplyBalanceDiffQuery, Argument, QueryMessage, QueryType, RegisterErc20TokenQuery,
RegisterErc721TokenQuery,
Expand Down Expand Up @@ -142,6 +143,23 @@
Ok(())
}

pub async fn update_erc721_metadata(
&mut self,
contract_address: Felt,
token_id: U256,
) -> Result<()> {
self.executor.send(QueryMessage::new(
"".to_string(),
vec![],
QueryType::UpdateErc721Metadata(UpdateErc721MetadataQuery {
contract_address,
token_id,
}),
))?;

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/erc.rs#L146-L158

Added lines #L146 - L158 were not covered by tests

Ok(())
}

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/erc.rs#L160-L161

Added lines #L160 - L161 were not covered by tests

async fn register_erc20_token_metadata<P: Provider + Sync>(
&mut self,
contract_address: Felt,
Expand Down
129 changes: 87 additions & 42 deletions crates/torii/sqlite/src/executor/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
pub decimals: u8,
}

#[derive(Debug, Clone)]
pub struct UpdateErc721MetadataQuery {
pub contract_address: Felt,
pub token_id: U256,
}

impl<'c, P: Provider + Sync + Send + 'static> Executor<'c, P> {
pub async fn apply_balance_diff(
&mut self,
Expand Down Expand Up @@ -180,21 +186,17 @@
Ok(())
}

pub async fn process_register_erc721_token_query(
register_erc721_token: RegisterErc721TokenQuery,
provider: Arc<P>,
name: String,
symbol: String,
) -> Result<RegisterErc721TokenMetadata> {
async fn fetch_token_uri(
provider: &P,
contract_address: Felt,
token_id: U256,
) -> Result<String> {

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L189-L193

Added lines #L189 - L193 were not covered by tests
let token_uri = if let Ok(token_uri) = provider
.call(
FunctionCall {
contract_address: register_erc721_token.contract_address,
contract_address,

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L197 was not covered by tests
entry_point_selector: get_selector_from_name("token_uri").unwrap(),
calldata: vec![
register_erc721_token.actual_token_id.low().into(),
register_erc721_token.actual_token_id.high().into(),
],
calldata: vec![token_id.low().into(), token_id.high().into()],

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L199 was not covered by tests
},
BlockId::Tag(BlockTag::Pending),
)
Expand All @@ -204,12 +206,9 @@
} else if let Ok(token_uri) = provider
.call(
FunctionCall {
contract_address: register_erc721_token.contract_address,
contract_address,

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L209 was not covered by tests
entry_point_selector: get_selector_from_name("tokenURI").unwrap(),
calldata: vec![
register_erc721_token.actual_token_id.low().into(),
register_erc721_token.actual_token_id.high().into(),
],
calldata: vec![token_id.low().into(), token_id.high().into()],

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L211 was not covered by tests
},
BlockId::Tag(BlockTag::Pending),
)
Expand All @@ -218,12 +217,10 @@
token_uri
} else {
warn!(
contract_address = format!("{:#x}", register_erc721_token.contract_address),
token_id = %register_erc721_token.actual_token_id,
contract_address = format!("{:#x}", contract_address),
token_id = %token_id,

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L220-L221

Added lines #L220 - L221 were not covered by tests
"Error fetching token URI, empty metadata will be used instead.",
);

// Ignoring the token URI if the contract can't return it.
ByteArray::cairo_serialize(&"".try_into().unwrap())
};

Expand All @@ -234,33 +231,82 @@
.iter()
.map(parse_cairo_short_string)
.collect::<Result<Vec<String>, _>>()
.map(|strings| strings.join(""))
.map_err(|_| anyhow::anyhow!("Failed parsing Array<Felt> to String"))?
.map(|strings| strings.join(""))?

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L234 was not covered by tests
} else {
return Err(anyhow::anyhow!("token_uri is neither ByteArray nor Array<Felt>"));
};

let metadata = if token_uri.is_empty() {
"".to_string()
Ok(token_uri)
}

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L239-L240

Added lines #L239 - L240 were not covered by tests

async fn fetch_token_metadata(
contract_address: Felt,
token_id: U256,
token_uri: &str,
) -> Result<String> {
if token_uri.is_empty() {
Ok("".to_string())

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L242-L248

Added lines #L242 - L248 were not covered by tests
} else {
let metadata = Self::fetch_metadata(&token_uri).await.with_context(|| {
format!(
"Failed to fetch metadata for token_id: {}",
register_erc721_token.actual_token_id
)
});

if let Ok(metadata) = metadata {
serde_json::to_string(&metadata).context("Failed to serialize metadata")?
} else {
warn!(
contract_address = format!("{:#x}", register_erc721_token.contract_address),
token_id = %register_erc721_token.actual_token_id,
"Error fetching metadata, empty metadata will be used instead.",
);
"".to_string()
let metadata = Self::fetch_metadata(token_uri)
.await
.with_context(|| format!("Failed to fetch metadata for token_id: {}", token_id));

match metadata {
Ok(metadata) => {
serde_json::to_string(&metadata).context("Failed to serialize metadata")

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L250-L256

Added lines #L250 - L256 were not covered by tests
}
Err(e) => {
warn!(
contract_address = format!("{:#x}", contract_address),
token_id = %token_id,
error = ?e,
"Error fetching metadata, empty metadata will be used instead.",

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L258-L263

Added lines #L258 - L263 were not covered by tests
);
Ok("".to_string())

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L265 was not covered by tests
}
}
};
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L269 was not covered by tests

pub async fn update_erc721_metadata(
&mut self,
contract_address: Felt,
token_id: U256,
provider: Arc<P>,
) -> Result<()> {
let token_uri = Self::fetch_token_uri(&provider, contract_address, token_id).await?;
let metadata = Self::fetch_token_metadata(contract_address, token_id, &token_uri).await?;

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L271-L278

Added lines #L271 - L278 were not covered by tests

// Update metadata in database
sqlx::query("UPDATE tokens SET metadata = ? WHERE contract_address = ? AND id LIKE ?")
.bind(&metadata)
.bind(felt_to_sql_string(&contract_address))
.bind(format!("%{}", u256_to_sql_string(&token_id)))
.execute(&mut *self.transaction)
.await?;

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L281-L286

Added lines #L281 - L286 were not covered by tests

Ok(())
}

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L288-L289

Added lines #L288 - L289 were not covered by tests

pub async fn process_register_erc721_token_query(
register_erc721_token: RegisterErc721TokenQuery,
provider: Arc<P>,
name: String,
symbol: String,
) -> Result<RegisterErc721TokenMetadata> {
let token_uri = Self::fetch_token_uri(
&provider,
register_erc721_token.contract_address,
register_erc721_token.actual_token_id,
)
.await?;

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L291-L302

Added lines #L291 - L302 were not covered by tests

let metadata = Self::fetch_token_metadata(
register_erc721_token.contract_address,
register_erc721_token.actual_token_id,
&token_uri,
)
.await?;

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

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/erc.rs#L304-L309

Added lines #L304 - L309 were not covered by tests

Ok(RegisterErc721TokenMetadata { query: register_erc721_token, metadata, name, symbol })
}
Expand All @@ -269,7 +315,6 @@
// metadata json schema
pub async fn fetch_metadata(token_uri: &str) -> Result<serde_json::Value> {
// Parse the token_uri

match token_uri {
uri if uri.starts_with("http") || uri.starts_with("https") => {
// Fetch metadata from HTTP/HTTPS URL
Expand Down
15 changes: 13 additions & 2 deletions crates/torii/sqlite/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use anyhow::{Context, Result};
use cainome::cairo_serde::{ByteArray, CairoSerde};
use dojo_types::schema::{Struct, Ty};
use erc::UpdateErc721MetadataQuery;
use sqlx::{FromRow, Pool, Sqlite, Transaction};
use starknet::core::types::{BlockId, BlockTag, Felt, FunctionCall};
use starknet::core::utils::{get_selector_from_name, parse_cairo_short_string};
Expand Down Expand Up @@ -115,10 +116,9 @@
TokenTransfer,
RegisterModel,
StoreEvent,
// similar to execute but doesn't create a new transaction
UpdateErc721Metadata(UpdateErc721MetadataQuery),
Flush,
Execute,
// rollback's the current transaction and starts a new one
Rollback,
Other,
}
Expand Down Expand Up @@ -763,6 +763,17 @@
res?;
}
}
QueryType::UpdateErc721Metadata(update_metadata) => {
debug!(target: LOG_TARGET, "Updating ERC721 metadata.");
let instant = Instant::now();
self.update_erc721_metadata(
update_metadata.contract_address,
update_metadata.token_id,
self.provider.clone(),
)
.await?;
debug!(target: LOG_TARGET, duration = ?instant.elapsed(), "Updated ERC721 metadata.");

Check warning on line 775 in crates/torii/sqlite/src/executor/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/sqlite/src/executor/mod.rs#L766-L775

Added lines #L766 - L775 were not covered by tests
}
QueryType::Other => {
query.execute(&mut **tx).await.map_err(|e| {
anyhow::anyhow!(
Expand Down
Loading