Skip to content

Commit

Permalink
feat(torii): add support for erc1155 (#2955)
Browse files Browse the repository at this point in the history
* feat(torii): add support for erc1155

* add erc1155 processors

* processors & uri

* uri token id

* erc1155 {id} substitution from spec

* fmt

* show in debug

* debug import

* token uri in warn

* cleanup and comment

* fmt

* fmt

* fix batch

* fix no token uri
  • Loading branch information
Larkooo authored Feb 12, 2025
1 parent f0c1e0b commit b87a011
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 372 deletions.
494 changes: 204 additions & 290 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions crates/torii/indexer/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use tracing::{debug, error, info, trace, warn};

use crate::constants::LOG_TARGET;
use crate::processors::controller::ControllerProcessor;
use crate::processors::erc1155_transfer_batch::Erc1155TransferBatchProcessor;
use crate::processors::erc1155_transfer_single::Erc1155TransferSingleProcessor;
use crate::processors::erc20_legacy_transfer::Erc20LegacyTransferProcessor;
use crate::processors::erc20_transfer::Erc20TransferProcessor;
use crate::processors::erc721_legacy_transfer::Erc721LegacyTransferProcessor;
Expand Down Expand Up @@ -107,6 +109,13 @@ impl<P: Provider + Send + Sync + std::fmt::Debug + 'static> Processors<P> {
Box::new(Erc721LegacyTransferProcessor) as Box<dyn EventProcessor<P>>,
],
),
(
ContractType::ERC1155,
vec![
Box::new(Erc1155TransferBatchProcessor) as Box<dyn EventProcessor<P>>,
Box::new(Erc1155TransferSingleProcessor) as Box<dyn EventProcessor<P>>,
],
),
(ContractType::UDC, vec![Box::new(ControllerProcessor) as Box<dyn EventProcessor<P>>]),
];

Expand Down
113 changes: 113 additions & 0 deletions crates/torii/indexer/src/processors/erc1155_transfer_batch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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::{self, TaskId, TaskPriority};

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

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

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

fn validate(&self, event: &Event) -> bool {
// key: [hash(TransferBatch), operator, from, to]
// data: [ids_len, ids[0].low, ids[0].high, ..., values_len, values[0].low, values[0].high,
// ...]
event.keys.len() == 4 && !event.data.is_empty()
}

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

fn task_identifier(&self, _event: &Event) -> TaskId {
task_manager::TASK_ID_SEQUENTIAL
}

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;
let from = event.keys[2];
let to = event.keys[3];

// ERC1155 TransferBatch event data format:
// - ids_len: felt (first element)
// - ids: U256[] (each element stored as 2 felts: [low, high])
// - values_len: felt
// - values: U256[] (each element stored as 2 felts: [low, high])
// Spec reference: https://eips.ethereum.org/EIPS/eip-1155#transferbatch
let ids_len = event.data[0].try_into().unwrap_or(0u64) as usize;
let mut current_idx = 1;

// First pass: read all token IDs
let mut token_ids = Vec::with_capacity(ids_len);
for _ in 0..ids_len {
if current_idx + 1 >= event.data.len() {
break;
}
let token_id = U256Cainome::cairo_deserialize(&event.data, current_idx)?;
token_ids.push(U256::from_words(token_id.low, token_id.high));
current_idx += 2;
}

// Move index to values array
let values_len = event.data[current_idx].try_into().unwrap_or(0u64) as usize;
current_idx += 1;

// Second pass: read and process amounts
for (idx, token_id) in token_ids.iter().enumerate() {
if idx >= values_len || current_idx + (idx * 2) + 1 >= event.data.len() {
break;
}

let amount = U256Cainome::cairo_deserialize(&event.data, current_idx + (idx * 2))?;
let amount = U256::from_words(amount.low, amount.high);

db.handle_nft_transfer(
token_address,
from,
to,
*token_id,
amount,
block_timestamp,
event_id,
block_number,
)
.await?;

debug!(
target: LOG_TARGET,
from = ?from,
to = ?to,
token_id = ?token_id,
amount = ?amount,
"ERC1155 TransferBatch"
);
}

Ok(())
}
}
76 changes: 76 additions & 0 deletions crates/torii/indexer/src/processors/erc1155_transfer_single.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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::{self, TaskId, TaskPriority};

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

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

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

fn validate(&self, event: &Event) -> bool {
// key: [hash(TransferSingle), operator, from, to]
// data: [id.low, id.high, value.low, value.high]
event.keys.len() == 4 && event.data.len() == 4
}

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

fn task_identifier(&self, _event: &Event) -> TaskId {
task_manager::TASK_ID_SEQUENTIAL
}

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;
let from = event.keys[2];
let to = event.keys[3];

let token_id = U256Cainome::cairo_deserialize(&event.data, 0)?;
let token_id = U256::from_words(token_id.low, token_id.high);

let amount = U256Cainome::cairo_deserialize(&event.data, 2)?;
let amount = U256::from_words(amount.low, amount.high);

db.handle_nft_transfer(
token_address,
from,
to,
token_id,
amount,
block_timestamp,
event_id,
block_number,
)
.await?;
debug!(target: LOG_TARGET, from = ?from, to = ?to, token_id = ?token_id, amount = ?amount, "ERC1155 TransferSingle");

Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ where
let token_id = U256Cainome::cairo_deserialize(&event.data, 2)?;
let token_id = U256::from_words(token_id.low, token_id.high);

db.handle_erc721_transfer(
db.handle_nft_transfer(
token_address,
from,
to,
token_id,
U256::from(1u8),
block_timestamp,
event_id,
block_number,
Expand Down
3 changes: 2 additions & 1 deletion crates/torii/indexer/src/processors/erc721_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ 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(
db.handle_nft_transfer(
token_address,
from,
to,
token_id,
U256::from(1u8),
block_timestamp,
event_id,
block_number,
Expand Down
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 @@ -10,6 +10,8 @@ use torii_sqlite::Sql;
use crate::task_manager::{TaskId, TaskPriority};

pub mod controller;
pub mod erc1155_transfer_batch;
pub mod erc1155_transfer_single;
pub mod erc20_legacy_transfer;
pub mod erc20_transfer;
pub mod erc721_legacy_transfer;
Expand Down
2 changes: 2 additions & 0 deletions crates/torii/migrations/20250212025607_transfers_event_id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE token_transfers
ADD COLUMN event_id TEXT;
24 changes: 13 additions & 11 deletions crates/torii/sqlite/src/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use starknet::providers::Provider;
use super::utils::{u256_to_sql_string, I256};
use super::{Sql, SQL_FELT_DELIMITER};
use crate::constants::TOKEN_TRANSFER_TABLE;
use crate::executor::erc::RegisterNftTokenQuery;
use crate::executor::{
ApplyBalanceDiffQuery, Argument, QueryMessage, QueryType, RegisterErc20TokenQuery,
RegisterErc721TokenQuery,
};
use crate::types::ContractType;
use crate::utils::{
Expand Down Expand Up @@ -79,12 +79,13 @@ impl Sql {
}

#[allow(clippy::too_many_arguments)]
pub async fn handle_erc721_transfer(
pub async fn handle_nft_transfer(
&mut self,
contract_address: Felt,
from_address: Felt,
to_address: Felt,
token_id: U256,
amount: U256,
block_timestamp: u64,
event_id: &str,
block_number: u64,
Expand All @@ -95,15 +96,14 @@ impl Sql {
let token_exists: bool = self.local_cache.contains_token_id(&token_id).await;

if !token_exists {
self.register_erc721_token_metadata(contract_address, &token_id, actual_token_id)
.await?;
self.register_nft_token_metadata(contract_address, &token_id, actual_token_id).await?;
}

self.store_erc_transfer_event(
contract_address,
from_address,
to_address,
U256::from(1u8),
amount,
&token_id,
block_timestamp,
event_id,
Expand All @@ -120,15 +120,15 @@ impl Sql {
);
let from_balance =
erc_cache.entry((ContractType::ERC721, from_balance_id)).or_default();
*from_balance -= I256::from(1u8);
*from_balance -= I256::from(amount);
}

if to_address != Felt::ZERO {
let to_balance_id =
format!("{}{SQL_FELT_DELIMITER}{}", felt_to_sql_string(&to_address), &token_id);
let to_balance =
erc_cache.entry((ContractType::ERC721, to_balance_id)).or_default();
*to_balance += I256::from(1u8);
*to_balance += I256::from(amount);
}
}

Expand Down Expand Up @@ -220,7 +220,7 @@ impl Sql {
Ok(())
}

async fn register_erc721_token_metadata(
async fn register_nft_token_metadata(
&mut self,
contract_address: Felt,
token_id: &str,
Expand All @@ -229,7 +229,7 @@ impl Sql {
self.executor.send(QueryMessage::new(
"".to_string(),
vec![],
QueryType::RegisterErc721Token(RegisterErc721TokenQuery {
QueryType::RegisterNftToken(RegisterNftTokenQuery {
token_id: token_id.to_string(),
contract_address,
actual_token_id,
Expand All @@ -256,20 +256,22 @@ impl Sql {
block_timestamp: u64,
event_id: &str,
) -> Result<()> {
let id = format!("{}:{}", event_id, token_id);
let insert_query = format!(
"INSERT INTO {TOKEN_TRANSFER_TABLE} (id, contract_address, from_address, to_address, \
amount, token_id, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
amount, token_id, event_id, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
);

self.executor.send(QueryMessage::new(
insert_query.to_string(),
vec![
Argument::String(event_id.to_string()),
Argument::String(id),
Argument::FieldElement(contract_address),
Argument::FieldElement(from),
Argument::FieldElement(to),
Argument::String(u256_to_sql_string(&amount)),
Argument::String(token_id.to_string()),
Argument::String(event_id.to_string()),
Argument::String(utc_dt_string_from_timestamp(block_timestamp)),
],
QueryType::TokenTransfer,
Expand Down
Loading

0 comments on commit b87a011

Please sign in to comment.