Skip to content

Commit

Permalink
Feat(Torii-core): Handle entity deletion events (#1439)
Browse files Browse the repository at this point in the history
* feat: add store_del_record

* feat: Improve fn delete_entity(), add event StoreDelRecordProcessor

* Fix: Uncommented database::set

* Feat: Improve fn process(), Improve fn delete_entity()

* Feat: add fn push_front(),fn build_delete_entity_queries_recursive()

* Feat: add fn push_front(),fn build_delete_entity_queries_recursive()

* Clean fn delete_entity()

* Add tests

* Improve current test on model_test, add delete test on model_test

* clean

* clean code

* remove fn delete from spawn-and-move

* manifest rollback

* enable tests

* Clean debug info
  • Loading branch information
gianalarcon authored Jan 25, 2024
1 parent 0736cdd commit 68e2199
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 61 deletions.
2 changes: 2 additions & 0 deletions bin/torii/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use tokio_stream::StreamExt;
use torii_core::engine::{Engine, EngineConfig, Processors};
use torii_core::processors::metadata_update::MetadataUpdateProcessor;
use torii_core::processors::register_model::RegisterModelProcessor;
use torii_core::processors::store_del_record::StoreDelRecordProcessor;
use torii_core::processors::store_set_record::StoreSetRecordProcessor;
use torii_core::processors::store_transaction::StoreTransactionProcessor;
use torii_core::simple_broker::SimpleBroker;
Expand Down Expand Up @@ -144,6 +145,7 @@ async fn main() -> anyhow::Result<()> {
Box::new(RegisterModelProcessor),
Box::new(StoreSetRecordProcessor),
Box::new(MetadataUpdateProcessor),
Box::new(StoreDelRecordProcessor),
],
transaction: vec![Box::new(StoreTransactionProcessor)],
..Processors::default()
Expand Down
21 changes: 11 additions & 10 deletions crates/dojo-core/src/world.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ trait IWorld<T> {

#[starknet::interface]
trait IUpgradeableWorld<T> {
fn upgrade(ref self: T, new_class_hash : ClassHash);
fn upgrade(ref self: T, new_class_hash: ClassHash);
}

#[starknet::interface]
Expand All @@ -70,15 +70,15 @@ mod world {
use starknet::{
get_caller_address, get_contract_address, get_tx_info,
contract_address::ContractAddressIntoFelt252, ClassHash, Zeroable, ContractAddress,
syscalls::{deploy_syscall, emit_event_syscall, replace_class_syscall}, SyscallResult, SyscallResultTrait,
SyscallResultTraitImpl
syscalls::{deploy_syscall, emit_event_syscall, replace_class_syscall}, SyscallResult,
SyscallResultTrait, SyscallResultTraitImpl
};

use dojo::database;
use dojo::database::index::WhereCondition;
use dojo::executor::{IExecutorDispatcher, IExecutorDispatcherTrait};
use dojo::world::{IWorldDispatcher, IWorld, IUpgradeableWorld};

use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait};

const NAME_ENTRYPOINT: felt252 =
Expand Down Expand Up @@ -112,7 +112,7 @@ mod world {
struct WorldUpgraded {
class_hash: ClassHash,
}

#[derive(Drop, starknet::Event)]
struct ContractDeployed {
salt: felt252,
Expand Down Expand Up @@ -641,17 +641,18 @@ mod world {
/// # Arguments
///
/// * `new_class_hash` - The new world class hash.
fn upgrade(ref self: ContractState, new_class_hash : ClassHash){
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
assert(new_class_hash.is_non_zero(), 'invalid class_hash');
assert(IWorld::is_owner(@self, get_tx_info().unbox().account_contract_address, WORLD), 'only owner can upgrade');
assert(
IWorld::is_owner(@self, get_tx_info().unbox().account_contract_address, WORLD),
'only owner can upgrade'
);

// upgrade to new_class_hash
replace_class_syscall(new_class_hash).unwrap();

// emit Upgrade Event
EventEmitter::emit(
ref self, WorldUpgraded {class_hash: new_class_hash }
);
EventEmitter::emit(ref self, WorldUpgraded { class_hash: new_class_hash });
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/torii/core/src/processors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ use crate::sql::Sql;

pub mod metadata_update;
pub mod register_model;
pub mod store_del_record;
pub mod store_set_record;
pub mod store_transaction;

const MODEL_INDEX: usize = 0;
const NUM_KEYS_INDEX: usize = 1;

#[async_trait]
pub trait EventProcessor<P>
where
Expand Down
58 changes: 58 additions & 0 deletions crates/torii/core/src/processors/store_del_record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::{Error, Ok, Result};
use async_trait::async_trait;
use dojo_world::contracts::model::ModelReader;
use dojo_world::contracts::world::WorldContractReader;
use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt};
use starknet::core::utils::parse_cairo_short_string;
use starknet::providers::Provider;
use tracing::info;

use super::EventProcessor;
use crate::processors::{MODEL_INDEX, NUM_KEYS_INDEX};
use crate::sql::Sql;

#[derive(Default)]
pub struct StoreDelRecordProcessor;

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

fn validate(&self, event: &Event) -> bool {
if event.keys.len() > 1 {
info!(
"invalid keys for event {}: {}",
<StoreDelRecordProcessor as EventProcessor<P>>::event_key(self),
<StoreDelRecordProcessor as EventProcessor<P>>::event_keys_as_string(self, event),
);
return false;
}
true
}

async fn process(
&self,
_world: &WorldContractReader<P>,
db: &mut Sql,
_block: &BlockWithTxs,
_transaction_receipt: &InvokeTransactionReceipt,
_event_id: &str,
event: &Event,
) -> Result<(), Error> {
let name = parse_cairo_short_string(&event.data[MODEL_INDEX])?;
info!("store delete record: {}", name);

let model = db.model(&name).await?;

let keys_start = NUM_KEYS_INDEX + 1;
let keys = event.data[keys_start..].to_vec();
let entity = model.schema().await?;
db.delete_entity(keys, entity).await?;
Ok(())
}
}
4 changes: 1 addition & 3 deletions crates/torii/core/src/processors/store_set_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ use starknet::providers::Provider;
use tracing::info;

use super::EventProcessor;
use crate::processors::{MODEL_INDEX, NUM_KEYS_INDEX};
use crate::sql::Sql;

#[derive(Default)]
pub struct StoreSetRecordProcessor;

const MODEL_INDEX: usize = 0;
const NUM_KEYS_INDEX: usize = 1;

#[async_trait]
impl<P> EventProcessor<P> for StoreSetRecordProcessor
where
Expand Down
4 changes: 4 additions & 0 deletions crates/torii/core/src/query_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ impl QueryQueue {
self.queue.push_back((statement.into(), arguments));
}

pub fn push_front<S: Into<String>>(&mut self, statement: S, arguments: Vec<Argument>) {
self.queue.push_front((statement.into(), arguments));
}

pub async fn execute_all(&mut self) -> sqlx::Result<u64> {
let mut total_affected = 0_u64;
let mut tx = self.pool.begin().await?;
Expand Down
45 changes: 39 additions & 6 deletions crates/torii/core/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,12 @@ impl Sql {
Ok(())
}

pub fn delete_entity(&mut self, model: String, key: FieldElement) {
let model = Argument::String(model);
let id = Argument::FieldElement(key);

self.query_queue.enqueue("DELETE FROM ? WHERE id = ?", vec![model, id]);
pub async fn delete_entity(&mut self, keys: Vec<FieldElement>, entity: Ty) -> Result<()> {
let entity_id = format!("{:#x}", poseidon_hash_many(&keys));
let path = vec![entity.name()];
self.build_delete_entity_queries_recursive(path, &entity_id, &entity);
self.query_queue.execute_all().await?;
Ok(())
}

pub fn set_metadata(&mut self, resource: &FieldElement, uri: &str) {
Expand Down Expand Up @@ -344,7 +345,6 @@ impl Sql {
if let Ty::Struct(_) = &member.ty {
let mut path_clone = path.clone();
path_clone.push(member.name.clone());

self.build_set_entity_queries_recursive(
path_clone, event_id, entity_id, &member.ty,
);
Expand All @@ -364,6 +364,39 @@ impl Sql {
}
}

fn build_delete_entity_queries_recursive(
&mut self,
path: Vec<String>,
entity_id: &str,
entity: &Ty,
) {
match entity {
Ty::Struct(s) => {
let table_id = path.join("$");
let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?");
self.query_queue
.push_front(statement, vec![Argument::String(entity_id.to_string())]);
for member in s.children.iter() {
if let Ty::Struct(_) = &member.ty {
let mut path_clone = path.clone();
path_clone.push(member.name.clone());
self.build_delete_entity_queries_recursive(
path_clone, entity_id, &member.ty,
);
}
}
}
Ty::Enum(e) => {
for child in e.options.iter() {
let mut path_clone = path.clone();
path_clone.push(child.name.clone());
self.build_delete_entity_queries_recursive(path_clone, entity_id, &child.ty);
}
}
_ => {}
}
}

fn build_model_query(&mut self, path: Vec<String>, model: &Ty, model_idx: i64) {
let table_id = path.join("$");
let mut indices = Vec::new();
Expand Down
2 changes: 0 additions & 2 deletions crates/torii/graphql/src/tests/entities_test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#[cfg(test)]
mod tests {

use anyhow::Result;
use async_graphql::dynamic::Schema;
use serde_json::Value;
Expand Down Expand Up @@ -235,7 +234,6 @@ mod tests {
let subrecord: Subrecord = serde_json::from_value(models[0].clone()).unwrap();
assert_eq!(&subrecord.__typename, "Subrecord");
assert_eq!(subrecord.subrecord_id, 1);

Ok(())
}
}
33 changes: 30 additions & 3 deletions crates/torii/graphql/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use tokio::sync::broadcast;
use tokio_stream::StreamExt;
use torii_core::engine::{Engine, EngineConfig, Processors};
use torii_core::processors::register_model::RegisterModelProcessor;
use torii_core::processors::store_del_record::StoreDelRecordProcessor;
use torii_core::processors::store_set_record::StoreSetRecordProcessor;
use torii_core::sql::Sql;

Expand Down Expand Up @@ -139,6 +140,14 @@ pub struct Subrecord {
pub entity: Option<Entity>,
}

#[derive(Deserialize, Debug, PartialEq)]
pub struct RecordSibling {
pub __typename: String,
pub record_id: u32,
pub random_u8: u8,
pub entity: Option<Entity>,
}

#[derive(Deserialize, Debug, PartialEq)]
pub struct Social {
pub name: String,
Expand Down Expand Up @@ -270,13 +279,14 @@ pub async fn spinup_types_test() -> Result<SqlitePool> {
let manifest =
Manifest::load_from_remote(&provider, migration.world_address().unwrap()).await.unwrap();

// Execute `create` and insert 10 records into storage
// Execute `create` and insert 11 records into storage
let records_contract =
manifest.contracts.iter().find(|contract| contract.name.eq("records")).unwrap();
let record_contract_address = records_contract.address.unwrap();
let InvokeTransactionResult { transaction_hash } = account
.execute(vec![Call {
calldata: vec![FieldElement::from_str("0xa").unwrap()],
to: records_contract.address.unwrap(),
to: record_contract_address,
selector: selector!("create"),
}])
.send()
Expand All @@ -285,13 +295,30 @@ pub async fn spinup_types_test() -> Result<SqlitePool> {

TransactionWaiter::new(transaction_hash, &provider).await?;

// Execute `delete` and delete Record with id 20
let InvokeTransactionResult { transaction_hash } = account
.execute(vec![Call {
calldata: vec![FieldElement::from_str("0x14").unwrap()],
to: record_contract_address,
selector: selector!("delete"),
}])
.send()
.await
.unwrap();

TransactionWaiter::new(transaction_hash, &provider).await?;

let (shutdown_tx, _) = broadcast::channel(1);
let mut engine = Engine::new(
world,
&mut db,
&provider,
Processors {
event: vec![Box::new(RegisterModelProcessor), Box::new(StoreSetRecordProcessor)],
event: vec![
Box::new(RegisterModelProcessor),
Box::new(StoreSetRecordProcessor),
Box::new(StoreDelRecordProcessor),
],
..Processors::default()
},
EngineConfig::default(),
Expand Down
Loading

0 comments on commit 68e2199

Please sign in to comment.