From 10c9bf74e5a4da779af64c7c06ba6fc343c1eafa Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 09:59:04 -0500 Subject: [PATCH 001/192] Use conditional compilation to support interrupt signals on windows (#1170) --- Cargo.lock | 30 +++++++++++++++--------------- crates/torii/server/src/cli.rs | 11 +++++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31a2f3d559..595b54e48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,7 +2255,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "starknet", @@ -2332,7 +2332,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "assert_fs", @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.6" +version = "0.3.7" dependencies = [ "crypto-bigint", "hex", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "assert_fs", @@ -4806,7 +4806,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.6" +version = "0.3.7" dependencies = [ "assert_matches", "clap", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "assert_matches", @@ -4855,7 +4855,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "assert_matches", @@ -7190,7 +7190,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "assert_fs", @@ -8221,7 +8221,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.6" +version = "0.3.7" dependencies = [ "async-trait", "camino", @@ -8247,7 +8247,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "async-trait", @@ -8282,7 +8282,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "async-graphql", @@ -8319,7 +8319,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.6" +version = "0.3.7" dependencies = [ "bytes", "dojo-types", @@ -8356,7 +8356,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "async-trait", diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index ca5d8f799b..4d59198a38 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -11,7 +11,10 @@ use sqlx::SqlitePool; use starknet::core::types::FieldElement; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; +#[cfg(target_family = "unix")] use tokio::signal::unix::{signal, SignalKind}; +#[cfg(target_family = "windows")] +use tokio::signal::windows::{ctrl_c, CtrlBreak}; use tokio::sync::broadcast; use tokio::sync::broadcast::Sender; use tokio_stream::StreamExt; @@ -77,9 +80,17 @@ async fn main() -> anyhow::Result<()> { // Setup cancellation for graceful shutdown let (shutdown_tx, _) = broadcast::channel(1); + + #[cfg(target_family = "unix")] let mut sigterm = signal(SignalKind::terminate())?; + #[cfg(target_family = "unix")] let mut sigint = signal(SignalKind::interrupt())?; + #[cfg(target_family = "windows")] + let mut sigterm = ctrl_c()?; + #[cfg(target_family = "windows")] + let mut sigint = CtrlBreak::new()?; + let database_url = format!("sqlite:{}", &args.database); let options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true); let pool = SqlitePoolOptions::new() From afa501c24c136ff654e353705ddd0061ae4827e2 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 13:43:29 -0500 Subject: [PATCH 002/192] Use http2 for proxy client (#1171) --- crates/torii/server/src/proxy.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/torii/server/src/proxy.rs b/crates/torii/server/src/proxy.rs index 1020397e53..1a5bfbb15c 100644 --- a/crates/torii/server/src/proxy.rs +++ b/crates/torii/server/src/proxy.rs @@ -9,7 +9,7 @@ use hyper::client::connect::dns::GaiResolver; use hyper::client::HttpConnector; use hyper::server::conn::AddrStream; use hyper::service::make_service_fn; -use hyper::{Body, Request, Response, Server, StatusCode}; +use hyper::{Body, Client, Request, Response, Server, StatusCode}; use hyper_reverse_proxy::ReverseProxy; use serde_json::json; use tokio::sync::RwLock; @@ -38,7 +38,9 @@ const DEFAULT_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60); lazy_static::lazy_static! { static ref PROXY_CLIENT: ReverseProxy> = { ReverseProxy::new( - hyper::Client::new(), + Client::builder() + .http2_only(true) + .build_http(), ) }; } From 4dd21678ad4417aefc4d852199c68ac5e52b49af Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 9 Nov 2023 11:32:30 -0800 Subject: [PATCH 003/192] Fix torii commit updates before publish (#1172) * Fix torii commit updates before publish * use serial_test --- Cargo.lock | 26 ++++++ crates/torii/core/src/sql.rs | 10 ++- crates/torii/graphql/Cargo.toml | 1 + .../graphql/src/tests/subscription_test.rs | 83 +++++++++++++------ 4 files changed, 92 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 595b54e48d..dffc16a6a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6979,6 +6979,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -8300,6 +8325,7 @@ dependencies = [ "scarb-ui", "serde", "serde_json", + "serial_test", "sozo", "sqlx", "starknet", diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index ced2f47a2c..f62c977d16 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -102,10 +102,11 @@ impl Sql { .fetch_one(&self.pool) .await?; - SimpleBroker::publish(model_registered); - let mut model_idx = 0_i64; self.build_register_queries_recursive(&model, vec![model.name()], &mut model_idx); + self.query_queue.execute_all().await?; + + SimpleBroker::publish(model_registered); Ok(()) } @@ -149,10 +150,11 @@ impl Sql { .fetch_one(&self.pool) .await?; - SimpleBroker::publish(entity_updated); - let path = vec![entity.name()]; self.build_set_entity_queries_recursive(path, event_id, &entity_id, &entity); + self.query_queue.execute_all().await?; + + SimpleBroker::publish(entity_updated); Ok(()) } diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml index 970ede7b2d..242bf96213 100644 --- a/crates/torii/graphql/Cargo.toml +++ b/crates/torii/graphql/Cargo.toml @@ -42,3 +42,4 @@ scarb.workspace = true sozo = { path = "../../sozo" } starknet-crypto.workspace = true starknet.workspace = true +serial_test = "2.0.0" diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index e1249f071a..e126404119 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -6,20 +6,18 @@ mod tests { use async_graphql::value; use dojo_types::primitive::Primitive; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; + use serial_test::serial; use sqlx::SqlitePool; use starknet::core::types::Event; use starknet_crypto::{poseidon_hash_many, FieldElement}; use tokio::sync::mpsc; - // use tokio_util::sync::CancellationToken; use torii_core::sql::Sql; use crate::tests::{model_fixtures, run_graphql_subscription}; #[sqlx::test(migrations = "../migrations")] + #[serial] async fn test_entity_subscription(pool: SqlitePool) { - // Sleep in order to run this test in a single thread - tokio::time::sleep(Duration::from_secs(1)).await; - // let cts = CancellationToken::new(); let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); model_fixtures(&mut db).await; @@ -28,7 +26,16 @@ mod tests { let entity_id = format!("{:#x}", poseidon_hash_many(&key)); let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); let expected_value: async_graphql::Value = value!({ - "entityUpdated": { "id": entity_id, "keys":vec![keys_str], "model_names": "Moves" } + "entityUpdated": { + "id": entity_id, + "keys":vec![keys_str], + "model_names": "Moves", + "models" : [{ + "player": format!("{:#x}", FieldElement::ONE), + "remaining": 10, + "last_direction": "Left" + }] + } }); let (tx, mut rx) = mpsc::channel(10); @@ -80,12 +87,20 @@ mod tests { // 2. The subscription is executed and it is listeing, waiting for publish() to be executed let response_value = run_graphql_subscription( &pool, - r#" - subscription { - entityUpdated { - id, keys, model_names - } - }"#, + r#"subscription { + entityUpdated { + id + keys + model_names + models { + ... on Moves { + player + remaining + last_direction + } + } + } + }"#, ) .await; // 4. The subcription has received the message from publish() @@ -95,10 +110,8 @@ mod tests { } #[sqlx::test(migrations = "../migrations")] + #[serial] async fn test_entity_subscription_with_id(pool: SqlitePool) { - // Sleep in order to run this test in a single thread - tokio::time::sleep(Duration::from_secs(1)).await; - // let cts = CancellationToken::new(); let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); model_fixtures(&mut db).await; @@ -107,7 +120,16 @@ mod tests { let entity_id = format!("{:#x}", poseidon_hash_many(&key)); let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); let expected_value: async_graphql::Value = value!({ - "entityUpdated": { "id": entity_id, "keys":vec![keys_str], "model_names": "Moves" } + "entityUpdated": { + "id": entity_id, + "keys":vec![keys_str], + "model_names": "Moves", + "models" : [{ + "player": format!("{:#x}", FieldElement::ONE), + "remaining": 10, + "last_direction": "Left" + }] + } }); let (tx, mut rx) = mpsc::channel(10); @@ -125,6 +147,11 @@ mod tests { key: true, ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), }, + Member { + name: "remaining".to_string(), + key: false, + ty: Ty::Primitive(Primitive::U8(Some(10))), + }, Member { name: "last_direction".to_string(), key: false, @@ -154,12 +181,20 @@ mod tests { // 2. The subscription is executed and it is listeing, waiting for publish() to be executed let response_value = run_graphql_subscription( &pool, - r#" - subscription { - entityUpdated(id: "0x579e8877c7755365d5ec1ec7d3a94a457eff5d1f40482bbe9729c064cdead2") { - id, keys, model_names - } - }"#, + r#"subscription { + entityUpdated(id: "0x579e8877c7755365d5ec1ec7d3a94a457eff5d1f40482bbe9729c064cdead2") { + id + keys + model_names + models { + ... on Moves { + player + remaining + last_direction + } + } + } + }"#, ) .await; // 4. The subscription has received the message from publish() @@ -169,6 +204,7 @@ mod tests { } #[sqlx::test(migrations = "../migrations")] + #[serial] async fn test_model_subscription(pool: SqlitePool) { let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); // 0. Preprocess model value @@ -217,10 +253,8 @@ mod tests { } #[sqlx::test(migrations = "../migrations")] + #[serial] async fn test_model_subscription_with_id(pool: SqlitePool) { - // Sleep in order to run this test at the end in a single thread - tokio::time::sleep(Duration::from_secs(2)).await; - let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); // 0. Preprocess model value let name = "Test".to_string(); @@ -267,6 +301,7 @@ mod tests { } #[sqlx::test(migrations = "../migrations")] + #[serial] async fn test_event_emitted(pool: SqlitePool) { let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); From 87a232a300f8c26ae311de8f4de5ef57e0bdc8ac Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 14:33:38 -0500 Subject: [PATCH 004/192] Prepare v0.3.8 --- Cargo.toml | 2 +- crates/dojo-core/Scarb.toml | 4 ++-- crates/dojo-defi/Scarb.toml | 2 +- crates/dojo-erc/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- .../dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml | 2 +- crates/dojo-physics/Scarb.toml | 2 +- crates/dojo-physics/examples/projectile/Scarb.toml | 2 +- crates/dojo-primitives/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- scripts/prepare_release.sh | 2 ++ 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9bbf90871b..076900495f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.7" +version = "0.3.8" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index fc13fc65da..c6d76eefc2 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,8 +2,8 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.7" +version = "0.3.8" [dependencies] -dojo_plugin = "0.3.7" +dojo_plugin = "0.3.8" starknet = "2.3.1" diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml index 35df95ac43..31486de60b 100644 --- a/crates/dojo-defi/Scarb.toml +++ b/crates/dojo-defi/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of a defi primitives for the Dojo framework" name = "dojo_defi" -version = "0.3.7" +version = "0.3.8" [lib] diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml index c6d0cf2a67..5b64d7c955 100644 --- a/crates/dojo-erc/Scarb.toml +++ b/crates/dojo-erc/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of ERC standards for the Dojo framework" name = "dojo_erc" -version = "0.3.7" +version = "0.3.8" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 5978d41efe..5f47925c97 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.7" +version = "0.3.8" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index a158dbb8b3..f5e6e6efd9 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.7" +version = "0.3.8" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-physics/Scarb.toml b/crates/dojo-physics/Scarb.toml index ff0587343e..afef609f91 100644 --- a/crates/dojo-physics/Scarb.toml +++ b/crates/dojo-physics/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.0.0-rc4" description = "Implementations of a physics library for the Dojo framework" name = "dojo_physics" -version = "0.3.7" +version = "0.3.8" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-physics/examples/projectile/Scarb.toml b/crates/dojo-physics/examples/projectile/Scarb.toml index 14bf9a7760..783790e547 100644 --- a/crates/dojo-physics/examples/projectile/Scarb.toml +++ b/crates/dojo-physics/examples/projectile/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "projectile" -version = "0.3.7" +version = "0.3.8" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-primitives/Scarb.toml b/crates/dojo-primitives/Scarb.toml index 251ab2d5fa..a016204add 100644 --- a/crates/dojo-primitives/Scarb.toml +++ b/crates/dojo-primitives/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of common primitives for the Dojo games" name = "dojo_primitives" -version = "0.3.7" +version = "0.3.8" [lib] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 43d4bf712b..5236a33cee 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.7" +version = "0.3.8" [cairo] sierra-replace-ids = true diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index f1a45d9af6..b19a6abfbc 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -6,3 +6,5 @@ next_version=$1 find . -type f -name "*.toml" -exec sed -i "" "s/version = \"$prev_version\"/version = \"$next_version\"/g" {} \; find . -type f -name "*.toml" -exec sed -i "" "s/dojo_plugin = \"$prev_version\"/dojo_plugin = \"$next_version\"/g" {} \; + +git commit -am "Prepare v$1" \ No newline at end of file From 3937942ee8ec8ec25c9d9bc7d5da31e63b53de51 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 15:44:37 -0500 Subject: [PATCH 005/192] Use separate grpc/graphql proxy clients (#1173) --- Cargo.lock | 30 +++++++++++++++--------------- crates/torii/server/src/proxy.rs | 13 ++++++++++--- scripts/clippy.sh | 1 - 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dffc16a6a7..96bfe5b7a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,7 +2255,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "starknet", @@ -2332,7 +2332,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "assert_fs", @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.7" +version = "0.3.8" dependencies = [ "crypto-bigint", "hex", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "assert_fs", @@ -4806,7 +4806,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.7" +version = "0.3.8" dependencies = [ "assert_matches", "clap", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "assert_matches", @@ -4855,7 +4855,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "assert_matches", @@ -7215,7 +7215,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "assert_fs", @@ -8246,7 +8246,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.7" +version = "0.3.8" dependencies = [ "async-trait", "camino", @@ -8272,7 +8272,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "async-trait", @@ -8307,7 +8307,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "async-graphql", @@ -8345,7 +8345,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.7" +version = "0.3.8" dependencies = [ "bytes", "dojo-types", @@ -8382,7 +8382,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "async-trait", diff --git a/crates/torii/server/src/proxy.rs b/crates/torii/server/src/proxy.rs index 1a5bfbb15c..4d34fafa35 100644 --- a/crates/torii/server/src/proxy.rs +++ b/crates/torii/server/src/proxy.rs @@ -36,7 +36,14 @@ const DEFAULT_EXPOSED_HEADERS: [&str; 3] = const DEFAULT_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60); lazy_static::lazy_static! { - static ref PROXY_CLIENT: ReverseProxy> = { + static ref GRAPHQL_PROXY_CLIENT: ReverseProxy> = { + ReverseProxy::new( + Client::builder() + .build_http(), + ) + }; + + static ref GRPC_PROXY_CLIENT: ReverseProxy> = { ReverseProxy::new( Client::builder() .http2_only(true) @@ -134,7 +141,7 @@ async fn handle( if req.uri().path().starts_with("/graphql") { if let Some(graphql_addr) = graphql_addr { let graphql_addr = format!("http://{}", graphql_addr); - return match PROXY_CLIENT.call(client_ip, &graphql_addr, req).await { + return match GRAPHQL_PROXY_CLIENT.call(client_ip, &graphql_addr, req).await { Ok(response) => Ok(response), Err(_error) => { error!("{:?}", _error); @@ -156,7 +163,7 @@ async fn handle( if content_type.to_str().unwrap().starts_with("application/grpc") { if let Some(grpc_addr) = grpc_addr { let grpc_addr = format!("http://{}", grpc_addr); - return match PROXY_CLIENT.call(client_ip, &grpc_addr, req).await { + return match GRPC_PROXY_CLIENT.call(client_ip, &grpc_addr, req).await { Ok(response) => Ok(response), Err(_error) => { error!("{:?}", _error); diff --git a/scripts/clippy.sh b/scripts/clippy.sh index d9c7098e40..1c3e4c5960 100755 --- a/scripts/clippy.sh +++ b/scripts/clippy.sh @@ -7,4 +7,3 @@ run_clippy() { } run_clippy - From 015e1d2027cef343aca6655f95cd6668305ca272 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 15:45:00 -0500 Subject: [PATCH 006/192] Prepare v0.3.9 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.toml | 4 +-- crates/dojo-defi/Scarb.toml | 2 +- crates/dojo-erc/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- crates/dojo-physics/Scarb.toml | 2 +- .../examples/projectile/Scarb.toml | 2 +- crates/dojo-primitives/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96bfe5b7a3..ec6ce14341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,7 +2255,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "starknet", @@ -2332,7 +2332,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "assert_fs", @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.8" +version = "0.3.9" dependencies = [ "crypto-bigint", "hex", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "assert_fs", @@ -4806,7 +4806,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.8" +version = "0.3.9" dependencies = [ "assert_matches", "clap", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "assert_matches", @@ -4855,7 +4855,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "assert_matches", @@ -7215,7 +7215,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "assert_fs", @@ -8246,7 +8246,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.8" +version = "0.3.9" dependencies = [ "async-trait", "camino", @@ -8272,7 +8272,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "async-trait", @@ -8307,7 +8307,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "async-graphql", @@ -8345,7 +8345,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.8" +version = "0.3.9" dependencies = [ "bytes", "dojo-types", @@ -8382,7 +8382,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 076900495f..9421d85f23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.8" +version = "0.3.9" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index c6d76eefc2..5120cf930b 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,8 +2,8 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.8" +version = "0.3.9" [dependencies] -dojo_plugin = "0.3.8" +dojo_plugin = "0.3.9" starknet = "2.3.1" diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml index 31486de60b..7591c99f7d 100644 --- a/crates/dojo-defi/Scarb.toml +++ b/crates/dojo-defi/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of a defi primitives for the Dojo framework" name = "dojo_defi" -version = "0.3.8" +version = "0.3.9" [lib] diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml index 5b64d7c955..c1f0acc29d 100644 --- a/crates/dojo-erc/Scarb.toml +++ b/crates/dojo-erc/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of ERC standards for the Dojo framework" name = "dojo_erc" -version = "0.3.8" +version = "0.3.9" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 5f47925c97..8aff6b7e6b 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.8" +version = "0.3.9" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index f5e6e6efd9..08f50c9eb1 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.8" +version = "0.3.9" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-physics/Scarb.toml b/crates/dojo-physics/Scarb.toml index afef609f91..db309f8a7c 100644 --- a/crates/dojo-physics/Scarb.toml +++ b/crates/dojo-physics/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.0.0-rc4" description = "Implementations of a physics library for the Dojo framework" name = "dojo_physics" -version = "0.3.8" +version = "0.3.9" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-physics/examples/projectile/Scarb.toml b/crates/dojo-physics/examples/projectile/Scarb.toml index 783790e547..b5b97ded16 100644 --- a/crates/dojo-physics/examples/projectile/Scarb.toml +++ b/crates/dojo-physics/examples/projectile/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "projectile" -version = "0.3.8" +version = "0.3.9" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-primitives/Scarb.toml b/crates/dojo-primitives/Scarb.toml index a016204add..b224886994 100644 --- a/crates/dojo-primitives/Scarb.toml +++ b/crates/dojo-primitives/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of common primitives for the Dojo games" name = "dojo_primitives" -version = "0.3.8" +version = "0.3.9" [lib] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 5236a33cee..7bc96d9411 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.8" +version = "0.3.9" [cairo] sierra-replace-ids = true From b0fb6846dc105b40d44ebc26d35d709fe249efd2 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 16:17:01 -0500 Subject: [PATCH 007/192] Fix windows sigterm handling (#1174) --- crates/torii/server/src/cli.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index 4d59198a38..23078cc438 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -11,10 +11,9 @@ use sqlx::SqlitePool; use starknet::core::types::FieldElement; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; +use tokio::signal::ctrl_c; #[cfg(target_family = "unix")] use tokio::signal::unix::{signal, SignalKind}; -#[cfg(target_family = "windows")] -use tokio::signal::windows::{ctrl_c, CtrlBreak}; use tokio::sync::broadcast; use tokio::sync::broadcast::Sender; use tokio_stream::StreamExt; @@ -81,15 +80,12 @@ async fn main() -> anyhow::Result<()> { // Setup cancellation for graceful shutdown let (shutdown_tx, _) = broadcast::channel(1); + let sigint = ctrl_c(); + #[cfg(target_family = "unix")] let mut sigterm = signal(SignalKind::terminate())?; - #[cfg(target_family = "unix")] - let mut sigint = signal(SignalKind::interrupt())?; - - #[cfg(target_family = "windows")] - let mut sigterm = ctrl_c()?; #[cfg(target_family = "windows")] - let mut sigint = CtrlBreak::new()?; + let (_, sigterm) = futures::channel::mpsc::unbounded::<()>(); let database_url = format!("sqlite:{}", &args.database); let options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true); @@ -157,7 +153,7 @@ async fn main() -> anyhow::Result<()> { _ = sigterm.recv() => { let _ = shutdown_tx.send(()); } - _ = sigint.recv() => { + _ = sigint => { let _ = shutdown_tx.send(()); } From 783c98a4aca4439d351f03fa3c127c847662d7eb Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 16:42:04 -0500 Subject: [PATCH 008/192] Add ci step to ensure buildable on windows (#1175) --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8f8e294b7..3d415ad857 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,20 @@ jobs: run: | cargo build -r --target wasm32-unknown-unknown -p torii-client + ensure-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + target: x86_64-pc-windows-msvc + - uses: Swatinem/rust-cache@v2 + - uses: arduino/setup-protoc@v2 + - name: "Ensure buildable on windows" + run: | + cargo build --target x86_64-pc-windows-msvc --bins + # cairofmt: # runs-on: ubuntu-latest # steps: From 748f5dce0bcce3fb3045eccac877954f867c521b Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 9 Nov 2023 16:43:07 -0500 Subject: [PATCH 009/192] Prepare v0.3.10 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.toml | 4 +-- crates/dojo-defi/Scarb.toml | 2 +- crates/dojo-erc/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- crates/dojo-physics/Scarb.toml | 2 +- .../examples/projectile/Scarb.toml | 2 +- crates/dojo-primitives/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- scripts/clippy.sh | 2 -- scripts/prepare_release.sh | 4 ++- 13 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec6ce14341..8de278bb93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,7 +2255,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "starknet", @@ -2332,7 +2332,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "assert_fs", @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.9" +version = "0.3.10" dependencies = [ "crypto-bigint", "hex", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "assert_fs", @@ -4806,7 +4806,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.9" +version = "0.3.10" dependencies = [ "assert_matches", "clap", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "assert_matches", @@ -4855,7 +4855,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "assert_matches", @@ -7215,7 +7215,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "assert_fs", @@ -8246,7 +8246,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.9" +version = "0.3.10" dependencies = [ "async-trait", "camino", @@ -8272,7 +8272,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "async-trait", @@ -8307,7 +8307,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "async-graphql", @@ -8345,7 +8345,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.9" +version = "0.3.10" dependencies = [ "bytes", "dojo-types", @@ -8382,7 +8382,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 9421d85f23..87147f1585 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.9" +version = "0.3.10" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 5120cf930b..fa14470e63 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,8 +2,8 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.9" +version = "0.3.10" [dependencies] -dojo_plugin = "0.3.9" +dojo_plugin = "0.3.10" starknet = "2.3.1" diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml index 7591c99f7d..bef8294936 100644 --- a/crates/dojo-defi/Scarb.toml +++ b/crates/dojo-defi/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of a defi primitives for the Dojo framework" name = "dojo_defi" -version = "0.3.9" +version = "0.3.10" [lib] diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml index c1f0acc29d..479680ec37 100644 --- a/crates/dojo-erc/Scarb.toml +++ b/crates/dojo-erc/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of ERC standards for the Dojo framework" name = "dojo_erc" -version = "0.3.9" +version = "0.3.10" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 8aff6b7e6b..09626b768f 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.9" +version = "0.3.10" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 08f50c9eb1..57472785e8 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.9" +version = "0.3.10" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-physics/Scarb.toml b/crates/dojo-physics/Scarb.toml index db309f8a7c..a94969353b 100644 --- a/crates/dojo-physics/Scarb.toml +++ b/crates/dojo-physics/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.0.0-rc4" description = "Implementations of a physics library for the Dojo framework" name = "dojo_physics" -version = "0.3.9" +version = "0.3.10" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-physics/examples/projectile/Scarb.toml b/crates/dojo-physics/examples/projectile/Scarb.toml index b5b97ded16..adc73c8825 100644 --- a/crates/dojo-physics/examples/projectile/Scarb.toml +++ b/crates/dojo-physics/examples/projectile/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "projectile" -version = "0.3.9" +version = "0.3.10" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-primitives/Scarb.toml b/crates/dojo-primitives/Scarb.toml index b224886994..aadd64d41e 100644 --- a/crates/dojo-primitives/Scarb.toml +++ b/crates/dojo-primitives/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of common primitives for the Dojo games" name = "dojo_primitives" -version = "0.3.9" +version = "0.3.10" [lib] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 7bc96d9411..ce7d799371 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.9" +version = "0.3.10" [cairo] sierra-replace-ids = true diff --git a/scripts/clippy.sh b/scripts/clippy.sh index 1c3e4c5960..8c01e87ecb 100755 --- a/scripts/clippy.sh +++ b/scripts/clippy.sh @@ -1,7 +1,5 @@ #!/bin/bash -#!/bin/bash - run_clippy() { cargo clippy --all-targets --all-features "$@" -- -D warnings -D future-incompatible -D nonstandard-style -D rust-2018-idioms -D unused } diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index b19a6abfbc..95cfb2d1a0 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -7,4 +7,6 @@ next_version=$1 find . -type f -name "*.toml" -exec sed -i "" "s/version = \"$prev_version\"/version = \"$next_version\"/g" {} \; find . -type f -name "*.toml" -exec sed -i "" "s/dojo_plugin = \"$prev_version\"/dojo_plugin = \"$next_version\"/g" {} \; -git commit -am "Prepare v$1" \ No newline at end of file +scripts/clippy.sh + +git commit -am "Prepare v$1" From be1676252c41f8188465741edcbcb381d2c12080 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 10 Nov 2023 10:16:26 -0500 Subject: [PATCH 010/192] Use cross platform interrupt handler (#1178) --- Cargo.lock | 23 +++++++++++++++++++++++ crates/torii/server/Cargo.toml | 2 ++ crates/torii/server/src/cli.rs | 21 +++++---------------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8de278bb93..c7d80ecd4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1934,6 +1934,16 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctrlc" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +dependencies = [ + "nix", + "windows-sys 0.48.0", +] + [[package]] name = "darling" version = "0.14.4" @@ -5253,6 +5263,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -8390,9 +8411,11 @@ dependencies = [ "camino", "chrono", "clap", + "ctrlc", "dojo-types", "dojo-world", "either", + "futures", "http", "http-body", "hyper", diff --git a/crates/torii/server/Cargo.toml b/crates/torii/server/Cargo.toml index 0b6acdfc14..147d3d330f 100644 --- a/crates/torii/server/Cargo.toml +++ b/crates/torii/server/Cargo.toml @@ -12,9 +12,11 @@ base64 = "0.21.2" camino.workspace = true chrono.workspace = true clap.workspace = true +ctrlc = { version = "3.4", features = [ "termination" ] } dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world" } either = "1.9.0" +futures.workspace = true http = "0.2.9" http-body = "0.4.5" hyper-reverse-proxy = { git = "https://github.com/tarrencev/hyper-reverse-proxy" } diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index 23078cc438..a86e95fda9 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -11,9 +11,6 @@ use sqlx::SqlitePool; use starknet::core::types::FieldElement; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; -use tokio::signal::ctrl_c; -#[cfg(target_family = "unix")] -use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::broadcast; use tokio::sync::broadcast::Sender; use tokio_stream::StreamExt; @@ -80,12 +77,11 @@ async fn main() -> anyhow::Result<()> { // Setup cancellation for graceful shutdown let (shutdown_tx, _) = broadcast::channel(1); - let sigint = ctrl_c(); - - #[cfg(target_family = "unix")] - let mut sigterm = signal(SignalKind::terminate())?; - #[cfg(target_family = "windows")] - let (_, sigterm) = futures::channel::mpsc::unbounded::<()>(); + let shutdown_tx_clone = shutdown_tx.clone(); + ctrlc::set_handler(move || { + let _ = shutdown_tx_clone.send(()); + }) + .expect("Error setting Ctrl-C handler"); let database_url = format!("sqlite:{}", &args.database); let options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true); @@ -150,13 +146,6 @@ async fn main() -> anyhow::Result<()> { info!("Graphql playground: {}\n", format!("http://{}/graphql", addr)); tokio::select! { - _ = sigterm.recv() => { - let _ = shutdown_tx.send(()); - } - _ = sigint => { - let _ = shutdown_tx.send(()); - } - _ = engine.start() => {}, _ = proxy_server.start(shutdown_tx.subscribe()) => {}, _ = graphql_server => {}, From 104afa0a59726921236b71285010cd1086dc4e05 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 10 Nov 2023 15:23:25 -0500 Subject: [PATCH 011/192] Use CI build artifacts for docker container to improve build performance (#1179) --- .github/workflows/release.yml | 42 ++++++++++++++++++++++++++++++----- Dockerfile | 39 ++++++++------------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7af2639904..022c4acdc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -217,10 +217,39 @@ jobs: const prunePrereleases = require('./.github/scripts/prune-prereleases.js') await prunePrereleases({github, context}) - docker-build-and-push: - name: Build and push Docker image + docker-build-and-push-linux-arm64: + name: Build and push linux-arm64 docker image runs-on: ubuntu-latest-4-cores - needs: prepare + needs: [prepare, release] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push docker image + uses: docker/build-push-action@v4 + with: + push: true + tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} + platforms: linux/arm64 + target: arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + + docker-build-and-push-linux-amd64: + name: Build and push linux-amd64 docker image + runs-on: ubuntu-latest-4-cores + needs: [prepare, release] steps: - name: Checkout repository @@ -236,9 +265,12 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image + - name: Build and push docker image uses: docker/build-push-action@v4 with: push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 + target: amd64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index b2a0054d70..685091562d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,16 @@ -FROM rust:slim-buster as builder -RUN apt-get -y update; \ - apt-get install -y --no-install-recommends \ - curl libssl-dev make clang-11 g++ llvm protobuf-compiler \ - pkg-config libz-dev zstd git; \ - apt-get autoremove -y; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/* +FROM debian:buster-slim as base -WORKDIR /dojo -COPY . . -RUN cargo build --release --config net.git-fetch-with-cli=true - -FROM debian:buster-slim LABEL description="Dojo is a provable game engine and toolchain for building onchain games and autonomous worlds with Cairo" \ authors="tarrence " \ source="https://github.com/dojoengine/dojo" \ documentation="https://book.dojoengine.org/" -RUN apt-get -y update; \ - apt-get install -y --no-install-recommends \ - curl; \ - apt-get autoremove -y; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/* - -# Set an environment variable for the port -ENV HEALTH_CHECK_PORT=5050 - -HEALTHCHECK --interval=3s --timeout=5s --start-period=1s --retries=5 \ - CMD curl --request POST \ - --header "Content-Type: application/json" \ - --data '{"jsonrpc": "2.0","method": "starknet_chainId","id":1}' http://localhost:${HEALTH_CHECK_PORT} || exit 1 +FROM base as amd64 +COPY target/x86_64-unknown-linux-gnu/release/katana /usr/local/bin/katana +COPY target/x86_64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo +COPY target/x86_64-unknown-linux-gnu/release/torii /usr/local/bin/torii -COPY --from=builder /dojo/target/release/katana /usr/local/bin/katana -COPY --from=builder /dojo/target/release/sozo /usr/local/bin/sozo -COPY --from=builder /dojo/target/release/torii /usr/local/bin/torii +FROM base as arm64 +COPY target/aarch64-unknown-linux-gnu/release/katana /usr/local/bin/katana +COPY target/aarch64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo +COPY target/aarch64-unknown-linux-gnu/release/torii /usr/local/bin/torii From b430bde5a2a59e79a8a2fcd2669cfbe4e596b66a Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 15 Nov 2023 19:03:11 +0300 Subject: [PATCH 012/192] fix(sozo): change priority order when fetching rpc url (#1188) * fix(sozo): change priority order when fetching rpc url * add tests --- crates/sozo/src/commands/options/starknet.rs | 79 +++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/crates/sozo/src/commands/options/starknet.rs b/crates/sozo/src/commands/options/starknet.rs index 10f151ee15..9f024f20b3 100644 --- a/crates/sozo/src/commands/options/starknet.rs +++ b/crates/sozo/src/commands/options/starknet.rs @@ -1,17 +1,19 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::Args; use dojo_world::metadata::Environment; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use url::Url; +const STARKNET_RPC_URL_ENV_VAR: &str = "STARKNET_RPC_URL"; + #[derive(Debug, Args)] #[command(next_help_heading = "Starknet options")] pub struct StarknetOptions { - #[arg(long, env = "STARKNET_RPC_URL", default_value = "http://localhost:5050")] + #[arg(long, env = STARKNET_RPC_URL_ENV_VAR, default_value = "http://localhost:5050")] #[arg(value_name = "URL")] #[arg(help = "The Starknet RPC endpoint.")] - pub rpc_url: Option, + pub rpc_url: Url, } impl StarknetOptions { @@ -19,25 +21,62 @@ impl StarknetOptions { &self, env_metadata: Option<&Environment>, ) -> Result> { - let url = if let Some(url) = self.rpc_url.clone() { - Some(url) - } else if let Some(url) = - env_metadata - .and_then(|env| env.rpc_url()) - .or(std::env::var("STARKNET_RPC_URL").ok().as_deref()) - { - Some(Url::parse(url)?) + Ok(JsonRpcClient::new(HttpTransport::new(self.url(env_metadata)?))) + } + + // we dont check the env var because that would be handled by `clap` + fn url(&self, env_metadata: Option<&Environment>) -> Result { + Ok(if let Some(url) = env_metadata.and_then(|env| env.rpc_url()) { + Url::parse(url)? } else { - None + self.rpc_url.clone() + }) + } +} + +#[cfg(test)] +mod tests { + use clap::Parser; + + use super::StarknetOptions; + use crate::commands::options::starknet::STARKNET_RPC_URL_ENV_VAR; + + const ENV_RPC: &str = "http://localhost:7474/"; + const METADATA_RPC: &str = "http://localhost:6060/"; + + #[derive(clap::Parser)] + struct Command { + #[clap(flatten)] + options: StarknetOptions, + } + + #[test] + fn url_exist_in_env_metadata_but_env_doesnt() { + let env_metadata = dojo_world::metadata::Environment { + rpc_url: Some(METADATA_RPC.into()), + ..Default::default() }; - if let Some(url) = url { - Ok(JsonRpcClient::new(HttpTransport::new(url))) - } else { - Err(anyhow!( - "Could not find Starknet RPC endpoint. Please specify it with --rpc-url or in the \ - environment config." - )) - } + let cmd = Command::parse_from([""]); + assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), METADATA_RPC); + } + + #[test] + fn url_doesnt_exist_in_env_metadata_but_env_does() { + std::env::set_var(STARKNET_RPC_URL_ENV_VAR, ENV_RPC); + let env_metadata = dojo_world::metadata::Environment::default(); + let cmd = Command::parse_from([""]); + assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_RPC); + } + + #[test] + fn exists_in_both() { + std::env::set_var(STARKNET_RPC_URL_ENV_VAR, ENV_RPC); + let env_metadata = dojo_world::metadata::Environment { + rpc_url: Some(METADATA_RPC.into()), + ..Default::default() + }; + let cmd = Command::parse_from([""]); + assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), METADATA_RPC); } } From 94d072bbe37b8e54f2400dc76633003bcd1e0969 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 16 Nov 2023 01:01:06 +0300 Subject: [PATCH 013/192] fix(dojo-lang): wrong type name when deriving Introspect for enum (#1190) --- crates/dojo-lang/src/introspect.rs | 3 ++- crates/dojo-lang/src/plugin_test_data/introspect | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/dojo-lang/src/introspect.rs b/crates/dojo-lang/src/introspect.rs index 7300157623..8cd46e1967 100644 --- a/crates/dojo-lang/src/introspect.rs +++ b/crates/dojo-lang/src/introspect.rs @@ -191,7 +191,7 @@ pub fn handle_introspect_enum( " dojo::database::schema::Ty::Enum( dojo::database::schema::Enum {{ - name: 'Direction', + name: '{name}', attrs: array![].span(), children: array![ {} @@ -200,6 +200,7 @@ pub fn handle_introspect_enum( )", arms_ty.join(",\n") ); + // Enums have 1 size and 8 bit layout by default let layout = vec![RewriteNode::Text("layout.append(8);\n".into())]; let size_precompute = 1; diff --git a/crates/dojo-lang/src/plugin_test_data/introspect b/crates/dojo-lang/src/plugin_test_data/introspect index 98e7f261d5..f24bd429ed 100644 --- a/crates/dojo-lang/src/plugin_test_data/introspect +++ b/crates/dojo-lang/src/plugin_test_data/introspect @@ -118,7 +118,7 @@ impl PlainEnumSchemaIntrospection of dojo::database::schema::SchemaIntrospection fn ty() -> dojo::database::schema::Ty { dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'PlainEnum', attrs: array![].span(), children: array![ ( @@ -164,7 +164,7 @@ impl EnumPrimitiveSchemaIntrospection of dojo::database::schema::SchemaIntrospec fn ty() -> dojo::database::schema::Ty { dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumPrimitive', attrs: array![].span(), children: array![ ( @@ -225,7 +225,7 @@ impl EnumTupleSchemaIntrospection of dojo::database::schema::SchemaIntrospection fn ty() -> dojo::database::schema::Ty { dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumTuple', attrs: array![].span(), children: array![ ( @@ -291,7 +291,7 @@ impl EnumCustomSchemaIntrospection of dojo::database::schema::SchemaIntrospectio fn ty() -> dojo::database::schema::Ty { dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumCustom', attrs: array![].span(), children: array![ ( @@ -694,7 +694,7 @@ impl PlainEnumDrop of Drop::; dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'PlainEnum', attrs: array![].span(), children: array![ @@ -752,7 +752,7 @@ layout.append(16); dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumPrimitive', attrs: array![].span(), children: array![ @@ -815,7 +815,7 @@ layout.append(8); dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumTuple', attrs: array![].span(), children: array![ @@ -883,7 +883,7 @@ dojo::database::schema::SchemaIntrospection::::layout(ref layout); dojo::database::schema::Ty::Enum( dojo::database::schema::Enum { - name: 'Direction', + name: 'EnumCustom', attrs: array![].span(), children: array![ From 2b3efb239e6dd850d9497c4fcb4f67b0924b87c5 Mon Sep 17 00:00:00 2001 From: Yun Date: Wed, 15 Nov 2023 16:20:25 -0800 Subject: [PATCH 014/192] Refactor torii grpc (#1186) --- crates/dojo-types/src/schema.rs | 56 ------------ crates/torii/client/src/client/mod.rs | 89 ++++++++----------- crates/torii/client/src/client/storage.rs | 25 ++---- .../torii/client/src/client/subscription.rs | 76 +++++++--------- crates/torii/core/src/error.rs | 10 ++- .../graphql/src/tests/types-test/Scarb.lock | 4 +- crates/torii/grpc/proto/types.proto | 18 ++-- crates/torii/grpc/proto/world.proto | 4 +- crates/torii/grpc/src/client.rs | 6 +- crates/torii/grpc/src/lib.rs | 2 +- crates/torii/grpc/src/server/mod.rs | 37 +++----- crates/torii/grpc/src/server/subscription.rs | 61 +++++++++---- .../grpc/src/{conversion.rs => types.rs} | 78 ++++++++++++++-- examples/spawn-and-move/Scarb.lock | 6 +- 14 files changed, 228 insertions(+), 244 deletions(-) rename crates/torii/grpc/src/{conversion.rs => types.rs} (77%) diff --git a/crates/dojo-types/src/schema.rs b/crates/dojo-types/src/schema.rs index a6ff30ebc5..ec86efd39c 100644 --- a/crates/dojo-types/src/schema.rs +++ b/crates/dojo-types/src/schema.rs @@ -20,62 +20,6 @@ impl Member { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct EntityQuery { - pub model: String, - pub clause: Clause, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum Clause { - Keys(KeysClause), - Attribute(AttributeClause), - Composite(CompositeClause), -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct KeysClause { - pub keys: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct AttributeClause { - pub attribute: String, - pub operator: ComparisonOperator, - pub value: Value, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct CompositeClause { - pub operator: LogicalOperator, - pub clauses: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum LogicalOperator { - And, - Or, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum ComparisonOperator { - Eq, - Neq, - Gt, - Gte, - Lt, - Lte, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum Value { - String(String), - Int(i64), - UInt(u64), - Bool(bool), - Bytes(Vec), -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModelMetadata { pub schema: Ty, diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index 6c58f2c559..ba713b5c5a 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use std::sync::Arc; use dojo_types::packing::unpack; -use dojo_types::schema::{Clause, EntityQuery, Ty}; +use dojo_types::schema::Ty; use dojo_types::WorldMetadata; use dojo_world::contracts::WorldContractReader; use parking_lot::{RwLock, RwLockReadGuard}; @@ -17,6 +17,7 @@ use starknet::providers::JsonRpcClient; use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; use torii_grpc::client::EntityUpdateStreaming; +use torii_grpc::types::KeysClause; use self::error::{Error, ParseError}; use self::storage::ModelStorage; @@ -46,7 +47,7 @@ impl Client { torii_url: String, rpc_url: String, world: FieldElement, - queries: Option>, + entities_keys: Option>, ) -> Result { let mut grpc_client = torii_grpc::client::WorldClient::new(torii_url, world).await?; @@ -61,23 +62,18 @@ impl Client { let provider = JsonRpcClient::new(HttpTransport::new(rpc_url)); let world_reader = WorldContractReader::new(world, provider); - if let Some(queries) = queries { - subbed_entities.add_entities(queries)?; + if let Some(keys) = entities_keys { + subbed_entities.add_entities(keys)?; // TODO: change this to querying the gRPC url instead - let subbed_entities = subbed_entities.entities.read().clone(); - for EntityQuery { model, clause } in subbed_entities { - let model_reader = world_reader.model(&model).await?; - let keys = if let Clause::Keys(clause) = clause { - clause.keys - } else { - return Err(Error::UnsupportedQuery); - }; - let values = model_reader.entity_storage(&keys).await?; + let subbed_entities = subbed_entities.entities_keys.read().clone(); + for keys in subbed_entities { + let model_reader = world_reader.model(&keys.model).await?; + let values = model_reader.entity_storage(&keys.keys).await?; client_storage.set_entity_storage( - cairo_short_string_to_felt(&model).unwrap(), - keys, + cairo_short_string_to_felt(&keys.model).unwrap(), + keys.keys, values, )?; } @@ -98,8 +94,8 @@ impl Client { self.metadata.read() } - pub fn subscribed_entities(&self) -> RwLockReadGuard<'_, HashSet> { - self.subscribed_entities.entities.read() + pub fn subscribed_entities(&self) -> RwLockReadGuard<'_, HashSet> { + self.subscribed_entities.entities_keys.read() } /// Returns the model value of an entity. @@ -109,27 +105,20 @@ impl Client { /// /// If the requested entity is not among the synced entities, it will attempt to fetch it from /// the RPC. - pub async fn entity(&self, entity: &EntityQuery) -> Result, Error> { - let Some(mut schema) = self.metadata.read().model(&entity.model).map(|m| m.schema.clone()) + pub async fn entity(&self, keys: &KeysClause) -> Result, Error> { + let Some(mut schema) = self.metadata.read().model(&keys.model).map(|m| m.schema.clone()) else { return Ok(None); }; - let keys = if let Clause::Keys(clause) = entity.clone().clause { - clause.keys - } else { - return Err(Error::UnsupportedQuery); - }; - - if !self.subscribed_entities.is_synced(entity) { - let model = self.world_reader.model(&entity.model).await?; - return Ok(Some(model.entity(&keys).await?)); + if !self.subscribed_entities.is_synced(keys) { + let model = self.world_reader.model(&keys.model).await?; + return Ok(Some(model.entity(&keys.keys).await?)); } let Ok(Some(raw_values)) = self.storage.get_entity_storage( - cairo_short_string_to_felt(&entity.model) - .map_err(ParseError::CairoShortStringToFelt)?, - &keys, + cairo_short_string_to_felt(&keys.model).map_err(ParseError::CairoShortStringToFelt)?, + &keys.keys, ) else { return Ok(Some(schema)); }; @@ -137,12 +126,12 @@ impl Client { let layout = self .metadata .read() - .model(&entity.model) + .model(&keys.model) .map(|m| m.layout.clone()) .expect("qed; layout should exist"); let unpacked = unpack(raw_values, layout).unwrap(); - let mut keys_and_unpacked = [keys.to_vec(), unpacked].concat(); + let mut keys_and_unpacked = [keys.keys.to_vec(), unpacked].concat(); schema.deserialize(&mut keys_and_unpacked).unwrap(); @@ -152,8 +141,9 @@ impl Client { /// Initiate the entity subscriptions and returns a [SubscriptionService] which when await'ed /// will execute the subscription service and starts the syncing process. pub async fn start_subscription(&self) -> Result { - let entities = self.subscribed_entities.entities.read().clone().into_iter().collect(); - let sub_res_stream = self.initiate_subscription(entities).await?; + let entities_keys = + self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); + let sub_res_stream = self.initiate_subscription(entities_keys).await?; let (service, handle) = SubscriptionService::new( Arc::clone(&self.storage), @@ -169,21 +159,15 @@ impl Client { /// Adds entities to the list of entities to be synced. /// /// NOTE: This will establish a new subscription stream with the server. - pub async fn add_entities_to_sync(&self, entities: Vec) -> Result<(), Error> { - for entity in &entities { - let keys = if let Clause::Keys(clause) = entity.clone().clause { - clause.keys - } else { - return Err(Error::UnsupportedQuery); - }; - - self.initiate_entity(&entity.model, keys.clone()).await?; + pub async fn add_entities_to_sync(&self, entities_keys: Vec) -> Result<(), Error> { + for keys in &entities_keys { + self.initiate_entity(&keys.model, keys.keys.clone()).await?; } - self.subscribed_entities.add_entities(entities)?; + self.subscribed_entities.add_entities(entities_keys)?; let updated_entities = - self.subscribed_entities.entities.read().clone().into_iter().collect(); + self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); let sub_res_stream = self.initiate_subscription(updated_entities).await?; match self.sub_client_handle.get() { @@ -196,11 +180,14 @@ impl Client { /// Removes entities from the list of entities to be synced. /// /// NOTE: This will establish a new subscription stream with the server. - pub async fn remove_entities_to_sync(&self, entities: Vec) -> Result<(), Error> { - self.subscribed_entities.remove_entities(entities)?; + pub async fn remove_entities_to_sync( + &self, + entities_keys: Vec, + ) -> Result<(), Error> { + self.subscribed_entities.remove_entities(entities_keys)?; let updated_entities = - self.subscribed_entities.entities.read().clone().into_iter().collect(); + self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); let sub_res_stream = self.initiate_subscription(updated_entities).await?; match self.sub_client_handle.get() { @@ -216,10 +203,10 @@ impl Client { async fn initiate_subscription( &self, - entities: Vec, + keys: Vec, ) -> Result { let mut grpc_client = self.inner.write().await; - let stream = grpc_client.subscribe_entities(entities).await?; + let stream = grpc_client.subscribe_entities(keys).await?; Ok(stream) } diff --git a/crates/torii/client/src/client/storage.rs b/crates/torii/client/src/client/storage.rs index 2f486825b2..d56b7db506 100644 --- a/crates/torii/client/src/client/storage.rs +++ b/crates/torii/client/src/client/storage.rs @@ -168,7 +168,7 @@ mod tests { use std::collections::HashMap; use std::sync::Arc; - use dojo_types::schema::{KeysClause, Ty}; + use dojo_types::schema::Ty; use dojo_types::WorldMetadata; use parking_lot::RwLock; use starknet::core::utils::cairo_short_string_to_felt; @@ -202,13 +202,8 @@ mod tests { fn err_if_set_values_too_many() { let storage = create_dummy_storage(); let keys = vec![felt!("0x12345")]; - let entity = dojo_types::schema::EntityQuery { - model: "Position".into(), - clause: dojo_types::schema::Clause::Keys(KeysClause { keys: keys.clone() }), - }; - let values = vec![felt!("1"), felt!("2"), felt!("3"), felt!("4"), felt!("5")]; - let model = cairo_short_string_to_felt(&entity.model).unwrap(); + let model = cairo_short_string_to_felt("Position").unwrap(); let result = storage.set_entity_storage(model, keys, values); assert!(storage.storage.read().is_empty()); @@ -222,13 +217,8 @@ mod tests { fn err_if_set_values_too_few() { let storage = create_dummy_storage(); let keys = vec![felt!("0x12345")]; - let entity = dojo_types::schema::EntityQuery { - model: "Position".into(), - clause: dojo_types::schema::Clause::Keys(KeysClause { keys: keys.clone() }), - }; - let values = vec![felt!("1"), felt!("2")]; - let model = cairo_short_string_to_felt(&entity.model).unwrap(); + let model = cairo_short_string_to_felt("Position").unwrap(); let result = storage.set_entity_storage(model, keys, values); assert!(storage.storage.read().is_empty()); @@ -242,15 +232,10 @@ mod tests { fn set_and_get_entity_value() { let storage = create_dummy_storage(); let keys = vec![felt!("0x12345")]; - let entity = dojo_types::schema::EntityQuery { - model: "Position".into(), - clause: dojo_types::schema::Clause::Keys(KeysClause { keys: keys.clone() }), - }; assert!(storage.storage.read().is_empty(), "storage must be empty initially"); - let model = storage.metadata.read().model(&entity.model).cloned().unwrap(); - + let model = storage.metadata.read().model("Position").cloned().unwrap(); let expected_storage_addresses = compute_all_storage_addresses( cairo_short_string_to_felt(&model.name).unwrap(), &keys, @@ -258,7 +243,7 @@ mod tests { ); let expected_values = vec![felt!("1"), felt!("2"), felt!("3"), felt!("4")]; - let model_name_in_felt = cairo_short_string_to_felt(&entity.model).unwrap(); + let model_name_in_felt = cairo_short_string_to_felt("Position").unwrap(); storage .set_entity_storage(model_name_in_felt, keys.clone(), expected_values.clone()) diff --git a/crates/torii/client/src/client/subscription.rs b/crates/torii/client/src/client/subscription.rs index 8bd82346f1..898d008130 100644 --- a/crates/torii/client/src/client/subscription.rs +++ b/crates/torii/client/src/client/subscription.rs @@ -4,7 +4,6 @@ use std::future::Future; use std::sync::Arc; use std::task::Poll; -use dojo_types::schema::{Clause, EntityQuery}; use dojo_types::WorldMetadata; use futures::channel::mpsc::{self, Receiver, Sender}; use futures_util::StreamExt; @@ -13,6 +12,7 @@ use starknet::core::types::{StateDiff, StateUpdate}; use starknet::core::utils::cairo_short_string_to_felt; use starknet_crypto::FieldElement; use torii_grpc::client::EntityUpdateStreaming; +use torii_grpc::types::KeysClause; use super::error::{Error, ParseError}; use super::ModelStorage; @@ -24,61 +24,54 @@ pub enum SubscriptionEvent { pub struct SubscribedEntities { metadata: Arc>, - pub(super) entities: RwLock>, + pub(super) entities_keys: RwLock>, /// All the relevant storage addresses derived from the subscribed entities pub(super) subscribed_storage_addresses: RwLock>, } impl SubscribedEntities { - pub(super) fn is_synced(&self, entity: &EntityQuery) -> bool { - self.entities.read().contains(entity) + pub(super) fn is_synced(&self, keys: &KeysClause) -> bool { + self.entities_keys.read().contains(keys) } pub(super) fn new(metadata: Arc>) -> Self { Self { metadata, - entities: Default::default(), + entities_keys: Default::default(), subscribed_storage_addresses: Default::default(), } } - pub(super) fn add_entities(&self, entities: Vec) -> Result<(), Error> { - for entity in entities { - Self::add_entity(self, entity)?; + pub(super) fn add_entities(&self, entities_keys: Vec) -> Result<(), Error> { + for keys in entities_keys { + Self::add_entity(self, keys)?; } Ok(()) } - pub(super) fn remove_entities(&self, entities: Vec) -> Result<(), Error> { - for entity in entities { - Self::remove_entity(self, entity)?; + pub(super) fn remove_entities(&self, entities_keys: Vec) -> Result<(), Error> { + for keys in entities_keys { + Self::remove_entity(self, keys)?; } Ok(()) } - pub(super) fn add_entity(&self, entity: EntityQuery) -> Result<(), Error> { - if !self.entities.write().insert(entity.clone()) { + pub(super) fn add_entity(&self, keys: KeysClause) -> Result<(), Error> { + if !self.entities_keys.write().insert(keys.clone()) { return Ok(()); } - let keys = if let Clause::Keys(clause) = entity.clause { - clause.keys - } else { - return Err(Error::UnsupportedQuery); - }; - let model_packed_size = self .metadata .read() .models - .get(&entity.model) + .get(&keys.model) .map(|c| c.packed_size) - .ok_or(Error::UnknownModel(entity.model.clone()))?; + .ok_or(Error::UnknownModel(keys.model.clone()))?; let storage_addresses = compute_all_storage_addresses( - cairo_short_string_to_felt(&entity.model) - .map_err(ParseError::CairoShortStringToFelt)?, - &keys, + cairo_short_string_to_felt(&keys.model).map_err(ParseError::CairoShortStringToFelt)?, + &keys.keys, model_packed_size, ); @@ -90,29 +83,22 @@ impl SubscribedEntities { Ok(()) } - pub(super) fn remove_entity(&self, entity: EntityQuery) -> Result<(), Error> { - if !self.entities.write().remove(&entity) { + pub(super) fn remove_entity(&self, keys: KeysClause) -> Result<(), Error> { + if !self.entities_keys.write().remove(&keys) { return Ok(()); } - let keys = if let Clause::Keys(clause) = entity.clause { - clause.keys - } else { - return Err(Error::UnsupportedQuery); - }; - let model_packed_size = self .metadata .read() .models - .get(&entity.model) + .get(&keys.model) .map(|c| c.packed_size) - .ok_or(Error::UnknownModel(entity.model.clone()))?; + .ok_or(Error::UnknownModel(keys.model.clone()))?; let storage_addresses = compute_all_storage_addresses( - cairo_short_string_to_felt(&entity.model) - .map_err(ParseError::CairoShortStringToFelt)?, - &keys, + cairo_short_string_to_felt(&keys.model).map_err(ParseError::CairoShortStringToFelt)?, + &keys.keys, model_packed_size, ); @@ -256,11 +242,12 @@ mod tests { use std::collections::HashMap; use std::sync::Arc; - use dojo_types::schema::{KeysClause, Ty}; + use dojo_types::schema::Ty; use dojo_types::WorldMetadata; use parking_lot::RwLock; use starknet::core::utils::cairo_short_string_to_felt; use starknet::macros::felt; + use torii_grpc::types::KeysClause; use crate::utils::compute_all_storage_addresses; @@ -295,29 +282,26 @@ mod tests { let metadata = self::create_dummy_metadata(); - let entity = dojo_types::schema::EntityQuery { - model: model_name, - clause: dojo_types::schema::Clause::Keys(KeysClause { keys }), - }; + let keys = KeysClause { model: model_name, keys }; let subscribed_entities = super::SubscribedEntities::new(Arc::new(RwLock::new(metadata))); - subscribed_entities.add_entities(vec![entity.clone()]).expect("able to add entity"); + subscribed_entities.add_entities(vec![keys.clone()]).expect("able to add entity"); let actual_storage_addresses_count = subscribed_entities.subscribed_storage_addresses.read().len(); let actual_storage_addresses = subscribed_entities.subscribed_storage_addresses.read().clone(); - assert!(subscribed_entities.entities.read().contains(&entity)); + assert!(subscribed_entities.entities_keys.read().contains(&keys)); assert_eq!(actual_storage_addresses_count, expected_storage_addresses.len()); assert!(expected_storage_addresses.all(|addr| actual_storage_addresses.contains(&addr))); - subscribed_entities.remove_entities(vec![entity.clone()]).expect("able to remove entities"); + subscribed_entities.remove_entities(vec![keys.clone()]).expect("able to remove entities"); let actual_storage_addresses_count_after = subscribed_entities.subscribed_storage_addresses.read().len(); assert_eq!(actual_storage_addresses_count_after, 0); - assert!(!subscribed_entities.entities.read().contains(&entity)); + assert!(!subscribed_entities.entities_keys.read().contains(&keys)); } } diff --git a/crates/torii/core/src/error.rs b/crates/torii/core/src/error.rs index 8ccccdc601..cdcf6b9b95 100644 --- a/crates/torii/core/src/error.rs +++ b/crates/torii/core/src/error.rs @@ -7,8 +7,8 @@ pub enum Error { Parse(#[from] ParseError), #[error(transparent)] Sql(#[from] sqlx::Error), - #[error("unsupported query clause")] - UnsupportedQuery, + #[error(transparent)] + QueryError(#[from] QueryError), } #[derive(Debug, thiserror::Error)] @@ -20,3 +20,9 @@ pub enum ParseError { #[error(transparent)] FromByteSliceError(#[from] FromByteSliceError), } + +#[derive(Debug, thiserror::Error)] +pub enum QueryError { + #[error("unsupported query")] + UnsupportedQuery, +} diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index 2ca7018569..6ad1bf2c03 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,14 +3,14 @@ version = 1 [[package]] name = "dojo" -version = "0.3.6" +version = "0.3.10" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.6" +version = "0.3.10" [[package]] name = "types_test" diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index eb2e6e9fad..1c853c005b 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -54,8 +54,7 @@ message EntityUpdate { } message EntityQuery { - string model = 1; - Clause clause = 2; + Clause clause = 1; } message Clause { @@ -67,18 +66,21 @@ message Clause { } message KeysClause { - repeated bytes keys = 1; + string model = 1; + repeated bytes keys = 2; } message AttributeClause { - string attribute = 1; - ComparisonOperator operator = 2; - Value value = 3; + string model = 1; + string attribute = 2; + ComparisonOperator operator = 3; + Value value = 4; } message CompositeClause { - LogicalOperator operator = 1; - repeated Clause clauses = 2; + string model = 1; + LogicalOperator operator = 2; + repeated Clause clauses = 3; } enum LogicalOperator { diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 10734e7e8e..66f5fce09d 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -25,8 +25,8 @@ message MetadataResponse { } message SubscribeEntitiesRequest { - // The list of entity queries to subscribe to. - repeated types.EntityQuery queries = 1; + // The list of entity keys to subscribe to. + repeated types.KeysClause entities_keys = 1; } message SubscribeEntitiesResponse { diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index c97b250b16..e4097849c3 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -1,5 +1,4 @@ //! Client implementation for the gRPC service. - use futures_util::stream::MapOk; use futures_util::{Stream, StreamExt, TryStreamExt}; use proto::world::{world_client, SubscribeEntitiesRequest}; @@ -8,6 +7,7 @@ use starknet_crypto::FieldElement; use crate::proto::world::{MetadataRequest, SubscribeEntitiesResponse}; use crate::proto::{self}; +use crate::types::KeysClause; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -67,12 +67,12 @@ impl WorldClient { /// Subscribe to the state diff for a set of entities of a World. pub async fn subscribe_entities( &mut self, - queries: Vec, + entities_keys: Vec, ) -> Result { let stream = self .inner .subscribe_entities(SubscribeEntitiesRequest { - queries: queries.into_iter().map(|e| e.into()).collect(), + entities_keys: entities_keys.into_iter().map(|e| e.into()).collect(), }) .await .map_err(Error::Grpc) diff --git a/crates/torii/grpc/src/lib.rs b/crates/torii/grpc/src/lib.rs index 4b19ce10cb..1fb5da7eb7 100644 --- a/crates/torii/grpc/src/lib.rs +++ b/crates/torii/grpc/src/lib.rs @@ -3,7 +3,7 @@ extern crate wasm_prost as prost; #[cfg(target_arch = "wasm32")] extern crate wasm_tonic as tonic; -pub mod conversion; +pub mod types; #[cfg(feature = "client")] pub mod client; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index c4e2088e70..d5ccac1680 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -7,7 +7,6 @@ use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; -use dojo_types::schema::KeysClause; use futures::Stream; use proto::world::{ MetadataRequest, MetadataResponse, SubscribeEntitiesRequest, SubscribeEntitiesResponse, @@ -26,7 +25,6 @@ use torii_core::error::{Error, ParseError}; use torii_core::model::{parse_sql_model_members, SqlModelMember}; use self::subscription::SubscribeRequest; -use crate::proto::types::clause::ClauseType; use crate::proto::world::world_server::WorldServer; use crate::proto::{self}; @@ -141,30 +139,19 @@ impl DojoWorld { async fn subscribe_entities( &self, - queries: Vec, + entities_keys: Vec, ) -> Result>, Error> { - let mut subs = Vec::with_capacity(queries.len()); - for query in queries { - let clause: KeysClause = query - .clause - .ok_or(Error::UnsupportedQuery) - .and_then(|clause| clause.clause_type.ok_or(Error::UnsupportedQuery)) - .and_then(|clause_type| match clause_type { - ClauseType::Keys(clause) => Ok(clause), - _ => Err(Error::UnsupportedQuery), - })? - .try_into() - .map_err(ParseError::FromByteSliceError)?; - - let model = cairo_short_string_to_felt(&query.model) + let mut subs = Vec::with_capacity(entities_keys.len()); + for keys in entities_keys { + let model = cairo_short_string_to_felt(&keys.model) .map_err(ParseError::CairoShortStringToFelt)?; let proto::types::ModelMetadata { packed_size, .. } = - self.model_metadata(&query.model).await?; + self.model_metadata(&keys.model).await?; subs.push(SubscribeRequest { - keys: clause.keys, + keys, model: subscription::ModelMetadata { name: model, packed_size: packed_size as usize, @@ -172,9 +159,7 @@ impl DojoWorld { }); } - let res = self.subscriber_manager.add_subscriber(subs).await; - - Ok(res) + self.subscriber_manager.add_subscriber(subs).await } } @@ -202,9 +187,11 @@ impl proto::world::world_server::World for DojoWorld { &self, request: Request, ) -> ServiceResult { - let SubscribeEntitiesRequest { queries } = request.into_inner(); - let rx = - self.subscribe_entities(queries).await.map_err(|e| Status::internal(e.to_string()))?; + let SubscribeEntitiesRequest { entities_keys } = request.into_inner(); + let rx = self + .subscribe_entities(entities_keys) + .await + .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) } } diff --git a/crates/torii/grpc/src/server/subscription.rs b/crates/torii/grpc/src/server/subscription.rs index 4750d69cdd..bc20140df7 100644 --- a/crates/torii/grpc/src/server/subscription.rs +++ b/crates/torii/grpc/src/server/subscription.rs @@ -6,7 +6,7 @@ use std::task::Poll; use futures_util::future::BoxFuture; use futures_util::FutureExt; use rand::Rng; -use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use starknet::core::types::{ BlockId, ContractStorageDiffItem, MaybePendingStateUpdate, StateUpdate, StorageEntry, }; @@ -15,10 +15,12 @@ use starknet::providers::Provider; use starknet_crypto::{poseidon_hash_many, FieldElement}; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::RwLock; +use torii_core::error::{Error, ParseError}; use tracing::{debug, error, trace}; -use super::error::SubscriptionError as Error; +use super::error::SubscriptionError; use crate::proto; +use crate::types::KeysClause; pub struct ModelMetadata { pub name: FieldElement, @@ -27,7 +29,22 @@ pub struct ModelMetadata { pub struct SubscribeRequest { pub model: ModelMetadata, - pub keys: Vec, + pub keys: proto::types::KeysClause, +} + +impl SubscribeRequest { + // pub fn slots(&self) -> Result, QueryError> { + // match self.query.clause { + // Clause::Keys(KeysClause { keys }) => { + // let base = poseidon_hash_many(&[ + // short_string!("dojo_storage"), + // req.model.name, + // poseidon_hash_many(&keys), + // ]); + // } + // _ => Err(QueryError::UnsupportedQuery), + // } + // } } pub struct Subscriber { @@ -45,33 +62,41 @@ pub struct SubscriberManager { impl SubscriberManager { pub(super) async fn add_subscriber( &self, - entities: Vec, - ) -> Receiver> { + reqs: Vec, + ) -> Result>, Error> + { let id = rand::thread_rng().gen::(); let (sender, receiver) = channel(1); // convert the list of entites into a list storage addresses - let storage_addresses = entities - .par_iter() - .map(|entity| { + let storage_addresses = reqs + .into_iter() + .map(|req| { + let keys: KeysClause = + req.keys.try_into().map_err(ParseError::FromByteSliceError)?; + let base = poseidon_hash_many(&[ short_string!("dojo_storage"), - entity.model.name, - poseidon_hash_many(&entity.keys), + req.model.name, + poseidon_hash_many(&keys.keys), ]); - (0..entity.model.packed_size) + let res = (0..req.model.packed_size) .into_par_iter() .map(|i| base + i.into()) - .collect::>() + .collect::>(); + + Ok(res) }) + .collect::, Error>>()? + .into_iter() .flatten() .collect::>(); self.subscribers.write().await.insert(id, Subscriber { storage_addresses, sender }); - receiver + Ok(receiver) } pub(super) async fn remove_subscriber(&self, id: usize) { @@ -79,8 +104,8 @@ impl SubscriberManager { } } -type PublishStateUpdateResult = Result<(), Error>; -type RequestStateUpdateResult = Result; +type PublishStateUpdateResult = Result<(), SubscriptionError>; +type RequestStateUpdateResult = Result; #[must_use = "Service does nothing unless polled"] pub struct Service { @@ -115,8 +140,10 @@ where } async fn fetch_state_update(provider: P, block_num: u64) -> (P, u64, RequestStateUpdateResult) { - let res = - provider.get_state_update(BlockId::Number(block_num)).await.map_err(Error::Provider); + let res = provider + .get_state_update(BlockId::Number(block_num)) + .await + .map_err(SubscriptionError::Provider); (provider, block_num, res) } diff --git a/crates/torii/grpc/src/conversion.rs b/crates/torii/grpc/src/types.rs similarity index 77% rename from crates/torii/grpc/src/conversion.rs rename to crates/torii/grpc/src/types.rs index 987059cfb2..e62dc3b8a8 100644 --- a/crates/torii/grpc/src/conversion.rs +++ b/crates/torii/grpc/src/types.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; use std::str::FromStr; -use dojo_types::schema::{ - AttributeClause, Clause, CompositeClause, EntityQuery, KeysClause, Ty, Value, -}; +use dojo_types::schema::Ty; +use serde::{Deserialize, Serialize}; use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; @@ -11,6 +10,64 @@ use starknet_crypto::FieldElement; use crate::proto; +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Query { + pub clause: Clause, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum Clause { + Keys(KeysClause), + Attribute(AttributeClause), + Composite(CompositeClause), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct KeysClause { + pub model: String, + pub keys: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct AttributeClause { + pub model: String, + pub attribute: String, + pub operator: ComparisonOperator, + pub value: Value, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct CompositeClause { + pub model: String, + pub operator: LogicalOperator, + pub clauses: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum LogicalOperator { + And, + Or, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum ComparisonOperator { + Eq, + Neq, + Gt, + Gte, + Lt, + Lte, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum Value { + String(String), + Int(i64), + UInt(u64), + Bool(bool), + Bytes(Vec), +} + impl TryFrom for dojo_types::schema::ModelMetadata { type Error = FromStrError; fn try_from(value: proto::types::ModelMetadata) -> Result { @@ -46,9 +103,9 @@ impl TryFrom for dojo_types::WorldMetadata { } } -impl From for proto::types::EntityQuery { - fn from(value: EntityQuery) -> Self { - Self { model: value.model, clause: Some(value.clause.into()) } +impl From for proto::types::EntityQuery { + fn from(value: Query) -> Self { + Self { clause: Some(value.clause.into()) } } } @@ -70,7 +127,10 @@ impl From for proto::types::Clause { impl From for proto::types::KeysClause { fn from(value: KeysClause) -> Self { - Self { keys: value.keys.iter().map(|k| k.to_bytes_be().into()).collect() } + Self { + model: value.model, + keys: value.keys.iter().map(|k| k.to_bytes_be().into()).collect(), + } } } @@ -84,13 +144,14 @@ impl TryFrom for KeysClause { .map(|k| FieldElement::from_byte_slice_be(&k)) .collect::, _>>()?; - Ok(Self { keys }) + Ok(Self { model: value.model, keys }) } } impl From for proto::types::AttributeClause { fn from(value: AttributeClause) -> Self { Self { + model: value.model, attribute: value.attribute, operator: value.operator as i32, value: Some(value.value.into()), @@ -101,6 +162,7 @@ impl From for proto::types::AttributeClause { impl From for proto::types::CompositeClause { fn from(value: CompositeClause) -> Self { Self { + model: value.model, operator: value.operator as i32, clauses: value.clauses.into_iter().map(|clause| clause.into()).collect(), } diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 9ffa906f1d..2370c6221a 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,18 +3,18 @@ version = 1 [[package]] name = "dojo" -version = "0.3.6" +version = "0.3.10" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.6" +version = "0.3.10" dependencies = [ "dojo", ] [[package]] name = "dojo_plugin" -version = "0.3.6" +version = "0.3.10" From 62980bbb36851e13a30e2f502e0fb6064df888ce Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 16 Nov 2023 16:06:26 +0300 Subject: [PATCH 015/192] refactor(katana): execute txs using iterator (#1180) * Execute tx thru iterator * update --- crates/katana/core/src/backend/mod.rs | 4 +- crates/katana/core/src/execution.rs | 131 ++++++++++-------- .../katana/core/src/service/block_producer.rs | 10 +- 3 files changed, 79 insertions(+), 66 deletions(-) diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index a57690686a..745fe78a06 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -185,9 +185,9 @@ impl Backend { let mut estimations = Vec::with_capacity(transactions.len()); - let results = TransactionExecutor::new(&mut state, &block_context, false) + let results = TransactionExecutor::new(&mut state, &block_context, false, transactions) .with_error_log() - .execute_many(transactions); + .execute(); for res in results { let exec_info = res?; diff --git a/crates/katana/core/src/execution.rs b/crates/katana/core/src/execution.rs index fd599ffbdf..e11f5a314d 100644 --- a/crates/katana/core/src/execution.rs +++ b/crates/katana/core/src/execution.rs @@ -95,9 +95,22 @@ impl Default for ExecutionOutcome { } } +/// The result of a transaction execution. +pub type TxExecutionResult = Result; + +/// A transaction executor. +/// +/// The transactions will be executed in an iterator fashion, sequentially, in the +/// exact order they are provided to the executor. The execution is done within its implementation +/// of the [`Iterator`] trait. pub struct TransactionExecutor<'a> { + /// A flag to enable/disable fee charging. charge_fee: bool, + /// The block context the transactions will be executed on. block_context: &'a BlockContext, + /// The transactions to be executed (in the exact order they are in the iterator). + transactions: std::vec::IntoIter, + /// The state the transactions will be executed on. state: &'a mut CachedStateWrapper, // logs flags @@ -111,6 +124,7 @@ impl<'a> TransactionExecutor<'a> { state: &'a mut CachedStateWrapper, block_context: &'a BlockContext, charge_fee: bool, + transactions: Vec, ) -> Self { Self { state, @@ -119,6 +133,7 @@ impl<'a> TransactionExecutor<'a> { error_log: false, events_log: false, resources_log: false, + transactions: transactions.into_iter(), } } @@ -133,78 +148,76 @@ impl<'a> TransactionExecutor<'a> { pub fn with_resources_log(self) -> Self { Self { resources_log: true, ..self } } -} -impl<'a> TransactionExecutor<'a> { - pub fn execute_many( - &mut self, - transactions: Vec, - ) -> Vec> { - transactions.into_iter().map(|tx| self.execute(tx)).collect() + /// A method to conveniently execute all the transactions and return their results. + pub fn execute(self) -> Vec { + self.collect() } +} - pub fn execute( - &mut self, - transaction: Transaction, - ) -> Result { - let sierra = if let Transaction::Declare(DeclareTransaction { - sierra_class: Some(sierra_class), - inner, - .. - }) = &transaction - { - Some((inner.class_hash(), sierra_class.clone())) - } else { - None - }; - - let res = match transaction.into() { - ExecutionTransaction::AccountTransaction(tx) => { - tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) - } - ExecutionTransaction::L1HandlerTransaction(tx) => { - tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) - } - }; - - match res { - Ok(exec_info) => { - if let Some((class_hash, sierra_class)) = sierra { - self.state - .set_sierra_class(class_hash, sierra_class) - .expect("failed to set sierra class"); +impl<'a> Iterator for TransactionExecutor<'a> { + type Item = TxExecutionResult; + fn next(&mut self) -> Option { + self.transactions.next().map(|tx| { + let sierra = if let Transaction::Declare(DeclareTransaction { + sierra_class: Some(sierra_class), + inner, + .. + }) = &tx + { + Some((inner.class_hash(), sierra_class.clone())) + } else { + None + }; + + let res = match tx.into() { + ExecutionTransaction::AccountTransaction(tx) => { + tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) } + ExecutionTransaction::L1HandlerTransaction(tx) => { + tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) + } + }; + + match res { + Ok(exec_info) => { + if let Some((class_hash, sierra_class)) = sierra { + self.state + .set_sierra_class(class_hash, sierra_class) + .expect("failed to set sierra class"); + } - if self.error_log { - if let Some(err) = &exec_info.revert_error { - let formatted_err = format!("{:?}", err).replace("\\n", "\n"); - warn!(target: "executor", "Transaction execution error: {formatted_err}"); + if self.error_log { + if let Some(err) = &exec_info.revert_error { + let formatted_err = format!("{:?}", err).replace("\\n", "\n"); + warn!(target: "executor", "Transaction execution error: {formatted_err}"); + } } - } - if self.resources_log { - trace!( - target: "executor", - "Transaction resource usage: {}", - pretty_print_resources(&exec_info.actual_resources) - ); - } + if self.resources_log { + trace!( + target: "executor", + "Transaction resource usage: {}", + pretty_print_resources(&exec_info.actual_resources) + ); + } + + if self.events_log { + trace_events(&events_from_exec_info(&exec_info)); + } - if self.events_log { - trace_events(&events_from_exec_info(&exec_info)); + Ok(exec_info) } - Ok(exec_info) - } + Err(err) => { + if self.error_log { + warn_message_transaction_error_exec_error(&err); + } - Err(err) => { - if self.error_log { - warn_message_transaction_error_exec_error(&err); + Err(err) } - - Err(err) } - } + }) } } diff --git a/crates/katana/core/src/service/block_producer.rs b/crates/katana/core/src/service/block_producer.rs index be8dbd6726..1f03d8c95a 100644 --- a/crates/katana/core/src/service/block_producer.rs +++ b/crates/katana/core/src/service/block_producer.rs @@ -228,16 +228,15 @@ impl IntervalBlockProducer { &mut state, &self.backend.env.read().block, !self.backend.config.read().disable_fee, + transactions.clone(), ) .with_error_log() .with_events_log() .with_resources_log() - .execute_many(transactions.clone()) - .into_iter() .zip(transactions) .map(|(res, tx)| match res { - Ok(exec_info) => { - let executed_tx = ExecutedTransaction::new(tx, exec_info); + Ok(execution_info) => { + let executed_tx = ExecutedTransaction::new(tx, execution_info); MaybeInvalidExecutedTransaction::Valid(Arc::new(executed_tx)) } Err(err) => { @@ -354,11 +353,12 @@ impl InstantBlockProducer { &mut state, &block_context, !backend.config.read().disable_fee, + transactions.clone(), ) .with_error_log() .with_events_log() .with_resources_log() - .execute_many(transactions.clone()); + .execute(); let outcome = backend .do_mine_block(create_execution_outcome( From 999691934f5d07588b538f9dd0ed5ca42ea0fb10 Mon Sep 17 00:00:00 2001 From: Junichi Sugiura Date: Sun, 19 Nov 2023 18:55:34 +0200 Subject: [PATCH 016/192] Add page info field to connection object (#1177) * Add page info field with dummy data to conneciton object * query page info * Enable metadata feature for torii * Add page info field to connection object * Fix page info query * [WIP] Nullable page info * Return page info even for limit/offset based pagination --- crates/torii/graphql/src/constants.rs | 1 + .../graphql/src/object/connection/mod.rs | 10 +++ .../src/object/connection/page_info.rs | 26 ++++++ crates/torii/graphql/src/object/entity.rs | 4 +- crates/torii/graphql/src/object/event.rs | 4 +- .../torii/graphql/src/object/metadata/mod.rs | 17 ++-- crates/torii/graphql/src/object/mod.rs | 4 +- crates/torii/graphql/src/object/model_data.rs | 17 ++-- crates/torii/graphql/src/query/data.rs | 89 ++++++++++++++++++- crates/torii/graphql/src/query/order.rs | 4 +- .../torii/graphql/src/tests/entities_test.rs | 46 ++++++++++ .../torii/graphql/src/tests/metadata_test.rs | 6 ++ crates/torii/graphql/src/tests/mod.rs | 11 +++ crates/torii/graphql/src/tests/models_test.rs | 6 ++ 14 files changed, 226 insertions(+), 19 deletions(-) diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index 8bbfb7bbf7..0117ba18b8 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -21,6 +21,7 @@ pub const EVENT_TYPE_NAME: &str = "World__Event"; pub const SOCIAL_TYPE_NAME: &str = "World__Social"; pub const CONTENT_TYPE_NAME: &str = "World__Content"; pub const METADATA_TYPE_NAME: &str = "World__Metadata"; +pub const PAGE_INFO_TYPE_NAME: &str = "World__PageInfo"; pub const TRANSACTION_TYPE_NAME: &str = "World__Transaction"; pub const QUERY_TYPE_NAME: &str = "World__Query"; pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription"; diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index 44a8426cfa..63b796e31b 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -1,9 +1,13 @@ +use async_graphql::connection::PageInfo; +use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::{Field, InputValue, ResolverContext, TypeRef}; use async_graphql::{Error, Name, Value}; use sqlx::sqlite::SqliteRow; use sqlx::Row; +use self::page_info::PageInfoObject; use super::ObjectTrait; +use crate::constants::PAGE_INFO_TYPE_NAME; use crate::query::order::Order; use crate::query::value_mapping_from_row; use crate::types::{GraphqlType, TypeData, TypeMapping, ValueMapping}; @@ -37,6 +41,10 @@ impl ConnectionObject { TypeData::Simple(TypeRef::named_list(format!("{}Edge", type_name))), ), (Name::new("total_count"), TypeData::Simple(TypeRef::named_nn(TypeRef::INT))), + ( + Name::new("page_info"), + TypeData::Nested((TypeRef::named_nn(PAGE_INFO_TYPE_NAME), IndexMap::new())), + ), ]); Self { @@ -109,6 +117,7 @@ pub fn connection_output( id_column: &str, total_count: i64, is_external: bool, + page_info: PageInfo, ) -> sqlx::Result { let model_edges = data .iter() @@ -134,5 +143,6 @@ pub fn connection_output( Ok(ValueMapping::from([ (Name::new("total_count"), Value::from(total_count)), (Name::new("edges"), Value::List(model_edges?)), + (Name::new("page_info"), PageInfoObject::value(page_info)), ])) } diff --git a/crates/torii/graphql/src/object/connection/page_info.rs b/crates/torii/graphql/src/object/connection/page_info.rs index 6e44938a57..126f00f7aa 100644 --- a/crates/torii/graphql/src/object/connection/page_info.rs +++ b/crates/torii/graphql/src/object/connection/page_info.rs @@ -1,4 +1,7 @@ +use async_graphql::connection::PageInfo; +use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::Field; +use async_graphql::{Name, Value}; use crate::mapping::PAGE_INFO_TYPE_MAPPING; use crate::object::{ObjectTrait, TypeMapping}; @@ -26,3 +29,26 @@ impl ObjectTrait for PageInfoObject { None } } + +impl PageInfoObject { + pub fn value(page_info: PageInfo) -> Value { + Value::Object(IndexMap::from([ + (Name::new("has_previous_page"), Value::from(page_info.has_previous_page)), + (Name::new("has_next_page"), Value::from(page_info.has_next_page)), + ( + Name::new("start_cursor"), + match page_info.start_cursor { + Some(val) => Value::from(val), + None => Value::Null, + }, + ), + ( + Name::new("end_cursor"), + match page_info.end_cursor { + Some(val) => Value::from(val), + None => Value::Null, + }, + ), + ])) + } +} diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index c17f3555d6..4d9751573e 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -52,7 +52,7 @@ impl ObjectTrait for EntityObject { let connection = parse_connection_arguments(&ctx)?; let keys = parse_keys_argument(&ctx)?; let total_count = count_rows(&mut conn, ENTITY_TABLE, &keys, &None).await?; - let data = fetch_multiple_rows( + let (data, page_info) = fetch_multiple_rows( &mut conn, ENTITY_TABLE, EVENT_ID_COLUMN, @@ -60,6 +60,7 @@ impl ObjectTrait for EntityObject { &None, &None, &connection, + total_count, ) .await?; let results = connection_output( @@ -69,6 +70,7 @@ impl ObjectTrait for EntityObject { EVENT_ID_COLUMN, total_count, false, + page_info, )?; Ok(Some(Value::Object(results))) diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index dbbf0ec4bd..31b660c2b9 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -49,7 +49,7 @@ impl ObjectTrait for EventObject { let connection = parse_connection_arguments(&ctx)?; let keys = parse_keys_argument(&ctx)?; let total_count = count_rows(&mut conn, EVENT_TABLE, &keys, &None).await?; - let data = fetch_multiple_rows( + let (data, page_info) = fetch_multiple_rows( &mut conn, EVENT_TABLE, ID_COLUMN, @@ -57,6 +57,7 @@ impl ObjectTrait for EventObject { &None, &None, &connection, + total_count, ) .await?; let results = connection_output( @@ -66,6 +67,7 @@ impl ObjectTrait for EventObject { ID_COLUMN, total_count, false, + page_info, )?; Ok(Some(Value::Object(results))) diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index 6177e4d90d..4a00b2dd43 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -1,8 +1,10 @@ +use async_graphql::connection::PageInfo; use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; use async_graphql::{Name, Value}; use sqlx::sqlite::SqliteRow; use sqlx::{Pool, Row, Sqlite}; +use super::connection::page_info::PageInfoObject; use super::connection::{connection_arguments, cursor, parse_connection_arguments}; use super::ObjectTrait; use crate::constants::{ @@ -54,7 +56,7 @@ impl ObjectTrait for MetadataObject { let mut conn = ctx.data::>()?.acquire().await?; let connection = parse_connection_arguments(&ctx)?; let total_count = count_rows(&mut conn, &table_name, &None, &None).await?; - let data = fetch_multiple_rows( + let (data, page_info) = fetch_multiple_rows( &mut conn, &table_name, ID_COLUMN, @@ -62,11 +64,13 @@ impl ObjectTrait for MetadataObject { &None, &None, &connection, + total_count, ) .await?; // convert json field to value_mapping expected by content object - let results = metadata_connection_output(&data, &type_mapping, total_count)?; + let results = + metadata_connection_output(&data, &type_mapping, total_count, page_info)?; Ok(Some(Value::Object(results))) }) @@ -85,6 +89,7 @@ fn metadata_connection_output( data: &[SqliteRow], types: &TypeMapping, total_count: i64, + page_info: PageInfo, ) -> sqlx::Result { let edges = data .iter() @@ -107,9 +112,10 @@ fn metadata_connection_output( value_mapping.insert(Name::new("content"), Value::Object(content)); - let mut edge = ValueMapping::new(); - edge.insert(Name::new("node"), Value::Object(value_mapping)); - edge.insert(Name::new("cursor"), Value::String(cursor)); + let edge = ValueMapping::from([ + (Name::new("node"), Value::Object(value_mapping)), + (Name::new("cursor"), Value::String(cursor)), + ]); Ok(Value::Object(edge)) }) @@ -118,6 +124,7 @@ fn metadata_connection_output( Ok(ValueMapping::from([ (Name::new("total_count"), Value::from(total_count)), (Name::new("edges"), Value::List(edges?)), + (Name::new("page_info"), PageInfoObject::value(page_info)), ])) } diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index 34e2700caa..494449654f 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -96,7 +96,7 @@ pub trait ObjectTrait: Send + Sync { let mut conn = ctx.data::>()?.acquire().await?; let connection = parse_connection_arguments(&ctx)?; let total_count = count_rows(&mut conn, &table_name, &None, &None).await?; - let data = fetch_multiple_rows( + let (data, page_info) = fetch_multiple_rows( &mut conn, &table_name, ID_COLUMN, @@ -104,6 +104,7 @@ pub trait ObjectTrait: Send + Sync { &None, &None, &connection, + total_count, ) .await?; let results = connection_output( @@ -113,6 +114,7 @@ pub trait ObjectTrait: Send + Sync { ID_COLUMN, total_count, false, + page_info, )?; Ok(Some(Value::Object(results))) diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index be2628d620..aadd7ec784 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -90,7 +90,8 @@ impl ObjectTrait for ModelDataObject { let connection = parse_connection_arguments(&ctx)?; let id_column = "event_id"; - let data = fetch_multiple_rows( + let total_count = count_rows(&mut conn, &type_name, &None, &filters).await?; + let (data, page_info) = fetch_multiple_rows( &mut conn, &type_name, id_column, @@ -98,12 +99,18 @@ impl ObjectTrait for ModelDataObject { &order, &filters, &connection, + total_count, ) .await?; - - let total_count = count_rows(&mut conn, &type_name, &None, &filters).await?; - let connection = - connection_output(&data, &type_mapping, &order, id_column, total_count, true)?; + let connection = connection_output( + &data, + &type_mapping, + &order, + id_column, + total_count, + true, + page_info, + )?; Ok(Some(Value::Object(connection))) }) diff --git a/crates/torii/graphql/src/query/data.rs b/crates/torii/graphql/src/query/data.rs index 20511846f3..de04e12e49 100644 --- a/crates/torii/graphql/src/query/data.rs +++ b/crates/torii/graphql/src/query/data.rs @@ -1,6 +1,7 @@ +use async_graphql::connection::PageInfo; use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; -use sqlx::{Result, Sqlite}; +use sqlx::{Result, Row, Sqlite}; use super::filter::{Filter, FilterValue}; use super::order::{CursorDirection, Direction, Order}; @@ -34,6 +35,7 @@ pub async fn fetch_single_row( sqlx::query(&query).fetch_one(conn).await } +#[allow(clippy::too_many_arguments)] pub async fn fetch_multiple_rows( conn: &mut PoolConnection, table_name: &str, @@ -42,14 +44,17 @@ pub async fn fetch_multiple_rows( order: &Option, filters: &Option>, connection: &ConnectionArguments, -) -> Result> { + total_count: i64, +) -> Result<(Vec, PageInfo)> { let mut conditions = build_conditions(keys, filters); + let mut cursor_param = &connection.after; if let Some(after_cursor) = &connection.after { conditions.push(handle_cursor(after_cursor, order, CursorDirection::After, id_column)?); } if let Some(before_cursor) = &connection.before { + cursor_param = &connection.before; conditions.push(handle_cursor(before_cursor, order, CursorDirection::Before, id_column)?); } @@ -58,7 +63,18 @@ pub async fn fetch_multiple_rows( query.push_str(&format!(" WHERE {}", conditions.join(" AND "))); } - let limit = connection.first.or(connection.last).or(connection.limit).unwrap_or(DEFAULT_LIMIT); + let is_cursor_based = connection.first.or(connection.last).is_some() || cursor_param.is_some(); + + let data_limit = + connection.first.or(connection.last).or(connection.limit).unwrap_or(DEFAULT_LIMIT); + let limit = if is_cursor_based { + match &cursor_param { + Some(_) => data_limit + 2, + None => data_limit + 1, // prev page does not exist + } + } else { + data_limit + }; // NOTE: Order is determined by the `order` param if provided, otherwise it's inferred from the // `first` or `last` param. Explicit ordering take precedence @@ -89,7 +105,72 @@ pub async fn fetch_multiple_rows( query.push_str(&format!(" OFFSET {}", offset)); } - sqlx::query(&query).fetch_all(conn).await + let mut data = sqlx::query(&query).fetch_all(conn).await?; + let mut page_info = PageInfo { + has_previous_page: false, + has_next_page: false, + start_cursor: None, + end_cursor: None, + }; + + if data.is_empty() { + Ok((data, page_info)) + } else if is_cursor_based { + let order_field = match order { + Some(order) => format!("external_{}", order.field), + None => id_column.to_string(), + }; + + match cursor_param { + Some(cursor_query) => { + let first_cursor = cursor::encode( + &data[0].try_get::(id_column)?, + &data[0].try_get_unchecked::(&order_field)?, + ); + + if &first_cursor == cursor_query && data.len() != 1 { + data.remove(0); + page_info.has_previous_page = true; + } else { + data.pop(); + } + + if data.len() as u64 == limit - 1 { + page_info.has_next_page = true; + data.pop(); + } + } + None => { + if data.len() as u64 == limit { + page_info.has_next_page = true; + data.pop(); + } + } + } + + if !data.is_empty() { + page_info.start_cursor = Some(cursor::encode( + &data[0].try_get::(id_column)?, + &data[0].try_get_unchecked::(&order_field)?, + )); + page_info.end_cursor = Some(cursor::encode( + &data[data.len() - 1].try_get::(id_column)?, + &data[data.len() - 1].try_get_unchecked::(&order_field)?, + )); + } + + Ok((data, page_info)) + } else { + let offset = connection.offset.unwrap_or(0); + if 1 < offset && offset < total_count as u64 { + page_info.has_previous_page = true; + } + if limit + offset < total_count as u64 { + page_info.has_next_page = true; + } + + Ok((data, page_info)) + } } fn handle_cursor( diff --git a/crates/torii/graphql/src/query/order.rs b/crates/torii/graphql/src/query/order.rs index 7b5bb81271..d368d26525 100644 --- a/crates/torii/graphql/src/query/order.rs +++ b/crates/torii/graphql/src/query/order.rs @@ -15,8 +15,8 @@ pub struct Order { #[derive(AsRefStr, Debug, EnumString)] pub enum CursorDirection { - #[strum(serialize = "<")] + #[strum(serialize = "<=")] After, - #[strum(serialize = ">")] + #[strum(serialize = ">=")] Before, } diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index a2119ba986..cf2850268e 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -24,6 +24,12 @@ mod tests { model_names }} }} + page_info {{ + has_previous_page + has_next_page + start_cursor + end_cursor + }} }} }} "#, @@ -131,6 +137,11 @@ mod tests { assert_eq!(connection.edges.first().unwrap(), three); assert_eq!(connection.edges.last().unwrap(), four); + assert!(connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor.unwrap(), three.cursor); + assert_eq!(connection.page_info.end_cursor.unwrap(), four.cursor); + let entities = entities_query(&schema, &format!("(first: 3, after: \"{}\")", three.cursor)).await; let connection: Connection = serde_json::from_value(entities).unwrap(); @@ -138,6 +149,11 @@ mod tests { assert_eq!(connection.edges.first().unwrap(), four); assert_eq!(connection.edges.last().unwrap(), six); + assert!(connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor.unwrap(), four.cursor); + assert_eq!(connection.page_info.end_cursor.unwrap(), six.cursor); + // cursor based backward pagination let entities = entities_query(&schema, &format!("(last: 2, before: \"{}\")", seven.cursor)).await; @@ -146,6 +162,11 @@ mod tests { assert_eq!(connection.edges.first().unwrap(), six); assert_eq!(connection.edges.last().unwrap(), five); + assert!(connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor.unwrap(), six.cursor); + assert_eq!(connection.page_info.end_cursor.unwrap(), five.cursor); + let entities = entities_query(&schema, &format!("(last: 3, before: \"{}\")", six.cursor)).await; let connection: Connection = serde_json::from_value(entities).unwrap(); @@ -153,6 +174,11 @@ mod tests { assert_eq!(connection.edges.first().unwrap(), five); assert_eq!(connection.edges.last().unwrap(), three); + assert!(connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor.unwrap(), five.cursor); + assert_eq!(connection.page_info.end_cursor.unwrap(), three.cursor); + let empty_entities = entities_query( &schema, &format!( @@ -164,6 +190,11 @@ mod tests { let connection: Connection = serde_json::from_value(empty_entities).unwrap(); assert_eq!(connection.edges.len(), 0); + assert!(!connection.page_info.has_previous_page); + assert!(!connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor, None); + assert_eq!(connection.page_info.end_cursor, None); + // offset/limit based pagination let entities = entities_query(&schema, "(limit: 2)").await; let connection: Connection = serde_json::from_value(entities).unwrap(); @@ -171,16 +202,31 @@ mod tests { assert_eq!(connection.edges.first().unwrap(), one); assert_eq!(connection.edges.last().unwrap(), two); + assert!(!connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor, None); + assert_eq!(connection.page_info.end_cursor, None); + let entities = entities_query(&schema, "(limit: 3, offset: 2)").await; let connection: Connection = serde_json::from_value(entities).unwrap(); assert_eq!(connection.edges.len(), 3); assert_eq!(connection.edges.first().unwrap(), three); assert_eq!(connection.edges.last().unwrap(), five); + assert!(connection.page_info.has_previous_page); + assert!(connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor, None); + assert_eq!(connection.page_info.end_cursor, None); + let empty_entities = entities_query(&schema, "(limit: 1, offset: 20)").await; let connection: Connection = serde_json::from_value(empty_entities).unwrap(); assert_eq!(connection.edges.len(), 0); + assert!(!connection.page_info.has_previous_page); + assert!(!connection.page_info.has_next_page); + assert_eq!(connection.page_info.start_cursor, None); + assert_eq!(connection.page_info.end_cursor, None); + // entity model union let id = poseidon_hash_many(&[FieldElement::ZERO]); let entity = entity_model_query(&schema, &id).await; diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index 63285035b9..d6490421ff 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -33,6 +33,12 @@ mod tests { } } } + page_info { + has_previous_page + has_next_page + start_cursor + end_cursor + } } } "#; diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 50fb21229b..83c7a3839c 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -40,6 +40,7 @@ use crate::schema::build_schema; pub struct Connection { pub total_count: i64, pub edges: Vec>, + pub page_info: PageInfo, } #[derive(Deserialize, Debug, PartialEq)] @@ -55,6 +56,16 @@ pub struct Entity { pub created_at: Option, } +#[derive(Deserialize, Debug, PartialEq)] +// same as type from `async-graphql` but derive necessary traits +// https://docs.rs/async-graphql/6.0.10/async_graphql/types/connection/struct.PageInfo.html +pub struct PageInfo { + pub has_previous_page: bool, + pub has_next_page: bool, + pub start_cursor: Option, + pub end_cursor: Option, +} + #[derive(Deserialize, Debug)] pub struct Moves { pub __typename: String, diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index 0c6e3ca181..a27b97e314 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -58,6 +58,12 @@ mod tests { }} }} }} + page_info {{ + has_previous_page + has_next_page + start_cursor + end_cursor + }} }} }} "#, From 1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 20 Nov 2023 16:07:40 -0500 Subject: [PATCH 017/192] Prepare v0.3.11 --- .github/workflows/release.yml | 19 ++++++++++++ Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.toml | 4 +-- crates/dojo-defi/Scarb.toml | 2 +- crates/dojo-erc/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- crates/dojo-physics/Scarb.toml | 2 +- .../examples/projectile/Scarb.toml | 2 +- crates/dojo-primitives/Scarb.toml | 2 +- .../graphql/src/tests/types-test/Scarb.lock | 4 +-- examples/spawn-and-move/Scarb.lock | 6 ++-- examples/spawn-and-move/Scarb.toml | 2 +- 14 files changed, 50 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 022c4acdc7..82f1a1e26c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -191,6 +191,13 @@ jobs: body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} + + - name: Upload binaries + uses: actions/upload-artifact@v2 + with: + name: binaries + path: target/ + retention-days: 1 cleanup: name: Release cleanup @@ -226,6 +233,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Download binaries + uses: actions/download-artifact@v2 + with: + name: binaries + path: target/ + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -255,6 +268,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Download binaries + uses: actions/download-artifact@v2 + with: + name: binaries + path: target/ + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 diff --git a/Cargo.lock b/Cargo.lock index c7d80ecd4a..30dc4f0f66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2265,7 +2265,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2312,7 +2312,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2334,7 +2334,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "starknet", @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "assert_fs", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.10" +version = "0.3.11" dependencies = [ "crypto-bigint", "hex", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "assert_fs", @@ -4816,7 +4816,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.10" +version = "0.3.11" dependencies = [ "assert_matches", "clap", @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "assert_matches", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "assert_matches", @@ -7236,7 +7236,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "assert_fs", @@ -8267,7 +8267,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.10" +version = "0.3.11" dependencies = [ "async-trait", "camino", @@ -8293,7 +8293,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "async-trait", @@ -8328,7 +8328,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "async-graphql", @@ -8366,7 +8366,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.10" +version = "0.3.11" dependencies = [ "bytes", "dojo-types", @@ -8403,7 +8403,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.10" +version = "0.3.11" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 87147f1585..b398a41b1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.10" +version = "0.3.11" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index fa14470e63..451b316682 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,8 +2,8 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.10" +version = "0.3.11" [dependencies] -dojo_plugin = "0.3.10" +dojo_plugin = "0.3.11" starknet = "2.3.1" diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml index bef8294936..8b4fdbf3d1 100644 --- a/crates/dojo-defi/Scarb.toml +++ b/crates/dojo-defi/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of a defi primitives for the Dojo framework" name = "dojo_defi" -version = "0.3.10" +version = "0.3.11" [lib] diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml index 479680ec37..83105a6b8b 100644 --- a/crates/dojo-erc/Scarb.toml +++ b/crates/dojo-erc/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of ERC standards for the Dojo framework" name = "dojo_erc" -version = "0.3.10" +version = "0.3.11" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 09626b768f..d6108e0735 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.10" +version = "0.3.11" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 57472785e8..167b5875ab 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.10" +version = "0.3.11" [cairo] sierra-replace-ids = true diff --git a/crates/dojo-physics/Scarb.toml b/crates/dojo-physics/Scarb.toml index a94969353b..c23523ebaf 100644 --- a/crates/dojo-physics/Scarb.toml +++ b/crates/dojo-physics/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.0.0-rc4" description = "Implementations of a physics library for the Dojo framework" name = "dojo_physics" -version = "0.3.10" +version = "0.3.11" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-physics/examples/projectile/Scarb.toml b/crates/dojo-physics/examples/projectile/Scarb.toml index adc73c8825..c54bfcb9ee 100644 --- a/crates/dojo-physics/examples/projectile/Scarb.toml +++ b/crates/dojo-physics/examples/projectile/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "projectile" -version = "0.3.10" +version = "0.3.11" [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } diff --git a/crates/dojo-primitives/Scarb.toml b/crates/dojo-primitives/Scarb.toml index aadd64d41e..b25e0045fb 100644 --- a/crates/dojo-primitives/Scarb.toml +++ b/crates/dojo-primitives/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "Implementations of common primitives for the Dojo games" name = "dojo_primitives" -version = "0.3.10" +version = "0.3.11" [lib] diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index 6ad1bf2c03..59ad92bcaa 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,14 +3,14 @@ version = 1 [[package]] name = "dojo" -version = "0.3.10" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.10" +version = "0.3.11" [[package]] name = "types_test" diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 2370c6221a..f3b1ddcf45 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,18 +3,18 @@ version = 1 [[package]] name = "dojo" -version = "0.3.10" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.10" +version = "0.3.11" dependencies = [ "dojo", ] [[package]] name = "dojo_plugin" -version = "0.3.10" +version = "0.3.11" diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index ce7d799371..2e9d836793 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.10" +version = "0.3.11" [cairo] sierra-replace-ids = true From 63fb839378624f6419032fb1020590ea163d9af3 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 20 Nov 2023 21:40:42 -0500 Subject: [PATCH 018/192] Scope upload artifacts --- .github/workflows/release.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82f1a1e26c..df726fb4bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -192,11 +192,18 @@ jobs: files: | ${{ steps.artifacts.outputs.file_name }} + # Upload these for use with the Docker build later - name: Upload binaries uses: actions/upload-artifact@v2 with: name: binaries - path: target/ + path: | + target/x86_64-unknown-linux-gnu/release/katana + target/x86_64-unknown-linux-gnu/release/sozo + target/x86_64-unknown-linux-gnu/release/torii + target/aarch64-unknown-linux-gnu/release/katana + target/aarch64-unknown-linux-gnu/release/sozo + target/aarch64-unknown-linux-gnu/release/torii retention-days: 1 cleanup: From 3ad461ed620872972e5bebc101bdabf7d90ccc33 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 20 Nov 2023 21:58:39 -0500 Subject: [PATCH 019/192] Update plugin to pull dojo from repo (#1163) * Update plugin to pull dojo from repo * Set preceise --- .github/workflows/ci.yml | 6 +++ crates/dojo-core/Scarb.lock | 5 ++- crates/dojo-core/Scarb.toml | 2 +- crates/dojo-defi/Scarb.lock | 7 ++-- crates/dojo-erc/Scarb.lock | 7 ++-- crates/dojo-lang/build.rs | 13 ++++++ crates/dojo-lang/src/plugin.rs | 55 ++++++-------------------- crates/dojo-primitives/Scarb.lock | 7 ++-- crates/dojo-test-utils/build.rs | 13 +++++- crates/dojo-test-utils/src/compiler.rs | 3 +- crates/sozo/src/main.rs | 3 +- examples/spawn-and-move/Scarb.lock | 1 + scripts/prepare_release.sh | 3 ++ 13 files changed, 65 insertions(+), 60 deletions(-) create mode 100644 crates/dojo-lang/build.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d415ad857..f425a218ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: runs-on: ubuntu-latest-4-cores steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} @@ -126,6 +128,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} @@ -155,6 +159,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 86d07c2b2d..a6f55141e4 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,11 +3,12 @@ version = 1 [[package]] name = "dojo" -version = "0.3.6" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.6" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 451b316682..076c4523b2 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -5,5 +5,5 @@ name = "dojo" version = "0.3.11" [dependencies] -dojo_plugin = "0.3.11" +dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } starknet = "2.3.1" diff --git a/crates/dojo-defi/Scarb.lock b/crates/dojo-defi/Scarb.lock index 8699f8b528..5d0969e80a 100644 --- a/crates/dojo-defi/Scarb.lock +++ b/crates/dojo-defi/Scarb.lock @@ -8,14 +8,14 @@ source = "git+https://github.com/ponderingdemocritus/cubit#9c3bbdfee7b165ab06ac8 [[package]] name = "dojo" -version = "0.3.3" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_defi" -version = "0.3.3" +version = "0.3.11" dependencies = [ "cubit", "dojo", @@ -23,4 +23,5 @@ dependencies = [ [[package]] name = "dojo_plugin" -version = "0.3.3" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" diff --git a/crates/dojo-erc/Scarb.lock b/crates/dojo-erc/Scarb.lock index b00f21f531..35117caa94 100644 --- a/crates/dojo-erc/Scarb.lock +++ b/crates/dojo-erc/Scarb.lock @@ -3,18 +3,19 @@ version = 1 [[package]] name = "dojo" -version = "0.3.1" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_erc" -version = "0.3.1" +version = "0.3.11" dependencies = [ "dojo", ] [[package]] name = "dojo_plugin" -version = "0.3.1" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" diff --git a/crates/dojo-lang/build.rs b/crates/dojo-lang/build.rs new file mode 100644 index 0000000000..109f547faf --- /dev/null +++ b/crates/dojo-lang/build.rs @@ -0,0 +1,13 @@ +use std::env; +use std::process::Command; + +fn main() { + let version = env!("CARGO_PKG_VERSION"); + let output = Command::new("git") + .args(["rev-list", "-n", "1", &format!("v{version}")]) + .output() + .expect("Failed to execute command"); + + let git_hash = String::from_utf8(output.stdout).unwrap().trim().to_string(); + println!("cargo:rustc-env=GIT_HASH={}", git_hash); +} diff --git a/crates/dojo-lang/src/plugin.rs b/crates/dojo-lang/src/plugin.rs index 29b67145c9..8ade21a985 100644 --- a/crates/dojo-lang/src/plugin.rs +++ b/crates/dojo-lang/src/plugin.rs @@ -1,6 +1,3 @@ -use std::fs; -use std::fs::File; -use std::io::Write; use std::sync::Arc; use anyhow::Result; @@ -16,16 +13,14 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::ids::SyntaxStablePtrId; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; -use camino::{Utf8Path, Utf8PathBuf}; -use directories::ProjectDirs; use dojo_types::system::Dependency; use dojo_world::manifest::Member; -use lazy_static::lazy_static; use scarb::compiler::plugin::builtin::BuiltinStarkNetPlugin; use scarb::compiler::plugin::{CairoPlugin, CairoPluginInstance}; -use scarb::core::{DependencyVersionReq, ManifestDependency, PackageId, PackageName, SourceId}; +use scarb::core::{PackageId, PackageName, SourceId}; use semver::Version; use smol_str::SmolStr; +use url::Url; use crate::contract::DojoContract; use crate::inline_macros::emit::EmitMacro; @@ -94,27 +89,6 @@ pub const PACKAGE_NAME: &str = "dojo_plugin"; #[derive(Debug, Default)] pub struct BuiltinDojoPlugin; -lazy_static! { - static ref MANIFEST_VERSION: Version = Version::parse(env!("CARGO_PKG_VERSION")).expect("Manifest version not defined"); - static ref MANIFEST_PATH: Utf8PathBuf = { - let pd = ProjectDirs::from("com", "dojoengine", "sozo") - .expect("no valid home directory path could be retrieved from the operating system"); - - let content = include_str!("../Scarb.toml"); - let path = pd.cache_dir().join(MANIFEST_VERSION.to_string()).join("Scarb.toml"); - - // Create the directory if it doesn't exist - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).expect("Failed to create directory"); - } - - let mut file = File::create(&path).expect("unable to create file"); - write!(file, "{}", content).expect("unable to write to file"); - - Utf8Path::from_path(&path).expect("invalid UTF-8 path").to_path_buf() - }; -} - impl BuiltinDojoPlugin { fn handle_mod(&self, db: &dyn SyntaxGroup, module_ast: ast::ItemModule) -> PluginResult { if module_ast.has_attr(db, DOJO_CONTRACT_ATTR) { @@ -124,15 +98,6 @@ impl BuiltinDojoPlugin { PluginResult::default() } - pub fn manifest_dependency() -> ManifestDependency { - let version_req = DependencyVersionReq::exact(&MANIFEST_VERSION); - ManifestDependency::builder() - .name(PackageName::new(PACKAGE_NAME)) - .source_id(SourceId::for_path(MANIFEST_PATH.as_path()).unwrap()) - .version_req(version_req) - .build() - } - fn result_with_diagnostic( &self, stable_ptr: SyntaxStablePtrId, @@ -222,11 +187,17 @@ impl BuiltinDojoPlugin { impl CairoPlugin for BuiltinDojoPlugin { fn id(&self) -> PackageId { - PackageId::new( - PackageName::new(PACKAGE_NAME), - MANIFEST_VERSION.to_owned(), - SourceId::for_path(MANIFEST_PATH.as_path()).unwrap(), - ) + let url = Url::parse("https://github.com/dojoengine/dojo").unwrap(); + let version = env!("CARGO_PKG_VERSION"); + let rev = env!("GIT_HASH"); + + let source_id = + SourceId::for_git(&url, &scarb::core::GitReference::Tag(format!("v{version}").into())) + .unwrap() + .with_precise(rev.to_string()) + .unwrap(); + + PackageId::new(PackageName::new(PACKAGE_NAME), Version::parse(version).unwrap(), source_id) } fn instantiate(&self) -> Result> { diff --git a/crates/dojo-primitives/Scarb.lock b/crates/dojo-primitives/Scarb.lock index 6e779d4e2d..193cddd288 100644 --- a/crates/dojo-primitives/Scarb.lock +++ b/crates/dojo-primitives/Scarb.lock @@ -3,18 +3,19 @@ version = 1 [[package]] name = "dojo" -version = "0.3.1" +version = "0.3.11" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.1" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" [[package]] name = "dojo_primitives" -version = "0.3.1" +version = "0.3.11" dependencies = [ "dojo", ] diff --git a/crates/dojo-test-utils/build.rs b/crates/dojo-test-utils/build.rs index f1b17f19ed..7160be1859 100644 --- a/crates/dojo-test-utils/build.rs +++ b/crates/dojo-test-utils/build.rs @@ -1,15 +1,25 @@ #[cfg(feature = "build-examples")] fn main() { use std::env; + use std::process::Command; use camino::{Utf8Path, Utf8PathBuf}; use dojo_lang::compiler::DojoCompiler; - use dojo_lang::plugin::{BuiltinDojoPlugin, CairoPluginRepository}; + use dojo_lang::plugin::CairoPluginRepository; use scarb::compiler::CompilerRepository; use scarb::core::{Config, TargetKind}; use scarb::ops::{self, CompileOpts}; use scarb_ui::Verbosity; + let version = env!("CARGO_PKG_VERSION"); + let output = Command::new("git") + .args(["rev-list", "-n", "1", &format!("v{version}")]) + .output() + .expect("Failed to execute command"); + + let git_hash = String::from_utf8(output.stdout).unwrap().trim().to_string(); + println!("cargo:rustc-env=GIT_HASH={}", git_hash); + let project_paths = ["../../examples/spawn-and-move", "../torii/graphql/src/tests/types-test"]; project_paths.iter().for_each(|path| compile(path)); @@ -36,7 +46,6 @@ fn main() { .log_filter_directive(env::var_os("SCARB_LOG")) .compilers(compilers) .cairo_plugins(cairo_plugins.into()) - .custom_source_patches(vec![BuiltinDojoPlugin::manifest_dependency()]) .build() .unwrap(); diff --git a/crates/dojo-test-utils/src/compiler.rs b/crates/dojo-test-utils/src/compiler.rs index d5d9b60974..96113264e4 100644 --- a/crates/dojo-test-utils/src/compiler.rs +++ b/crates/dojo-test-utils/src/compiler.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use assert_fs::TempDir; use camino::{Utf8Path, Utf8PathBuf}; use dojo_lang::compiler::DojoCompiler; -use dojo_lang::plugin::{BuiltinDojoPlugin, CairoPluginRepository}; +use dojo_lang::plugin::CairoPluginRepository; use scarb::compiler::CompilerRepository; use scarb::core::Config; use scarb::ops; @@ -26,7 +26,6 @@ pub fn build_test_config(path: &str) -> anyhow::Result { .global_config_dir_override(Some(Utf8Path::from_path(config_dir.path()).unwrap())) .target_dir_override(Some(Utf8Path::from_path(target_dir.path()).unwrap().to_path_buf())) .ui_verbosity(Verbosity::Verbose) - .custom_source_patches(vec![BuiltinDojoPlugin::manifest_dependency()]) .log_filter_directive(env::var_os("SCARB_LOG")) .compilers(compilers) .cairo_plugins(cairo_plugins.into()) diff --git a/crates/sozo/src/main.rs b/crates/sozo/src/main.rs index 46f50ab274..93ec26d3ee 100644 --- a/crates/sozo/src/main.rs +++ b/crates/sozo/src/main.rs @@ -6,7 +6,7 @@ use anyhow::Result; use camino::Utf8PathBuf; use clap::Parser; use dojo_lang::compiler::DojoCompiler; -use dojo_lang::plugin::{BuiltinDojoPlugin, CairoPluginRepository}; +use dojo_lang::plugin::CairoPluginRepository; use scarb::compiler::CompilerRepository; use scarb::core::{Config, TomlManifest}; use scarb_ui::{OutputFormat, Ui}; @@ -42,7 +42,6 @@ fn cli_main(args: SozoArgs) -> Result<()> { .profile(args.profile_spec.determine()?) .offline(args.offline) .cairo_plugins(cairo_plugins.into()) - .custom_source_patches(vec![BuiltinDojoPlugin::manifest_dependency()]) .ui_verbosity(args.ui_verbosity()) .compilers(compilers) .build()?; diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index f3b1ddcf45..8c1d889a10 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -18,3 +18,4 @@ dependencies = [ [[package]] name = "dojo_plugin" version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 95cfb2d1a0..2659a79cca 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -9,4 +9,7 @@ find . -type f -name "*.toml" -exec sed -i "" "s/dojo_plugin = \"$prev_version\" scripts/clippy.sh +git checkout -b release/v$1 git commit -am "Prepare v$1" +git tag -a "v$1" -m "Version $1" +git push origin release/v$1 --tags \ No newline at end of file From 4d4eef68ce34e8ef8ac89a534bf15e6baf9bdbda Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 21 Nov 2023 09:07:38 -0500 Subject: [PATCH 020/192] add protoc to udeps ci --- .github/workflows/cargo-udeps.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cargo-udeps.yml b/.github/workflows/cargo-udeps.yml index a9d9a7cdc3..0e0ebdd7a3 100644 --- a/.github/workflows/cargo-udeps.yml +++ b/.github/workflows/cargo-udeps.yml @@ -22,6 +22,8 @@ jobs: # cargo-udeps require nightly to run toolchain: nightly + - uses: arduino/setup-protoc@v2 + - name: Install cargo-udeps run: cargo install --locked cargo-udeps From d6f2772c270db3415b675c9483d31dca317045d9 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 21 Nov 2023 09:14:44 -0500 Subject: [PATCH 021/192] Remove packages now part of origami (#1198) --- .github/workflows/ci.yml | 33 - crates/dojo-defi/.gitignore | 1 - crates/dojo-defi/README.md | 173 -- crates/dojo-defi/Scarb.lock | 27 - crates/dojo-defi/Scarb.toml | 14 - crates/dojo-defi/src/dutch_auction.cairo | 4 - .../dojo-defi/src/dutch_auction/common.cairo | 27 - crates/dojo-defi/src/dutch_auction/gda.cairo | 67 - .../dojo-defi/src/dutch_auction/vrgda.cairo | 121 -- crates/dojo-defi/src/lib.cairo | 4 - crates/dojo-defi/src/market.cairo | 3 - .../src/market/constant_product_market.cairo | 211 --- crates/dojo-defi/src/market/models.cairo | 72 - crates/dojo-defi/src/market/systems.cairo | 195 --- crates/dojo-defi/src/tests.cairo | 11 - .../tests/constant_product_market_tests.cairo | 188 --- .../src/tests/continuous_gda_test.cairo | 67 - .../src/tests/discrete_gda_test.cairo | 79 - .../src/tests/linear_vrgda_test.cairo | 54 - .../src/tests/logistic_vrgda_test.cairo | 62 - crates/dojo-defi/src/tests/utils.cairo | 25 - crates/dojo-erc/.gitignore | 1 - crates/dojo-erc/Scarb.lock | 21 - crates/dojo-erc/Scarb.toml | 29 - crates/dojo-erc/src/lib.cairo | 17 - crates/dojo-erc/src/tests/constants.cairo | 60 - crates/dojo-erc/src/tests/erc1155_tests.cairo | 836 ---------- crates/dojo-erc/src/tests/erc20_tests.cairo | 546 ------- crates/dojo-erc/src/tests/erc721_tests.cairo | 1453 ----------------- crates/dojo-erc/src/tests/test_erc1155.cairo | 629 ------- crates/dojo-erc/src/tests/test_erc721.cairo | 862 ---------- crates/dojo-erc/src/tests/utils.cairo | 32 - crates/dojo-erc/src/token/erc1155.cairo | 5 - .../dojo-erc/src/token/erc1155/erc1155.cairo | 439 ----- .../src/token/erc1155/interface.cairo | 62 - .../dojo-erc/src/token/erc1155/models.cairo | 33 - crates/dojo-erc/src/token/erc20.cairo | 331 ---- crates/dojo-erc/src/token/erc20_models.cairo | 30 - crates/dojo-erc/src/token/erc721.cairo | 5 - crates/dojo-erc/src/token/erc721/erc721.cairo | 443 ----- .../dojo-erc/src/token/erc721/interface.cairo | 60 - crates/dojo-erc/src/token/erc721/models.cairo | 48 - crates/dojo-physics/.gitignore | 1 - crates/dojo-physics/Scarb.toml | 11 - .../examples/projectile/Scarb.toml | 7 - .../examples/projectile/cairo_project.toml | 3 - .../examples/projectile/src/lib.cairo | 1 - .../examples/projectile/src/projectile.cairo | 245 --- crates/dojo-physics/src/lib.cairo | 1 - crates/dojo-physics/src/vec2.cairo | 261 --- crates/dojo-primitives/Scarb.lock | 21 - crates/dojo-primitives/Scarb.toml | 12 - .../dojo-primitives/src/commit_reveal.cairo | 31 - .../src/commit_reveal_test.cairo | 15 - crates/dojo-primitives/src/lib.cairo | 4 - scripts/cairo_test.sh | 4 - 56 files changed, 7997 deletions(-) delete mode 100644 crates/dojo-defi/.gitignore delete mode 100644 crates/dojo-defi/README.md delete mode 100644 crates/dojo-defi/Scarb.lock delete mode 100644 crates/dojo-defi/Scarb.toml delete mode 100644 crates/dojo-defi/src/dutch_auction.cairo delete mode 100644 crates/dojo-defi/src/dutch_auction/common.cairo delete mode 100644 crates/dojo-defi/src/dutch_auction/gda.cairo delete mode 100644 crates/dojo-defi/src/dutch_auction/vrgda.cairo delete mode 100644 crates/dojo-defi/src/lib.cairo delete mode 100644 crates/dojo-defi/src/market.cairo delete mode 100644 crates/dojo-defi/src/market/constant_product_market.cairo delete mode 100644 crates/dojo-defi/src/market/models.cairo delete mode 100644 crates/dojo-defi/src/market/systems.cairo delete mode 100644 crates/dojo-defi/src/tests.cairo delete mode 100644 crates/dojo-defi/src/tests/constant_product_market_tests.cairo delete mode 100644 crates/dojo-defi/src/tests/continuous_gda_test.cairo delete mode 100644 crates/dojo-defi/src/tests/discrete_gda_test.cairo delete mode 100644 crates/dojo-defi/src/tests/linear_vrgda_test.cairo delete mode 100644 crates/dojo-defi/src/tests/logistic_vrgda_test.cairo delete mode 100644 crates/dojo-defi/src/tests/utils.cairo delete mode 100644 crates/dojo-erc/.gitignore delete mode 100644 crates/dojo-erc/Scarb.lock delete mode 100644 crates/dojo-erc/Scarb.toml delete mode 100644 crates/dojo-erc/src/lib.cairo delete mode 100644 crates/dojo-erc/src/tests/constants.cairo delete mode 100644 crates/dojo-erc/src/tests/erc1155_tests.cairo delete mode 100644 crates/dojo-erc/src/tests/erc20_tests.cairo delete mode 100644 crates/dojo-erc/src/tests/erc721_tests.cairo delete mode 100644 crates/dojo-erc/src/tests/test_erc1155.cairo delete mode 100644 crates/dojo-erc/src/tests/test_erc721.cairo delete mode 100644 crates/dojo-erc/src/tests/utils.cairo delete mode 100644 crates/dojo-erc/src/token/erc1155.cairo delete mode 100644 crates/dojo-erc/src/token/erc1155/erc1155.cairo delete mode 100644 crates/dojo-erc/src/token/erc1155/interface.cairo delete mode 100644 crates/dojo-erc/src/token/erc1155/models.cairo delete mode 100644 crates/dojo-erc/src/token/erc20.cairo delete mode 100644 crates/dojo-erc/src/token/erc20_models.cairo delete mode 100644 crates/dojo-erc/src/token/erc721.cairo delete mode 100644 crates/dojo-erc/src/token/erc721/erc721.cairo delete mode 100644 crates/dojo-erc/src/token/erc721/interface.cairo delete mode 100644 crates/dojo-erc/src/token/erc721/models.cairo delete mode 100644 crates/dojo-physics/.gitignore delete mode 100644 crates/dojo-physics/Scarb.toml delete mode 100644 crates/dojo-physics/examples/projectile/Scarb.toml delete mode 100644 crates/dojo-physics/examples/projectile/cairo_project.toml delete mode 100644 crates/dojo-physics/examples/projectile/src/lib.cairo delete mode 100644 crates/dojo-physics/examples/projectile/src/projectile.cairo delete mode 100644 crates/dojo-physics/src/lib.cairo delete mode 100644 crates/dojo-physics/src/vec2.cairo delete mode 100644 crates/dojo-primitives/Scarb.lock delete mode 100644 crates/dojo-primitives/Scarb.toml delete mode 100644 crates/dojo-primitives/src/commit_reveal.cairo delete mode 100644 crates/dojo-primitives/src/commit_reveal_test.cairo delete mode 100644 crates/dojo-primitives/src/lib.cairo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f425a218ad..7f0c610c66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,39 +80,6 @@ jobs: - uses: arduino/setup-protoc@v2 - run: cargo run --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test - dojo-erc-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - - run: cargo run --bin sozo -- --manifest-path crates/dojo-erc/Scarb.toml test - - dojo-defi-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - - run: cargo run --bin sozo -- --manifest-path crates/dojo-defi/Scarb.toml test - - dojo-primitive-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - - run: cargo run --bin sozo -- --manifest-path crates/dojo-primitives/Scarb.toml test - dojo-spawn-and-move-example-test: runs-on: ubuntu-latest steps: diff --git a/crates/dojo-defi/.gitignore b/crates/dojo-defi/.gitignore deleted file mode 100644 index 1de565933b..0000000000 --- a/crates/dojo-defi/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/crates/dojo-defi/README.md b/crates/dojo-defi/README.md deleted file mode 100644 index d806f17ce4..0000000000 --- a/crates/dojo-defi/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# Gradual Dutch Auctions (GDA) - -## Introduction - -Gradual Dutch Auctions (GDA) enable efficient sales of assets without relying on liquid markets. GDAs offer a novel solution for selling both non-fungible tokens (NFTs) and fungible tokens through discrete and continuous mechanisms. - -## Discrete GDA - -### Motivation - -Discrete GDAs are perfect for selling NFTs in integer quantities. They offer an efficient way to conduct bulk purchases through a sequence of Dutch auctions. - -### Mechanism - -The process involves holding virtual Dutch auctions for each token, allowing for efficient clearing of batches. Price decay is exponential, controlled by a decay constant, and the starting price increases by a fixed scale factor. - -### Calculating Batch Purchase Prices - -Calculations can be made efficiently for purchasing a batch of auctions, following a given price function. - -## Continuous GDA - -### Motivation - -Continuous GDAs offer a mechanism for selling fungible tokens, allowing for constant rate emissions over time. - -### Mechanism - -The process works by incrementally making more assets available for sale, splitting sales into an infinite sequence of auctions. Various price functions, including exponential decay, can be applied. - -### Calculating Purchase Prices - -It's possible to compute the purchase price for any quantity of tokens gas-efficiently, using specific mathematical expressions. - -## How to Use - -### Discrete Gradual Dutch Auction - -The `DiscreteGDA` structure represents a Gradual Dutch Auction using discrete time steps. Here's how you can use it: - -#### Creating a Discrete GDA - -```rust -let gda = DiscreteGDA { - sold: Fixed::new_unscaled(0), - initial_price: Fixed::new_unscaled(100, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), // 1.1 - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), // 0.5, -}; -``` - -#### Calculating the Purchase Price - -You can calculate the purchase price for a specific quantity at a given time using the `purchase_price` method. - -```rust -let time_since_start = FixedTrait::new(2, false); // 2 days since the start, it must be scaled to avoid overflow. -let quantity = FixedTrait::new_unscaled(5, false); // Quantity to purchase -let price = gda.purchase_price(time_since_start, quantity); -``` - -### Continuous Gradual Dutch Auction - -The `ContinuousGDA` structure represents a Gradual Dutch Auction using continuous time steps. - -#### Creating a Continuous GDA - -```rust -let gda = ContinuousGDA { - initial_price: FixedTrait::new_unscaled(1000, false), - emission_rate: FixedTrait::ONE(), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), -}; -``` - -#### Calculating the Purchase Price - -Just like with the discrete version, you can calculate the purchase price for a specific quantity at a given time using the `purchase_price` method. - -```rust -let time_since_last = FixedTrait::new(1, false); // 1 day since the last purchase, it must be scaled to avoid overflow. -let quantity = FixedTrait::new_unscaled(3, false); // Quantity to purchase -let price = gda.purchase_price(time_since_last, quantity); -``` - ---- - -These examples demonstrate how to create instances of the `DiscreteGDA` and `ContinuousGDA` structures, and how to utilize their `purchase_price` methods to calculate the price for purchasing specific quantities at given times. - -You'll need to include the `cubit` crate in your project to work with the `Fixed` type and mathematical operations like `exp` and `pow`. Make sure to follow the respective documentation for additional details and proper integration into your project. - -## Conclusion - -GDAs present a powerful tool for selling both fungible and non-fungible tokens in various contexts. They offer efficient, flexible solutions for asset sales, opening doors to innovative applications beyond traditional markets. - -# Variable Rate GDAs (VRGDAs) - -## Overview - -Variable Rate GDAs (VRGDAs) enable the selling of tokens according to a custom schedule, raising or lowering prices based on the sales pace. VRGDA is a generalization of the GDA mechanism. - -## How to Use - -### Linear Variable Rate Gradual Dutch Auction (LinearVRGDA) - -The `LinearVRGDA` struct represents a linear auction where the price decays based on the target price, decay constant, and per-time-unit rate. - -#### Creating a LinearVRGDA instance - -```rust -const _69_42: u128 = 1280572973596917000000; -const _0_31: u128 = 5718490662849961000; - -let auction = LinearVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - per_time_unit: FixedTrait::new_unscaled(2, false), -}; -``` - -#### Calculating Target Sale Time - -```rust -let target_sale_time = auction.get_target_sale_time(sold_quantity); -``` - -#### Calculating VRGDA Price - -```rust -let price = auction.get_vrgda_price(time_since_start, sold_quantity); -``` - -### Logistic Variable Rate Gradual Dutch Auction (LogisticVRGDA) - -The `LogisticVRGDA` struct represents an auction where the price decays according to a logistic function, based on the target price, decay constant, max sellable quantity, and time scale. - -#### Creating a LogisticVRGDA instance - -```rust -const MAX_SELLABLE: u128 = 6392; -const _0_0023: u128 = 42427511369531970; - -let auction = LogisticVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), - time_scale: FixedTrait::new(_0_0023, false), -}; -``` - -#### Calculating Target Sale Time - -```rust -let target_sale_time = auction.get_target_sale_time(sold_quantity); -``` - -#### Calculating VRGDA Price - -```rust -let price = auction.get_vrgda_price(time_since_start, sold_quantity); -``` - -Make sure to import the required dependencies at the beginning of your Cairo file: - -```rust -use cubit::f128::types::fixed::{Fixed, FixedTrait}; -``` - -These examples show you how to create instances of both `LinearVRGDA` and `LogisticVRGDA` and how to use their methods to calculate the target sale time and VRGDA price. - -## Conclusion - -VRGDAs offer a flexible way to issue NFTs on nearly any schedule, enabling seamless purchases at any time. diff --git a/crates/dojo-defi/Scarb.lock b/crates/dojo-defi/Scarb.lock deleted file mode 100644 index 5d0969e80a..0000000000 --- a/crates/dojo-defi/Scarb.lock +++ /dev/null @@ -1,27 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "cubit" -version = "1.2.0" -source = "git+https://github.com/ponderingdemocritus/cubit#9c3bbdfee7b165ab06ac8cc7046ce4d4c8866bfc" - -[[package]] -name = "dojo" -version = "0.3.11" -dependencies = [ - "dojo_plugin", -] - -[[package]] -name = "dojo_defi" -version = "0.3.11" -dependencies = [ - "cubit", - "dojo", -] - -[[package]] -name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml deleted file mode 100644 index 8b4fdbf3d1..0000000000 --- a/crates/dojo-defi/Scarb.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -cairo-version = "2.3.1" -description = "Implementations of a defi primitives for the Dojo framework" -name = "dojo_defi" -version = "0.3.11" - -[lib] - -[dependencies] -cubit = { git = "https://github.com/ponderingdemocritus/cubit" } - -dojo = { path = "../dojo-core" } - -[[target.dojo]] diff --git a/crates/dojo-defi/src/dutch_auction.cairo b/crates/dojo-defi/src/dutch_auction.cairo deleted file mode 100644 index 9c0635143f..0000000000 --- a/crates/dojo-defi/src/dutch_auction.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod gda; -mod vrgda; -mod common; - diff --git a/crates/dojo-defi/src/dutch_auction/common.cairo b/crates/dojo-defi/src/dutch_auction/common.cairo deleted file mode 100644 index f323cb885f..0000000000 --- a/crates/dojo-defi/src/dutch_auction/common.cairo +++ /dev/null @@ -1,27 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait, ONE_u128}; -use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; - -fn to_days_fp(x: Fixed) -> Fixed { - x / FixedTrait::new(86400, false) -} - -fn from_days_fp(x: Fixed) -> Fixed { - x * FixedTrait::new(86400, false) -} - - -#[cfg(test)] -mod test_common { - use cubit::f128::types::fixed::{Fixed, FixedTrait, ONE_u128}; - use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; - - use super::{to_days_fp, from_days_fp}; - - #[test] - #[available_gas(20000000)] - fn test_days_convertions() { - let days = FixedTrait::new(2, false); - assert_approx_equal(days, to_days_fp(from_days_fp(days)), TOLERANCE * 10); - } -} - diff --git a/crates/dojo-defi/src/dutch_auction/gda.cairo b/crates/dojo-defi/src/dutch_auction/gda.cairo deleted file mode 100644 index f2d61b51ec..0000000000 --- a/crates/dojo-defi/src/dutch_auction/gda.cairo +++ /dev/null @@ -1,67 +0,0 @@ -use cubit::f128::math::core::{exp, pow}; -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use debug::PrintTrait; - -/// A Gradual Dutch Auction represented using discrete time steps. -/// The purchase price for a given quantity is calculated based on -/// the initial price, scale factor, decay constant, and the time since -/// the auction has started. -#[derive(Copy, Drop, Serde, starknet::Storage)] -struct DiscreteGDA { - sold: Fixed, - initial_price: Fixed, - scale_factor: Fixed, - decay_constant: Fixed, -} - -#[generate_trait] -impl DiscreteGDAImpl of DiscreteGDATrait { - /// Calculates the purchase price for a given quantity of the item at a specific time. - /// - /// # Arguments - /// - /// * `time_since_start`: Time since the start of the auction in days. - /// * `quantity`: Quantity of the item being purchased. - /// - /// # Returns - /// - /// * A `Fixed` representing the purchase price. - fn purchase_price(self: @DiscreteGDA, time_since_start: Fixed, quantity: Fixed) -> Fixed { - let num1 = *self.initial_price * pow(*self.scale_factor, *self.sold); - let num2 = pow(*self.scale_factor, quantity) - FixedTrait::ONE(); - let den1 = exp(*self.decay_constant * time_since_start); - let den2 = *self.scale_factor - FixedTrait::ONE(); - (num1 * num2) / (den1 * den2) - } -} - -/// A Gradual Dutch Auction represented using continuous time steps. -/// The purchase price is calculated based on the initial price, -/// emission rate, decay constant, and the time since the last purchase in days. -#[derive(Copy, Drop, Serde, starknet::Storage)] -struct ContinuousGDA { - initial_price: Fixed, - emission_rate: Fixed, - decay_constant: Fixed, -} - -#[generate_trait] -impl ContinuousGDAImpl of ContinuousGDATrait { - /// Calculates the purchase price for a given quantity of the item at a specific time. - /// - /// # Arguments - /// - /// * `time_since_last`: Time since the last purchase in the auction in days. - /// * `quantity`: Quantity of the item being purchased. - /// - /// # Returns - /// - /// * A `Fixed` representing the purchase price. - fn purchase_price(self: @ContinuousGDA, time_since_last: Fixed, quantity: Fixed) -> Fixed { - let num1 = *self.initial_price / *self.decay_constant; - let num2 = exp((*self.decay_constant * quantity) / *self.emission_rate) - FixedTrait::ONE(); - let den = exp(*self.decay_constant * time_since_last); - (num1 * num2) / den - } -} diff --git a/crates/dojo-defi/src/dutch_auction/vrgda.cairo b/crates/dojo-defi/src/dutch_auction/vrgda.cairo deleted file mode 100644 index 3790bfc64a..0000000000 --- a/crates/dojo-defi/src/dutch_auction/vrgda.cairo +++ /dev/null @@ -1,121 +0,0 @@ -use cubit::f128::math::core::{ln, abs, exp}; -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -/// A Linear Variable Rate Gradual Dutch Auction (VRGDA) struct. -/// Represents an auction where the price decays linearly based on the target price, -/// decay constant, and per-time-unit rate. -#[derive(Copy, Drop, Serde, starknet::Storage)] -struct LinearVRGDA { - target_price: Fixed, - decay_constant: Fixed, - per_time_unit: Fixed, -} - -#[generate_trait] -impl LinearVRGDAImpl of LinearVRGDATrait { - /// Calculates the target sale time based on the quantity sold. - /// - /// # Arguments - /// - /// * `sold`: Quantity sold. - /// - /// # Returns - /// - /// * A `Fixed` representing the target sale time. - fn get_target_sale_time(self: @LinearVRGDA, sold: Fixed) -> Fixed { - sold / *self.per_time_unit - } - - /// Calculates the VRGDA price at a specific time since the auction started. - /// - /// # Arguments - /// - /// * `time_since_start`: Time since the auction started. - /// * `sold`: Quantity sold. - /// - /// # Returns - /// - /// * A `Fixed` representing the price. - fn get_vrgda_price(self: @LinearVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { - *self.target_price - * exp( - *self.decay_constant - * (time_since_start - - self.get_target_sale_time(sold + FixedTrait::new(1, false))) - ) - } - - fn get_reverse_vrgda_price(self: @LinearVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { - *self.target_price - * exp( - (*self.decay_constant * FixedTrait::new(1, true)) - * (time_since_start - - self.get_target_sale_time(sold + FixedTrait::new(1, false))) - ) - } -} - - -#[derive(Copy, Drop, Serde, starknet::Storage)] -struct LogisticVRGDA { - target_price: Fixed, - decay_constant: Fixed, - max_sellable: Fixed, - time_scale: Fixed, -} - -// A Logistic Variable Rate Gradual Dutch Auction (VRGDA) struct. -/// Represents an auction where the price decays according to a logistic function, -/// based on the target price, decay constant, max sellable quantity, and time scale. -#[generate_trait] -impl LogisticVRGDAImpl of LogisticVRGDATrait { - /// Calculates the target sale time using a logistic function based on the quantity sold. - /// - /// # Arguments - /// - /// * `sold`: Quantity sold. - /// - /// # Returns - /// - /// * A `Fixed` representing the target sale time. - fn get_target_sale_time(self: @LogisticVRGDA, sold: Fixed) -> Fixed { - let logistic_limit = *self.max_sellable + FixedTrait::ONE(); - let logistic_limit_double = logistic_limit * FixedTrait::new_unscaled(2, false); - abs( - ln(logistic_limit_double / (sold + logistic_limit) - FixedTrait::ONE()) - / *self.time_scale - ) - } - - /// Calculates the VRGDA price at a specific time since the auction started, - /// using the logistic function. - /// - /// # Arguments - /// - /// * `time_since_start`: Time since the auction started. - /// * `sold`: Quantity sold. - /// - /// # Returns - /// - /// * A `Fixed` representing the price. - fn get_vrgda_price(self: @LogisticVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { - *self.target_price - * exp( - *self.decay_constant - * (time_since_start - - self.get_target_sale_time(sold + FixedTrait::new(1, false))) - ) - } - - fn get_reverse_vrgda_price( - self: @LogisticVRGDA, time_since_start: Fixed, sold: Fixed - ) -> Fixed { - *self.target_price - * exp( - (*self.decay_constant * FixedTrait::new(1, true)) - * (time_since_start - - self.get_target_sale_time(sold + FixedTrait::new(1, false))) - ) - } -} - diff --git a/crates/dojo-defi/src/lib.cairo b/crates/dojo-defi/src/lib.cairo deleted file mode 100644 index b764885332..0000000000 --- a/crates/dojo-defi/src/lib.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod market; -mod dutch_auction; -mod tests; - diff --git a/crates/dojo-defi/src/market.cairo b/crates/dojo-defi/src/market.cairo deleted file mode 100644 index cb5ccb799c..0000000000 --- a/crates/dojo-defi/src/market.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod models; -mod systems; -mod constant_product_market; diff --git a/crates/dojo-defi/src/market/constant_product_market.cairo b/crates/dojo-defi/src/market/constant_product_market.cairo deleted file mode 100644 index 1fd4839804..0000000000 --- a/crates/dojo-defi/src/market/constant_product_market.cairo +++ /dev/null @@ -1,211 +0,0 @@ -use starknet::ContractAddress; -use dojo_defi::market::models::Market; -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -const SCALING_FACTOR: u128 = 10000; - -#[generate_trait] -impl MarketImpl of MarketTrait { - fn buy(self: @Market, quantity: u128) -> u128 { - assert(quantity < *self.item_quantity, 'not enough liquidity'); - let (quantity, available, cash) = normalize(quantity, self); - let k = cash * available; - let cost = (k / (available - quantity)) - cash; - cost - } - - fn sell(self: @Market, quantity: u128) -> u128 { - let (quantity, available, cash) = normalize(quantity, self); - let k = cash * available; - let payout = cash - (k / (available + quantity)); - payout - } - - // Get normalized reserve cash amount and item quantity - fn get_reserves(self: @Market) -> (u128, u128) { - let reserve_quantity: u128 = (*self.item_quantity).into() * SCALING_FACTOR; - (*self.cash_amount, reserve_quantity) - } - - // Get the liquidity of the market - // Use cubit fixed point math library to compute the square root of the product of the reserves - fn liquidity(self: @Market) -> Fixed { - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // L = sqrt(X * Y) - (reserve_amount * reserve_quantity).sqrt() - } - - // Check if the market has liquidity - fn has_liquidity(self: @Market) -> bool { - *self.cash_amount > 0 || *self.item_quantity > 0 - } - - // Given some amount of cash, return the equivalent/optimal quantity of items - // based on the reserves in the market - fn quote_quantity(self: @Market, amount: u128) -> u128 { - assert(amount > 0, 'insufficient amount'); - assert(self.has_liquidity(), 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert amount to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // dy = Y * dx / X - let quantity_optimal = (reserve_quantity * amount) / reserve_amount; - - // Convert from fixed point to u128 - let res: u128 = quantity_optimal.try_into().unwrap(); - res - } - - // Given some quantity of items, return the equivalent/optimal amount of cash - // based on the reserves in the market - fn quote_amount(self: @Market, quantity: u128) -> u128 { - assert(quantity > 0, 'insufficient quantity'); - assert(self.has_liquidity(), 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // Normalize quantity - let quantity: u128 = quantity.into() * SCALING_FACTOR; - - // Convert quantity to fixed point - let quantity = FixedTrait::new_unscaled(quantity, false); - - // dx = X * dy / Y - let amount_optimal = (reserve_amount * quantity) / reserve_quantity; - - // Convert from fixed point to u128 - amount_optimal.try_into().unwrap() - } - - // Inner function to add liquidity to the market, computes the optimal amount and quantity - // - // Arguments: - // - // amount: The amount of cash to add to the market - // quantity: The quantity of items to add to the market - // - // Returns: - // - // (amount, quantity): The amount of cash and quantity of items added to the market - fn add_liquidity_inner(self: @Market, amount: u128, quantity: u128) -> (u128, u128) { - // If there is no liquidity, then the amount and quantity are the optimal - if !self.has_liquidity() { - // Ensure that the amount and quantity are greater than zero - assert(amount > 0, 'insufficient amount'); - assert(quantity > 0, 'insufficient quantity'); - (amount, quantity) - } else { - // Given the amount, get optimal quantity to add to the market - let quantity_optimal = self.quote_quantity(amount); - if quantity_optimal <= quantity { - // Add the given amount and optimal quantity to the market - (amount, quantity_optimal) - } else { - let amount_optimal = self.quote_amount(quantity); - // Ensure that the optimal amount is less than or equal to the given amount - assert(amount_optimal <= amount, 'insufficient amount'); - (amount_optimal, quantity) - } - } - } - - // Add liquidity to the market, mints shares for the given amount of liquidity provided - // - // Arguments: - // - // amount: The amount of cash to add to the market - // quantity: The quantity of items to add to the market - // - // Returns: - // - // (amount, quantity, shares): The amount of cash and quantity of items added to the market and the shares minted - fn add_liquidity(self: @Market, amount: u128, quantity: u128) -> (u128, u128, Fixed) { - // Compute the amount and quantity to add to the market - let (amount, quantity) = self.add_liquidity_inner(amount, quantity); - // Mint shares for the given amount of liquidity provided - let shares = self.mint_shares(amount, quantity); - (amount, quantity, shares) - } - - // Mint shares for the given amount of liquidity provided - fn mint_shares(self: @Market, amount: u128, quantity: u128) -> Fixed { - // If there is no liquidity, then mint total shares - if !self.has_liquidity() { - let quantity: u128 = quantity.into() * SCALING_FACTOR; - (FixedTrait::new_unscaled(amount, false) * FixedTrait::new_unscaled(quantity, false)) - .sqrt() - } else { - // Convert amount to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, _) = self.get_reserves(); - - // Convert reserve amount to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - - // Get total liquidity - let liquidity = self.liquidity(); - - // Compute the amount of shares to mint - // S = dx * L/X = dy * L/Y - (amount * liquidity) / reserve_amount - } - } - - // Remove liquidity from the market, return the corresponding amount and quantity payout - // - // Arguments: - // - // shares: The amount of liquidity shares to remove from the market - // - // Returns: - // - // (amount, quantity): The amount of cash and quantity of items removed from the market - fn remove_liquidity(self: @Market, shares: Fixed) -> (u128, u128) { - // Ensure that the market has liquidity - let liquidity = self.liquidity(); - assert(shares <= liquidity, 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // Compute the amount and quantity to remove from the market - // dx = S * X / L - let amount = (shares * reserve_amount) / liquidity; - // dy = S * Y / L - let quantity = (shares * reserve_quantity) / liquidity; - - // Convert amount and quantity both from fixed point to u128 and unscaled u128, respectively - (amount.try_into().unwrap(), quantity.try_into().unwrap() / SCALING_FACTOR) - } -} - -fn normalize(quantity: u128, market: @Market) -> (u128, u128, u128) { - let quantity: u128 = quantity.into() * SCALING_FACTOR; - let available: u128 = (*market.item_quantity).into() * SCALING_FACTOR; - (quantity, available, *market.cash_amount) -} diff --git a/crates/dojo-defi/src/market/models.cairo b/crates/dojo-defi/src/market/models.cairo deleted file mode 100644 index 1249b0a71a..0000000000 --- a/crates/dojo-defi/src/market/models.cairo +++ /dev/null @@ -1,72 +0,0 @@ -use starknet::ContractAddress; -use dojo::database::schema::{Struct, Ty, SchemaIntrospection, Member, serialize_member}; - -// Cubit fixed point math library -use cubit::f128::types::fixed::Fixed; - -const SCALING_FACTOR: u128 = 10000; - -impl SchemaIntrospectionFixed of SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - 2 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(128); - layout.append(1); - } - - #[inline(always)] - fn ty() -> Ty { - Ty::Struct( - Struct { - name: 'Fixed', - attrs: array![].span(), - children: array![ - serialize_member( - @Member { name: 'mag', ty: Ty::Primitive('u128'), attrs: array![].span() } - ), - serialize_member( - @Member { name: 'sign', ty: Ty::Primitive('bool'), attrs: array![].span() } - ) - ] - .span() - } - ) - } -} - -#[derive(Model, Copy, Drop, Serde)] -struct Cash { - #[key] - player: ContractAddress, - amount: u128, -} - -#[derive(Model, Copy, Drop, Serde)] -struct Item { - #[key] - player: ContractAddress, - #[key] - item_id: u32, - quantity: u128, -} - -#[derive(Model, Copy, Drop, Serde)] -struct Liquidity { - #[key] - player: ContractAddress, - #[key] - item_id: u32, - shares: Fixed, -} - -#[derive(Model, Copy, Drop, Serde)] -struct Market { - #[key] - item_id: u32, - cash_amount: u128, - item_quantity: u128, -} diff --git a/crates/dojo-defi/src/market/systems.cairo b/crates/dojo-defi/src/market/systems.cairo deleted file mode 100644 index ca37bc768b..0000000000 --- a/crates/dojo-defi/src/market/systems.cairo +++ /dev/null @@ -1,195 +0,0 @@ -use cubit::f128::types::fixed::Fixed; -use dojo::world::IWorldDispatcher; - - -trait ITrade { - fn buy(self: @TContractState, world: IWorldDispatcher, item_id: u32, quantity: u128); - fn sell(self: @TContractState, world: IWorldDispatcher, item_id: u32, quantity: u128); -} - - -#[dojo::contract] -mod Trade { - use dojo_defi::market::models::{Item, Cash, Market}; - use dojo_defi::market::constant_product_market::MarketTrait; - - use super::ITrade; - - - #[external(v0)] - impl TradeImpl of ITrade { - fn buy(self: @ContractState, world: IWorldDispatcher, item_id: u32, quantity: u128) { - - let player = starknet::get_caller_address(); - - let player_cash = get!(world, (player), Cash); - - let market = get!(world, (item_id), Market); - - let cost = market.buy(quantity); - assert(cost <= player_cash.amount, 'not enough cash'); - - // update market - set!( - world, - (Market { - item_id: item_id, - cash_amount: market.cash_amount + cost, - item_quantity: market.item_quantity - quantity, - }) - ); - - // update player cash - set!(world, (Cash { player: player, amount: player_cash.amount - cost })); - - // update player item - let item = get!(world, (player, item_id), Item); - set!( - world, - (Item { player: player, item_id: item_id, quantity: item.quantity + quantity }) - ); - } - - - - fn sell(self: @ContractState, world: IWorldDispatcher, item_id: u32, quantity: u128) { - let player = starknet::get_caller_address(); - - let item = get!(world, (player, item_id), Item); - let player_quantity = item.quantity; - assert(player_quantity >= quantity, 'not enough items'); - - let player_cash = get!(world, (player), Cash); - - let market = get!(world, (item_id), Market); - let payout = market.sell(quantity); - - // update market - set!( - world, - (Market { - item_id: item_id, - cash_amount: market.cash_amount - payout, - item_quantity: market.item_quantity + quantity, - }) - ); - - // update player cash - set!(world, (Cash { player: player, amount: player_cash.amount + payout })); - - // update player item - set!( - world, - (Item { player: player, item_id: item_id, quantity: player_quantity - quantity }) - ); - } - - } - -} - - - -trait ILiquidity { - fn add(self: @TContractState, world: IWorldDispatcher, item_id: u32, amount: u128, quantity: u128); - fn remove(self: @TContractState, world: IWorldDispatcher, item_id: u32, shares: Fixed); -} - - -#[dojo::contract] -mod Liquidity { - use cubit::f128::types::fixed::Fixed; - - use dojo_defi::market::models::{Item, Cash, Market, Liquidity}; - use dojo_defi::market::constant_product_market::MarketTrait; - - use super::ILiquidity; - - #[external(v0)] - impl LiquidityImpl of ILiquidity { - - fn add(self: @ContractState, world: IWorldDispatcher, item_id: u32, amount: u128, quantity: u128) { - let player = starknet::get_caller_address(); - - let item = get!(world, (player, item_id), Item); - let player_quantity = item.quantity; - assert(player_quantity >= quantity, 'not enough items'); - - let player_cash = get!(world, (player), Cash); - assert(amount <= player_cash.amount, 'not enough cash'); - - let market = get!(world, (item_id), Market); - let (cost_cash, cost_quantity, liquidity_shares) = market.add_liquidity(amount, quantity); - - // update market - set!( - world, - (Market { - item_id: item_id, - cash_amount: market.cash_amount + cost_cash, - item_quantity: market.item_quantity + cost_quantity - }) - ); - - // update player cash - set!(world, (Cash { player: player, amount: player_cash.amount - cost_cash })); - - // update player item - set!( - world, - (Item { player: player, item_id: item_id, quantity: player_quantity - cost_quantity }) - ); - - // update player liquidity - let player_liquidity = get!(world, (player, item_id), Liquidity); - set!( - world, - (Liquidity { - player: player, item_id: item_id, shares: player_liquidity.shares + liquidity_shares - }) - ); - } - - - fn remove(self: @ContractState, world: IWorldDispatcher, item_id: u32, shares: Fixed) { - let player = starknet::get_caller_address(); - - let player_liquidity = get!(world, (player, item_id), Liquidity); - assert(player_liquidity.shares >= shares, 'not enough shares'); - - let market = get!(world, (item_id), Market); - let (payout_cash, payout_quantity) = market.remove_liquidity(shares); - - // update market - set!( - world, - (Market { - item_id: item_id, - cash_amount: market.cash_amount - payout_cash, - item_quantity: market.item_quantity - payout_quantity - }) - ); - - // update player cash - let player_cash = get!(world, (player), Cash); - set!(world, (Cash { player: player, amount: player_cash.amount + payout_cash })); - - // update player item - let item = get!(world, (player, item_id), Item); - let player_quantity = item.quantity; - set!( - world, - (Item { player: player, item_id: item_id, quantity: player_quantity + payout_quantity }) - ); - - // update player liquidity - let player_liquidity = get!(world, (player, item_id), Liquidity); - set!( - world, - (Liquidity { - player: player, item_id: item_id, shares: player_liquidity.shares - shares - }) - ); - } - } -} diff --git a/crates/dojo-defi/src/tests.cairo b/crates/dojo-defi/src/tests.cairo deleted file mode 100644 index 178dfdfa03..0000000000 --- a/crates/dojo-defi/src/tests.cairo +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(test)] -mod discrete_gda_test; -#[cfg(test)] -mod continuous_gda_test; -#[cfg(test)] -mod linear_vrgda_test; -#[cfg(test)] -mod logistic_vrgda_test; -#[cfg(test)] -mod constant_product_market_tests; -mod utils; diff --git a/crates/dojo-defi/src/tests/constant_product_market_tests.cairo b/crates/dojo-defi/src/tests/constant_product_market_tests.cairo deleted file mode 100644 index 4d51200d4d..0000000000 --- a/crates/dojo-defi/src/tests/constant_product_market_tests.cairo +++ /dev/null @@ -1,188 +0,0 @@ -use traits::Into; - -use dojo_defi::market::models::Market; -use dojo_defi::market::constant_product_market::{MarketTrait, SCALING_FACTOR}; -use dojo_defi::tests::utils::{TOLERANCE, assert_approx_equal}; - -use cubit::f128::types::FixedTrait; - -#[test] -#[should_panic(expected: ('not enough liquidity',))] -fn test_not_enough_quantity() { - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 1 - }; // pool 1:1 - let cost = market.buy(10); -} - -#[test] -#[available_gas(100000)] -fn test_market_buy() { - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 - }; // pool 1:10 - let cost = market.buy(5); - assert(cost == SCALING_FACTOR * 1, 'wrong cost'); -} - -#[test] -#[available_gas(100000)] -fn test_market_sell() { - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 - }; // pool 1:10 - let payout = market.sell(5); - assert(payout == 3334, 'wrong payout'); -} - -#[test] -#[available_gas(500000)] -fn test_market_add_liquidity_no_initial() { - // Without initial liquidity - let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; - - // Add liquidity - let (amount, quantity) = (SCALING_FACTOR * 5, 5); // pool 1:1 - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert that the amount and quantity added are the same as the given amount and quantity - // and that the liquidity shares minted are the same as the entire liquidity - assert(amount_add == amount, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Convert amount and quantity to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - let quantity: u128 = quantity.into() * SCALING_FACTOR; - let quantity = FixedTrait::new_unscaled(quantity, false); - assert(liquidity_add == (amount * quantity).sqrt(), 'wrong liquidity'); -} - -#[test] -#[available_gas(600000)] -fn test_market_add_liquidity_optimal() { - // With initial liquidity - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 - }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Add liquidity with the same ratio - let (amount, quantity) = (SCALING_FACTOR * 2, 20); // pool 1:10 - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert - assert(amount_add == amount, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount, false); - let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Compute the expected liquidity shares - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - let final_liquidity = initial_liquidity + liquidity_add; - assert_approx_equal(expected_liquidity, final_liquidity, TOLERANCE); -} - -#[test] -#[available_gas(1000000)] -fn test_market_add_liquidity_not_optimal() { - // With initial liquidity - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 - }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Add liquidity without the same ratio - let (amount, quantity) = (SCALING_FACTOR * 2, 10); // pool 1:5 - - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert that the amount added is optimal even though the - // amount originally requested was not - let amount_optimal = SCALING_FACTOR * 1; - assert(amount_add == amount_optimal, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount_add, false); - let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Get expecteed liquidity - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - - let final_liquidity = initial_liquidity + liquidity_add; -// assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); -} - -#[test] -#[should_panic(expected: ('insufficient amount',))] -fn test_market_add_liquidity_insufficient_amount() { - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 - }; // pool 1:10 - // Adding 20 items requires (SCALING_FACTOR * 2) cash amount to maintain the ratio - // Therefore this should fail - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(SCALING_FACTOR * 1, 20); -} - -#[test] -#[available_gas(1000000)] -fn test_market_remove_liquidity() { - // With initial liquidity - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 - }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Remove half of the liquidity - let two = FixedTrait::new_unscaled(2, false); - let liquidity_remove = initial_liquidity / two; - - let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); - - // Assert that the amount and quantity removed are half of the initial amount and quantity - assert(amount_remove == SCALING_FACTOR * 1, 'wrong cash amount'); - assert(quantity_remove == 10, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 2 - amount_remove, false); - let expected_quantity: u128 = (20 - quantity_remove).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Get expecteed liquidity - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - - let final_liquidity = initial_liquidity - liquidity_remove; -// assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); -} - -#[test] -#[should_panic(expected: ('insufficient liquidity',))] -fn test_market_remove_liquidity_no_initial() { - // Without initial liquidity - let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; // pool 1:10 - - // Remove liquidity - let one = FixedTrait::new_unscaled(1, false); - - let (amount_remove, quantity_remove) = market.remove_liquidity(one); -} - -#[test] -#[should_panic(expected: ('insufficient liquidity',))] -fn test_market_remove_liquidity_more_than_available() { - // With initial liquidity - let market = Market { - item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 - }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Remove twice of the liquidity - let two = FixedTrait::new_unscaled(2, false); - let liquidity_remove = initial_liquidity * two; - - let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); -} diff --git a/crates/dojo-defi/src/tests/continuous_gda_test.cairo b/crates/dojo-defi/src/tests/continuous_gda_test.cairo deleted file mode 100644 index 0e404c11bb..0000000000 --- a/crates/dojo-defi/src/tests/continuous_gda_test.cairo +++ /dev/null @@ -1,67 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use dojo_defi::dutch_auction::gda::{ContinuousGDA, ContinuousGDATrait}; -use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; - -// ipynb with calculations at https://colab.research.google.com/drive/14elIFRXdG3_gyiI43tP47lUC_aClDHfB?usp=sharing -#[test] -#[available_gas(2000000)] -fn test_price_1() { - let auction = ContinuousGDA { - initial_price: FixedTrait::new_unscaled(1000, false), - emission_rate: FixedTrait::ONE(), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(22128445337405634000000, false); - let time_since_last = FixedTrait::new_unscaled(10, false); - let quantity = FixedTrait::new_unscaled(9, false); - let price: Fixed = auction.purchase_price(time_since_last, quantity); - assert_approx_equal(price, expected, TOLERANCE) -} - - -#[test] -#[available_gas(2000000)] -fn test_price_2() { - let auction = ContinuousGDA { - initial_price: FixedTrait::new_unscaled(1000, false), - emission_rate: FixedTrait::ONE(), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(89774852279643700000, false); - let time_since_last = FixedTrait::new_unscaled(20, false); - let quantity = FixedTrait::new_unscaled(8, false); - let price: Fixed = auction.purchase_price(time_since_last, quantity); - assert_approx_equal(price, expected, TOLERANCE) -} - -#[test] -#[available_gas(2000000)] -fn test_price_3() { - let auction = ContinuousGDA { - initial_price: FixedTrait::new_unscaled(1000, false), - emission_rate: FixedTrait::ONE(), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(20393925850936156000, false); - let time_since_last = FixedTrait::new_unscaled(30, false); - let quantity = FixedTrait::new_unscaled(15, false); - let price: Fixed = auction.purchase_price(time_since_last, quantity); - assert_approx_equal(price, expected, TOLERANCE) -} - -#[test] -#[available_gas(2000000)] -fn test_price_4() { - let auction = ContinuousGDA { - initial_price: FixedTrait::new_unscaled(1000, false), - emission_rate: FixedTrait::ONE(), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(3028401847768577000000, false); - let time_since_last = FixedTrait::new_unscaled(40, false); - let quantity = FixedTrait::new_unscaled(35, false); - let price: Fixed = auction.purchase_price(time_since_last, quantity); - assert_approx_equal(price, expected, TOLERANCE) -} - diff --git a/crates/dojo-defi/src/tests/discrete_gda_test.cairo b/crates/dojo-defi/src/tests/discrete_gda_test.cairo deleted file mode 100644 index ac0a52dc73..0000000000 --- a/crates/dojo-defi/src/tests/discrete_gda_test.cairo +++ /dev/null @@ -1,79 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use dojo_defi::dutch_auction::gda::{DiscreteGDA, DiscreteGDATrait}; -use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; - -#[test] -#[available_gas(2000000)] -fn test_initial_price() { - let auction = DiscreteGDA { - sold: FixedTrait::new_unscaled(0, false), - initial_price: FixedTrait::new_unscaled(1000, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let price = auction.purchase_price(FixedTrait::ZERO(), FixedTrait::ONE()); - assert_approx_equal(price, auction.initial_price, TOLERANCE) -} - -// ipynb with calculations at https://colab.research.google.com/drive/14elIFRXdG3_gyiI43tP47lUC_aClDHfB?usp=sharing -#[test] -#[available_gas(2000000)] -fn test_price_1() { - let auction = DiscreteGDA { - sold: FixedTrait::new_unscaled(1, false), - initial_price: FixedTrait::new_unscaled(1000, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(1856620062541316600000, false); - let price = auction - .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); - assert_approx_equal(price, expected, TOLERANCE) -} - -#[test] -#[available_gas(2000000)] -fn test_price_2() { - let auction = DiscreteGDA { - sold: FixedTrait::new_unscaled(2, false), - initial_price: FixedTrait::new_unscaled(1000, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), - decay_constant: FixedTrait::new(1, false) / FixedTrait::new(2, false), - }; - let expected = FixedTrait::new(2042282068795448600000, false); - let price = auction - .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); - assert_approx_equal(price, expected, TOLERANCE) -} - -#[test] -#[available_gas(2000000)] -fn test_price_3() { - let auction = DiscreteGDA { - sold: FixedTrait::new_unscaled(4, false), - initial_price: FixedTrait::new_unscaled(1000, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(2471161303242493000000, false); - let price = auction - .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); - assert_approx_equal(price, expected, TOLERANCE) -} - -#[test] -#[available_gas(2000000)] -fn test_price_4() { - let auction = DiscreteGDA { - sold: FixedTrait::new_unscaled(20, false), - initial_price: FixedTrait::new_unscaled(1000, false), - scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), - decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), - }; - let expected = FixedTrait::new(291, false); - let price = auction - .purchase_price(FixedTrait::new_unscaled(85, false), FixedTrait::new_unscaled(1, false), ); - assert_approx_equal(price, expected, TOLERANCE) -} - diff --git a/crates/dojo-defi/src/tests/linear_vrgda_test.cairo b/crates/dojo-defi/src/tests/linear_vrgda_test.cairo deleted file mode 100644 index 3a532d06e6..0000000000 --- a/crates/dojo-defi/src/tests/linear_vrgda_test.cairo +++ /dev/null @@ -1,54 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use dojo_defi::dutch_auction::common::{to_days_fp, from_days_fp}; -use dojo_defi::dutch_auction::vrgda::{LinearVRGDA, LinearVRGDATrait}; -use dojo_defi::tests::utils::assert_rel_approx_eq; - -const _69_42: u128 = 1280572973596917000000; -const _0_31: u128 = 5718490662849961000; -const DELTA_0_0005: u128 = 9223372036854776; -const DELTA_0_02: u128 = 368934881474191000; -const DELTA: u128 = 184467440737095; - -#[test] -#[available_gas(2000000)] -fn test_target_price() { - let auction = LinearVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - per_time_unit: FixedTrait::new_unscaled(2, false), - }; - let time = from_days_fp(auction.get_target_sale_time(FixedTrait::new(1, false))); - let cost = auction - .get_vrgda_price(to_days_fp(time + FixedTrait::new(1, false)), FixedTrait::ZERO()); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_0005, false)); -} - -#[test] -#[available_gas(20000000)] -fn test_pricing_basic() { - let auction = LinearVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - per_time_unit: FixedTrait::new_unscaled(2, false), - }; - let time_delta = FixedTrait::new(10368001, false); // 120 days - let num_mint = FixedTrait::new(239, true); - let cost = auction.get_vrgda_price(time_delta, num_mint); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); -} - -#[test] -#[available_gas(20000000)] -fn test_pricing_basic_reverse() { - let auction = LinearVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - per_time_unit: FixedTrait::new_unscaled(2, false), - }; - let time_delta = FixedTrait::new(10368001, false); // 120 days - let num_mint = FixedTrait::new(239, true); - let cost = auction.get_reverse_vrgda_price(time_delta, num_mint); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); -} - diff --git a/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo b/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo deleted file mode 100644 index c8e7a13cb2..0000000000 --- a/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo +++ /dev/null @@ -1,62 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use dojo_defi::dutch_auction::common::{from_days_fp}; -use dojo_defi::dutch_auction::vrgda::{LogisticVRGDA, LogisticVRGDATrait}; -use dojo_defi::tests::utils::assert_rel_approx_eq; - - -use debug::PrintTrait; -const _69_42: u128 = 1280572973596917000000; -const _0_31: u128 = 5718490662849961000; -const DELTA_0_0005: u128 = 9223372036854776; -const DELTA_0_02: u128 = 368934881474191000; -const MAX_SELLABLE: u128 = 6392; -const _0_0023: u128 = 42427511369531970; - -#[test] -#[available_gas(200000000)] -fn test_target_price() { - let auction = LogisticVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), - time_scale: FixedTrait::new(_0_0023, false), - }; - let time = from_days_fp(auction.get_target_sale_time(FixedTrait::new(1, false))); - - let cost = auction.get_vrgda_price(time + FixedTrait::new(1, false), FixedTrait::ZERO()); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_0005, false)); -} - -#[test] -#[available_gas(200000000)] -fn test_pricing_basic() { - let auction = LogisticVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), - time_scale: FixedTrait::new(_0_0023, false), - }; - let time_delta = FixedTrait::new(10368001, false); - let num_mint = FixedTrait::new(876, false); - - let cost = auction.get_vrgda_price(time_delta, num_mint); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); -} - -#[test] -#[available_gas(200000000)] -fn test_pricing_basic_reverse() { - let auction = LogisticVRGDA { - target_price: FixedTrait::new(_69_42, false), - decay_constant: FixedTrait::new(_0_31, false), - max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), - time_scale: FixedTrait::new(_0_0023, false), - }; - let time_delta = FixedTrait::new(10368001, false); - let num_mint = FixedTrait::new(876, false); - - let cost = auction.get_reverse_vrgda_price(time_delta, num_mint); - assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); -} - diff --git a/crates/dojo-defi/src/tests/utils.cairo b/crates/dojo-defi/src/tests/utils.cairo deleted file mode 100644 index 470af7258c..0000000000 --- a/crates/dojo-defi/src/tests/utils.cairo +++ /dev/null @@ -1,25 +0,0 @@ -use cubit::f128::types::fixed::{Fixed, FixedTrait}; - -use debug::PrintTrait; - -const TOLERANCE: u128 = 18446744073709550; // 0.001 - -fn assert_approx_equal(expected: Fixed, actual: Fixed, tolerance: u128) { - let left_bound = expected - FixedTrait::new(tolerance, false); - let right_bound = expected + FixedTrait::new(tolerance, false); - assert(left_bound <= actual && actual <= right_bound, 'Not approx eq'); -} - -fn assert_rel_approx_eq(a: Fixed, b: Fixed, max_percent_delta: Fixed) { - if b == FixedTrait::ZERO() { - assert(a == b, 'a should eq ZERO'); - } - let percent_delta = if a > b { - (a - b) / b - } else { - (b - a) / b - }; - - assert(percent_delta < max_percent_delta, 'a ~= b not satisfied'); -} - diff --git a/crates/dojo-erc/.gitignore b/crates/dojo-erc/.gitignore deleted file mode 100644 index 1de565933b..0000000000 --- a/crates/dojo-erc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/crates/dojo-erc/Scarb.lock b/crates/dojo-erc/Scarb.lock deleted file mode 100644 index 35117caa94..0000000000 --- a/crates/dojo-erc/Scarb.lock +++ /dev/null @@ -1,21 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "dojo" -version = "0.3.11" -dependencies = [ - "dojo_plugin", -] - -[[package]] -name = "dojo_erc" -version = "0.3.11" -dependencies = [ - "dojo", -] - -[[package]] -name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml deleted file mode 100644 index 83105a6b8b..0000000000 --- a/crates/dojo-erc/Scarb.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -cairo-version = "2.3.1" -description = "Implementations of ERC standards for the Dojo framework" -name = "dojo_erc" -version = "0.3.11" - -[cairo] -sierra-replace-ids = true - -[lib] - -[dependencies] -dojo = { path = "../dojo-core" } - -[scripts] -build = "sozo build" -migrate = "sozo migrate" -test = "sozo test" - -# Generate targets/manifest with sozo -[[target.dojo]] - - -[tool.dojo.env] -# Katana -rpc_url = "http://localhost:5050" -account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" -private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x4cb5561a3a2a19d14f67f71a9da59c9194e2bbb44e1774260a111094fbd8f39" \ No newline at end of file diff --git a/crates/dojo-erc/src/lib.cairo b/crates/dojo-erc/src/lib.cairo deleted file mode 100644 index 537938a7ba..0000000000 --- a/crates/dojo-erc/src/lib.cairo +++ /dev/null @@ -1,17 +0,0 @@ -mod token { - mod erc20; - mod erc20_models; - mod erc721; - mod erc1155; - -} - -#[cfg(test)] -mod tests { - mod constants; - mod utils; - - mod erc20_tests; - mod erc721_tests; - mod erc1155_tests; -} diff --git a/crates/dojo-erc/src/tests/constants.cairo b/crates/dojo-erc/src/tests/constants.cairo deleted file mode 100644 index 6487fc1836..0000000000 --- a/crates/dojo-erc/src/tests/constants.cairo +++ /dev/null @@ -1,60 +0,0 @@ -use starknet::ContractAddress; -use starknet::contract_address_const; - -const NAME: felt252 = 'NAME'; -const SYMBOL: felt252 = 'SYMBOL'; -const DECIMALS: u8 = 18_u8; -const SUPPLY: u256 = 2000; -const VALUE: u256 = 300; -const ROLE: felt252 = 'ROLE'; -const OTHER_ROLE: felt252 = 'OTHER_ROLE'; -const URI: felt252 = 'URI'; -const TOKEN_ID: u256 = 21; -const TOKEN_AMOUNT: u256 = 42; -const TOKEN_ID_2: u256 = 2; -const TOKEN_AMOUNT_2: u256 = 69; -const PUBKEY: felt252 = 'PUBKEY'; - -fn ADMIN() -> ContractAddress { - contract_address_const::<'ADMIN'>() -} - -fn AUTHORIZED() -> ContractAddress { - contract_address_const::<'AUTHORIZED'>() -} - -fn ZERO() -> ContractAddress { - contract_address_const::<0>() -} - -fn CALLER() -> ContractAddress { - contract_address_const::<'CALLER'>() -} - -fn OWNER() -> ContractAddress { - contract_address_const::<'OWNER'>() -} - -fn NEW_OWNER() -> ContractAddress { - contract_address_const::<'NEW_OWNER'>() -} - -fn OTHER() -> ContractAddress { - contract_address_const::<'OTHER'>() -} - -fn OTHER_ADMIN() -> ContractAddress { - contract_address_const::<'OTHER_ADMIN'>() -} - -fn SPENDER() -> ContractAddress { - contract_address_const::<'SPENDER'>() -} - -fn RECIPIENT() -> ContractAddress { - contract_address_const::<'RECIPIENT'>() -} - -fn OPERATOR() -> ContractAddress { - contract_address_const::<'OPERATOR'>() -} diff --git a/crates/dojo-erc/src/tests/erc1155_tests.cairo b/crates/dojo-erc/src/tests/erc1155_tests.cairo deleted file mode 100644 index 6951695b51..0000000000 --- a/crates/dojo-erc/src/tests/erc1155_tests.cairo +++ /dev/null @@ -1,836 +0,0 @@ -use dojo_erc::tests::utils; -use dojo_erc::tests::constants::{ - ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID, TOKEN_AMOUNT, - TOKEN_ID_2, TOKEN_AMOUNT_2 -}; - -use dojo_erc::token::erc1155::ERC1155::ERC1155Impl; -use dojo_erc::token::erc1155::ERC1155::ERC1155CamelOnlyImpl; -use dojo_erc::token::erc1155::ERC1155::ERC1155MetadataImpl; -use dojo_erc::token::erc1155::ERC1155::InternalImpl; -use dojo_erc::token::erc1155::ERC1155::WorldInteractionsImpl; -use dojo_erc::token::erc1155::ERC1155::{TransferSingle, TransferBatch, ApprovalForAll}; -use dojo_erc::token::erc1155::ERC1155; -use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::testing; -use zeroable::Zeroable; -use dojo::test_utils::spawn_test_world; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::token::erc1155::models::{ - ERC1155Meta, erc_1155_meta, ERC1155OperatorApproval, erc_1155_operator_approval, ERC1155Balance, - erc_1155_balance -}; -use dojo_erc::token::erc1155::ERC1155::_worldContractMemberStateTrait; -use debug::PrintTrait; - -// -// Setup -// - -fn STATE() -> (IWorldDispatcher, ERC1155::ContractState) { - let world = spawn_test_world( - array![ - erc_1155_meta::TEST_CLASS_HASH, - erc_1155_operator_approval::TEST_CLASS_HASH, - erc_1155_balance::TEST_CLASS_HASH, - ] - ); - let mut state = ERC1155::contract_state_for_testing(); - state._world.write(world.contract_address); - - InternalImpl::_mint(ref state, OWNER(), TOKEN_ID, TOKEN_AMOUNT); - utils::drop_event(ZERO()); - - InternalImpl::_mint(ref state, OWNER(), TOKEN_ID_2, TOKEN_AMOUNT_2); - utils::drop_event(ZERO()); - - (world, state) -} - -fn setup() -> ERC1155::ContractState { - let (world, mut state) = STATE(); - ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); - utils::drop_event(ZERO()); - state -} - -// -// initializer & constructor -// - -#[test] -#[available_gas(20000000)] -fn test_constructor() { - let (world, mut state) = STATE(); - ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); - - assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - assert(ERC1155MetadataImpl::uri(@state, 0) == URI, 'Uri should be URI'); -// assert( -// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_ID), 'Missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_METADATA_ID), -// 'missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), -// 'missing interface ID' -// ); -} - -#[test] -#[available_gas(20000000)] -fn test_initializer() { - let (world, mut state) = STATE(); - InternalImpl::initializer(ref state, NAME, SYMBOL, URI); - - assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - - assert(ERC1155Impl::balance_of(@state, OWNER(), 0) == 0, 'Balance should be zero'); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'should be TOKEN_AMOUNT' - ); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'should be TOKEN_AMOUNT_2' - ); -} - - -// -// Getters -// - -#[test] -#[available_gas(20000000)] -fn test_balance_of() { - let mut state = setup(); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Should return balance' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid account',))] -fn test_balance_of_zero() { - let state = setup(); - ERC1155Impl::balance_of(@state, ZERO(), TOKEN_ID); -} - - -#[test] -#[available_gas(20000000)] -fn test_balance_of_batch() { - let mut state = setup(); - - InternalImpl::_mint(ref state, OTHER(), TOKEN_ID_2, TOKEN_AMOUNT_2); - - let balances = ERC1155Impl::balance_of_batch( - @state, array![OWNER(), OTHER()], array![TOKEN_ID, TOKEN_ID_2] - ); - - assert(*balances.at(0) == TOKEN_AMOUNT, 'Should return TOKEN_AMOUNT'); - assert(*balances.at(1) == TOKEN_AMOUNT_2, 'Should return TOKEN_AMOUNT_2'); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid account',))] -fn test_balance_of_batch_zero() { - let state = setup(); - ERC1155Impl::balance_of_batch(@state, array![OTHER(), ZERO()], array![TOKEN_ID_2, TOKEN_ID]); -} - - -// -// set_approval_for_all & _set_approval_for_all -// - -#[test] -#[available_gas(20000000)] -fn test_set_approval_for_all() { - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - - assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); - - ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); - assert_event_approval_for_all(OWNER(), OPERATOR(), true); - - assert( - ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); - - ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), false); - assert_event_approval_for_all(OWNER(), OPERATOR(), false); - - assert( - !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Approval not revoked correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: self approval',))] -fn test_set_approval_for_all_owner_equal_operator_true() { - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - ERC1155Impl::set_approval_for_all(ref state, OWNER(), true); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: self approval',))] -fn test_set_approval_for_all_owner_equal_operator_false() { - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - ERC1155Impl::set_approval_for_all(ref state, OWNER(), false); -} - -#[test] -#[available_gas(20000000)] -fn test__set_approval_for_all() { - let (world, mut state) = STATE(); - assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); - - InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); - assert_event_approval_for_all(OWNER(), OPERATOR(), true); - - assert( - ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); - - InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); - assert_event_approval_for_all(OWNER(), OPERATOR(), false); - - assert( - !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: self approval',))] -fn test__set_approval_for_all_owner_equal_operator_true() { - let (world, mut state) = STATE(); - InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: self approval',))] -fn test__set_approval_for_all_owner_equal_operator_false() { - let (world, mut state) = STATE(); - InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); -} - - -// -// safe_transfer_from & safeTransferFrom -// - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_owner() { - let mut state = setup(); - let id = TOKEN_ID; - let amount = TOKEN_AMOUNT; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, id); - - testing::set_caller_address(owner); - ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); - assert_event_transfer_single(owner, recipient, id, amount); - - assert_state_after_transfer(@state, owner, recipient, id); -} - -#[test] -#[available_gas(50000000)] -fn test_transferFrom_owner() { - let mut state = setup(); - let id = TOKEN_ID; - let amount = TOKEN_AMOUNT; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, id); - - testing::set_caller_address(owner); - ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); - assert_event_transfer_single(owner, recipient, id, amount); - - assert_state_after_transfer(@state, owner, recipient, id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: wrong sender',))] -fn test_safe_transfer_from_zero() { - let (world, mut state) = STATE(); - ERC1155Impl::safe_transfer_from( - ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: wrong sender',))] -fn test_safeTransferFrom_zero() { - let (world, mut state) = STATE(); - ERC1155CamelOnlyImpl::safeTransferFrom( - ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid receiver',))] -fn test_safe_transfer_from_to_zero() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC1155Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![]); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid receiver',))] -fn test_safeTransferFrom_to_zero() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC1155CamelOnlyImpl::safeTransferFrom( - ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_to_owner() { - let mut state = setup(); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before' - ); - - testing::set_caller_address(OWNER()); - ERC1155Impl::safe_transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![]); - assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' - ); -} - -#[test] -#[available_gas(50000000)] -fn test_safeTransferFrom_to_owner() { - let mut state = setup(); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before' - ); - - testing::set_caller_address(OWNER()); - ERC1155CamelOnlyImpl::safeTransferFrom( - ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); - assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' - ); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_approved_for_all() { - let mut state = setup(); - let id = TOKEN_ID; - let amount = TOKEN_AMOUNT; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, id); - - testing::set_caller_address(owner); - ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); - assert_event_transfer_single(owner, recipient, id, amount); - - assert_state_after_transfer(@state, owner, recipient, id); -} - -#[test] -#[available_gas(50000000)] -fn test_safeTransferFrom_approved_for_all() { - let mut state = setup(); - let id = TOKEN_ID; - let amount = TOKEN_AMOUNT; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, id); - - testing::set_caller_address(owner); - ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); - assert_event_transfer_single(owner, recipient, id, amount); - - assert_state_after_transfer(@state, owner, recipient, id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: unauthorized caller',))] -fn test_safe_transfer_from_unauthorized() { - let mut state = setup(); - testing::set_caller_address(OTHER()); - ERC1155Impl::safe_transfer_from( - ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: unauthorized caller',))] -fn test_safeTransferFrom_unauthorized() { - let mut state = setup(); - testing::set_caller_address(OTHER()); - ERC1155CamelOnlyImpl::safeTransferFrom( - ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] - ); -} - - -// -// safe_batch_transfer_from & safeBatchTransferFrom -// - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_from_owner() { - let mut state = setup(); - let owner = OWNER(); - let recipient = RECIPIENT(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert_state_before_batch_transfer(@state, owner, recipient); - - testing::set_caller_address(owner); - ERC1155Impl::safe_batch_transfer_from( - ref state, owner, recipient, ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(owner, recipient, ids, amounts); - - assert_state_after_batch_transfer(@state, owner, recipient); -} - -#[test] -#[available_gas(50000000)] -fn test_safeBatchTransferFrom_owner() { - let mut state = setup(); - let owner = OWNER(); - let recipient = RECIPIENT(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert_state_before_batch_transfer(@state, owner, recipient); - - testing::set_caller_address(owner); - ERC1155CamelOnlyImpl::safeBatchTransferFrom( - ref state, owner, recipient, ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(owner, recipient, ids, amounts); - - assert_state_after_batch_transfer(@state, owner, recipient); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: wrong sender',))] -fn test_safe_batch_transfer_from_zero() { - let (world, mut state) = STATE(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - ERC1155Impl::safe_batch_transfer_from(ref state, ZERO(), RECIPIENT(), ids, amounts, array![]); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: wrong sender',))] -fn test_safeBatchTransferFrom_zero() { - let (world, mut state) = STATE(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - ERC1155CamelOnlyImpl::safeBatchTransferFrom( - ref state, ZERO(), RECIPIENT(), ids, amounts, array![] - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid receiver',))] -fn test_safe_batch_transfer_from_to_zero() { - let (world, mut state) = STATE(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid receiver',))] -fn test_safeBatchTransferFrom_to_zero() { - let (world, mut state) = STATE(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_from_to_owner() { - let mut state = setup(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before1' - ); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of owner before1' - ); - - testing::set_caller_address(OWNER()); - ERC1155Impl::safe_batch_transfer_from( - ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner after1' - ); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of owner after2' - ); -} - -#[test] -#[available_gas(50000000)] -fn test_safeBatchTransferFrom_to_owner() { - let mut state = setup(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before1' - ); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of owner before1' - ); - - testing::set_caller_address(OWNER()); - ERC1155CamelOnlyImpl::safeBatchTransferFrom( - ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner after1' - ); - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of owner after2' - ); -} - -#[test] -#[available_gas(50000000)] -fn test_batch_transfer_from_approved_for_all() { - let mut state = setup(); - let owner = OWNER(); - let recipient = RECIPIENT(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert_state_before_batch_transfer(@state, owner, recipient); - - testing::set_caller_address(owner); - ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC1155Impl::safe_batch_transfer_from( - ref state, owner, recipient, ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(owner, recipient, ids, amounts); - - assert_state_after_batch_transfer(@state, owner, recipient); -} - -#[test] -#[available_gas(50000000)] -fn test_safeBatchTransferFrom_approved_for_all() { - let mut state = setup(); - let owner = OWNER(); - let recipient = RECIPIENT(); - - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - assert_state_before_batch_transfer(@state, owner, recipient); - - testing::set_caller_address(owner); - ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC1155CamelOnlyImpl::safeBatchTransferFrom( - ref state, owner, recipient, ids.clone(), amounts.clone(), array![] - ); - assert_event_transfer_batch(owner, recipient, ids, amounts); - - assert_state_after_batch_transfer(@state, owner, recipient); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: unauthorized caller',))] -fn test_safe_batch_transfer_from_unauthorized() { - let mut state = setup(); - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - testing::set_caller_address(OTHER()); - ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), RECIPIENT(), ids, amounts, array![]); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: unauthorized caller',))] -fn test_safeBatchTransferFrom_unauthorized() { - let mut state = setup(); - let ids = array![TOKEN_ID, TOKEN_ID_2]; - let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; - - testing::set_caller_address(OTHER()); - ERC1155CamelOnlyImpl::safeBatchTransferFrom( - ref state, OWNER(), RECIPIENT(), ids, amounts, array![] - ); -} - -// -// _mint -// - -#[test] -#[available_gas(20000000)] -fn test__mint() { - let (world, mut state) = STATE(); - let recipient = RECIPIENT(); - - assert( - ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before' - ); - - InternalImpl::_mint(ref state, recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); - assert_event_transfer_single(ZERO(), recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); - - assert( - ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of recipient after' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: invalid receiver',))] -fn test__mint_to_zero() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, ZERO(), TOKEN_ID, TOKEN_AMOUNT); -} - - -// -// _burn -// - -#[test] -#[available_gas(20000000)] -fn test__burn() { - let mut state = setup(); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before' - ); - - testing::set_caller_address(OWNER()); - InternalImpl::_burn(ref state, TOKEN_ID, 2); - assert_event_transfer_single(OWNER(), ZERO(), TOKEN_ID, 2); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT - 2, - 'Balance of owner after' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC1155: insufficient balance',))] -fn test__burn_more_than_balance() { - let mut state = setup(); - - assert( - ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of owner before' - ); - InternalImpl::_burn(ref state, TOKEN_ID, TOKEN_AMOUNT + 1); -} - - -// -// Helpers -// - -fn assert_state_before_transfer( - state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256, -) { - assert(ERC1155Impl::balance_of(state, owner, id) == TOKEN_AMOUNT, 'Balance of owner before'); - assert(ERC1155Impl::balance_of(state, recipient, id) == 0, 'Balance of recipient before'); -} - -fn assert_state_after_transfer( - state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256 -) { - assert(ERC1155Impl::balance_of(state, owner, id) == 0, 'Balance of owner after'); - assert( - ERC1155Impl::balance_of(state, recipient, id) == TOKEN_AMOUNT, 'Balance of recipient after' - ); -} - -fn assert_state_before_batch_transfer( - state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress -) { - assert( - ERC1155Impl::balance_of(state, owner, TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner before1' - ); - assert( - ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of owner before2' - ); - assert( - ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == 0, 'Balance of recipient before1' - ); - assert( - ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before2' - ); -} - -fn assert_state_after_batch_transfer( - state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress -) { - assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID) == 0, 'Balance of owner after1'); - assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == 0, 'Balance of owner after2'); - assert( - ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == TOKEN_AMOUNT, - 'Balance of recipient after1' - ); - assert( - ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, - 'Balance of recipient after2' - ); -} - - -// -// events -// - -fn assert_event_approval_for_all( - owner: ContractAddress, operator: ContractAddress, approved: bool -) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.owner == owner, 'Invalid `owner`'); - assert(event.operator == operator, 'Invalid `operator`'); - assert(event.approved == approved, 'Invalid `approved`'); - utils::assert_no_events_left(ZERO()); -} - -fn assert_event_transfer_single( - from: ContractAddress, to: ContractAddress, id: u256, amount: u256 -) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.from == from, 'Invalid `from`'); - assert(event.to == to, 'Invalid `to`'); - assert(event.id == id, 'Invalid `id`'); - assert(event.value == amount, 'Invalid `amount`'); - utils::assert_no_events_left(ZERO()); -} - -fn assert_event_transfer_batch( - from: ContractAddress, to: ContractAddress, ids: Array, amounts: Array -) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.from == from, 'Invalid `from`'); - assert(event.to == to, 'Invalid `to`'); - assert(event.ids.len() == event.values.len(), 'Invalid array length'); - - let mut i = 0; - - loop { - if i == event.ids.len() { - break; - } - - assert(event.ids.at(i) == ids.at(i), 'Invalid `id`'); - assert(event.values.at(i) == amounts.at(i), 'Invalid `id`'); - - i += 1; - }; - - utils::assert_no_events_left(ZERO()); -} - diff --git a/crates/dojo-erc/src/tests/erc20_tests.cairo b/crates/dojo-erc/src/tests/erc20_tests.cairo deleted file mode 100644 index 669cfc7c4d..0000000000 --- a/crates/dojo-erc/src/tests/erc20_tests.cairo +++ /dev/null @@ -1,546 +0,0 @@ -use integer::BoundedInt; -use integer::u256; -use integer::u256_from_felt252; -use dojo_erc::tests::utils; -use dojo_erc::tests::constants::{ - ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE -}; -use dojo_erc::token::erc20::ERC20::Approval; -use dojo_erc::token::erc20::ERC20::ERC20Impl; -use dojo_erc::token::erc20::ERC20::InternalImpl; -use dojo_erc::token::erc20::ERC20::Transfer; -use dojo_erc::token::erc20::ERC20; -use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::testing; -use zeroable::Zeroable; -use dojo::test_utils::spawn_test_world; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::token::erc20_models::{ - ERC20Allowance, erc_20_allowance, ERC20Balance, erc_20_balance, ERC20Meta, erc_20_meta -}; -use dojo_erc::token::erc20::ERC20::_worldContractMemberStateTrait; -use debug::PrintTrait; - -// -// Setup -// - -fn STATE() -> (IWorldDispatcher, ERC20::ContractState) { - let world = spawn_test_world( - array![ - erc_20_allowance::TEST_CLASS_HASH, - erc_20_balance::TEST_CLASS_HASH, - erc_20_meta::TEST_CLASS_HASH, - ] - ); - let mut state = ERC20::contract_state_for_testing(); - state._world.write(world.contract_address); - (world, state) -} - -fn setup() -> ERC20::ContractState { - let (world, mut state) = STATE(); - ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); - utils::drop_event(ZERO()); - state -} - -// -// initializer & constructor -// - -#[test] -#[available_gas(25000000)] -fn test_initializer() { - let (world, mut state) = STATE(); - InternalImpl::initializer(ref state, NAME, SYMBOL); - - assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); - assert(ERC20Impl::total_supply(@state) == 0, 'Supply should eq 0'); -} - - -#[test] -#[available_gas(25000000)] -fn test_constructor() { - let (world, mut state) = STATE(); - ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); - - assert_only_event_transfer(ZERO(), OWNER(), SUPPLY); - - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq inital_supply'); - assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq inital_supply'); - assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); -} - -// -// Getters -// - -#[test] -#[available_gas(25000000)] -fn test_total_supply() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, OWNER(), SUPPLY); - assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq SUPPLY'); -} - -#[test] -#[available_gas(25000000)] -fn test_balance_of() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, OWNER(), SUPPLY); - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq SUPPLY'); -} - - -#[test] -#[available_gas(25000000)] -fn test_allowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Should eq VALUE'); -} - -// -// approve & _approve -// - -#[test] -#[available_gas(25000000)] -fn test_approve() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - assert(ERC20Impl::approve(ref state, SPENDER(), VALUE), 'Should return true'); - - assert_only_event_approval(OWNER(), SPENDER(), VALUE); - assert( - ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' - ); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve from 0',))] -fn test_approve_from_zero() { - let mut state = setup(); - ERC20Impl::approve(ref state, SPENDER(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve to 0',))] -fn test_approve_to_zero() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -fn test__approve() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - InternalImpl::_approve(ref state, OWNER(), SPENDER(), VALUE); - - assert_only_event_approval(OWNER(), SPENDER(), VALUE); - assert( - ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' - ); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve from 0',))] -fn test__approve_from_zero() { - let mut state = setup(); - InternalImpl::_approve(ref state, Zeroable::zero(), SPENDER(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve to 0',))] -fn test__approve_to_zero() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - InternalImpl::_approve(ref state, OWNER(), Zeroable::zero(), VALUE); -} - -// -// transfer & _transfer -// - -#[test] -#[available_gas(25000000)] -fn test_transfer() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - assert(ERC20Impl::transfer(ref state, RECIPIENT(), VALUE), 'Should return true'); - - assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq VALUE'); - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - VALUE'); - assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); -} - -#[test] -#[available_gas(25000000)] -fn test__transfer() { - let mut state = setup(); - - InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), VALUE); - - assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq amount'); - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); - assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test__transfer_not_enough_balance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - - let balance_plus_one = SUPPLY + 1; - InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), balance_plus_one); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: transfer from 0',))] -fn test__transfer_from_zero() { - let mut state = setup(); - InternalImpl::_transfer(ref state, Zeroable::zero(), RECIPIENT(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: transfer to 0',))] -fn test__transfer_to_zero() { - let mut state = setup(); - InternalImpl::_transfer(ref state, OWNER(), Zeroable::zero(), VALUE); -} - -// -// transfer_from -// - -#[test] -#[available_gas(30000000)] -fn test_transfer_from() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - utils::drop_event(ZERO()); - - testing::set_caller_address(SPENDER()); - assert(ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE), 'Should return true'); - - assert_event_approval(OWNER(), SPENDER(), 0); - assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - - assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Should eq amount'); - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq suppy - amount'); - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == 0, 'Should eq 0'); - assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); -} - -#[test] -#[available_gas(25000000)] -fn test_transfer_from_doesnt_consume_infinite_allowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), BoundedInt::max()); - - testing::set_caller_address(SPENDER()); - ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE); - - assert( - ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), - 'Allowance should not change' - ); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_transfer_from_greater_than_allowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - - testing::set_caller_address(SPENDER()); - let allowance_plus_one = VALUE + 1; - ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), allowance_plus_one); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: transfer to 0',))] -fn test_transfer_from_to_zero_address() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - - testing::set_caller_address(SPENDER()); - ERC20Impl::transfer_from(ref state, OWNER(), Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_transfer_from_from_zero_address() { - let mut state = setup(); - ERC20Impl::transfer_from(ref state, Zeroable::zero(), RECIPIENT(), VALUE); -} - -// -// increase_allowance & increaseAllowance -// - -#[test] -#[available_gas(25000000)] -fn test_increase_allowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - utils::drop_event(ZERO()); - - assert(ERC20::increase_allowance(ref state, SPENDER(), VALUE), 'Should return true'); - - assert_only_event_approval(OWNER(), SPENDER(), VALUE * 2); - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve to 0',))] -fn test_increase_allowance_to_zero_address() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20::increase_allowance(ref state, Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve from 0',))] -fn test_increase_allowance_from_zero_address() { - let mut state = setup(); - ERC20::increase_allowance(ref state, SPENDER(), VALUE); -} - -#[test] -#[available_gas(25000000)] -fn test_increaseAllowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - utils::drop_event(ZERO()); - - assert(ERC20::increaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); - - assert_only_event_approval(OWNER(), SPENDER(), 2 * VALUE); - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve to 0',))] -fn test_increaseAllowance_to_zero_address() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20::increaseAllowance(ref state, Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: approve from 0',))] -fn test_increaseAllowance_from_zero_address() { - let mut state = setup(); - ERC20::increaseAllowance(ref state, SPENDER(), VALUE); -} - -// -// decrease_allowance & decreaseAllowance -// - -#[test] -#[available_gas(25000000)] -fn test_decrease_allowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - utils::drop_event(ZERO()); - - assert(ERC20::decrease_allowance(ref state, SPENDER(), VALUE), 'Should return true'); - - assert_only_event_approval(OWNER(), SPENDER(), 0); - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_decrease_allowance_to_zero_address() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20::decrease_allowance(ref state, Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_decrease_allowance_from_zero_address() { - let mut state = setup(); - ERC20::decrease_allowance(ref state, SPENDER(), VALUE); -} - -#[test] -#[available_gas(25000000)] -fn test_decreaseAllowance() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20Impl::approve(ref state, SPENDER(), VALUE); - utils::drop_event(ZERO()); - - assert(ERC20::decreaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); - - assert_only_event_approval(OWNER(), SPENDER(), 0); - assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_decreaseAllowance_to_zero_address() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC20::decreaseAllowance(ref state, Zeroable::zero(), VALUE); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('u256_sub Overflow',))] -fn test_decreaseAllowance_from_zero_address() { - let mut state = setup(); - ERC20::decreaseAllowance(ref state, SPENDER(), VALUE); -} - -// -// _spend_allowance -// - -#[test] -#[available_gas(25000000)] -fn test__spend_allowance_not_unlimited() { - let mut state = setup(); - - InternalImpl::_approve(ref state, OWNER(), SPENDER(), SUPPLY); - utils::drop_event(ZERO()); - - InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), VALUE); - - assert_only_event_approval(OWNER(), SPENDER(), SUPPLY - VALUE); - assert( - ERC20Impl::allowance(@state, OWNER(), SPENDER()) == SUPPLY - VALUE, - 'Should eq supply - amount' - ); -} - -#[test] -#[available_gas(25000000)] -fn test__spend_allowance_unlimited() { - let mut state = setup(); - InternalImpl::_approve(ref state, OWNER(), SPENDER(), BoundedInt::max()); - - let max_minus_one: u256 = BoundedInt::max() - 1; - InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), max_minus_one); - - assert( - ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), - 'Allowance should not change' - ); -} - -// -// _mint -// - -#[test] -#[available_gas(25000000)] -fn test__mint() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, OWNER(), VALUE); - assert_only_event_transfer(ZERO(), OWNER(), VALUE); - assert(ERC20Impl::balance_of(@state, OWNER()) == VALUE, 'Should eq amount'); - assert(ERC20Impl::total_supply(@state) == VALUE, 'Should eq total supply'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: mint to 0',))] -fn test__mint_to_zero() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, Zeroable::zero(), VALUE); -} - -// -// _burn -// - -#[test] -#[available_gas(25000000)] -fn test__burn() { - let mut state = setup(); - InternalImpl::_burn(ref state, OWNER(), VALUE); - - assert_only_event_transfer(OWNER(), ZERO(), VALUE); - assert(ERC20Impl::total_supply(@state) == SUPPLY - VALUE, 'Should eq supply - amount'); - assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); -} - -#[test] -#[available_gas(25000000)] -#[should_panic(expected: ('ERC20: burn from 0',))] -fn test__burn_from_zero() { - let mut state = setup(); - InternalImpl::_burn(ref state, Zeroable::zero(), VALUE); -} - -// -// Helpers -// - -fn assert_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.owner == owner, 'Invalid `owner`'); - assert(event.spender == spender, 'Invalid `spender`'); - assert(event.value == value, 'Invalid `value`'); -} - -fn assert_only_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { - assert_event_approval(owner, spender, value); - utils::assert_no_events_left(ZERO()); -} - -fn assert_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.from == from, 'Invalid `from`'); - assert(event.to == to, 'Invalid `to`'); - assert(event.value == value, 'Invalid `value`'); -} - -fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { - assert_event_transfer(from, to, value); - utils::assert_no_events_left(ZERO()); -} diff --git a/crates/dojo-erc/src/tests/erc721_tests.cairo b/crates/dojo-erc/src/tests/erc721_tests.cairo deleted file mode 100644 index 415db0ff16..0000000000 --- a/crates/dojo-erc/src/tests/erc721_tests.cairo +++ /dev/null @@ -1,1453 +0,0 @@ -use integer::u256; -use integer::u256_from_felt252; -use dojo_erc::tests::utils; -use dojo_erc::tests::constants::{ - ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID -}; - -use dojo_erc::token::erc721::ERC721::ERC721Impl; -use dojo_erc::token::erc721::ERC721::ERC721CamelOnlyImpl; -use dojo_erc::token::erc721::ERC721::ERC721MetadataImpl; -use dojo_erc::token::erc721::ERC721::InternalImpl; -use dojo_erc::token::erc721::ERC721::WorldInteractionsImpl; -use dojo_erc::token::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; -use dojo_erc::token::erc721::ERC721; -use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::testing; -use zeroable::Zeroable; -use dojo::test_utils::spawn_test_world; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::token::erc721::models::{ - ERC721Meta, erc_721_meta, ERC721OperatorApproval, erc_721_operator_approval, ERC721Owner, - erc_721_owner, ERC721Balance, erc_721_balance, ERC721TokenApproval, erc_721_token_approval -}; -use dojo_erc::token::erc721::ERC721::_worldContractMemberStateTrait; -use debug::PrintTrait; - -// -// Setup -// - -fn STATE() -> (IWorldDispatcher, ERC721::ContractState) { - let world = spawn_test_world( - array![ - erc_721_meta::TEST_CLASS_HASH, - erc_721_operator_approval::TEST_CLASS_HASH, - erc_721_owner::TEST_CLASS_HASH, - erc_721_balance::TEST_CLASS_HASH, - erc_721_token_approval::TEST_CLASS_HASH, - ] - ); - let mut state = ERC721::contract_state_for_testing(); - state._world.write(world.contract_address); - (world, state) -} - -fn setup() -> ERC721::ContractState { - let (world, mut state) = STATE(); - ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); - utils::drop_event(ZERO()); - state -} - -// fn setup_receiver() -> ContractAddress { -// utils::deploy(ERC721Receiver::TEST_CLASS_HASH, array![]) -// } - -// fn setup_camel_receiver() -> ContractAddress { -// utils::deploy(CamelERC721ReceiverMock::TEST_CLASS_HASH, array![]) -// } - -// fn setup_account() -> ContractAddress { -// let mut calldata = array![PUBKEY]; -// utils::deploy(Account::TEST_CLASS_HASH, calldata) -// } - -// fn setup_camel_account() -> ContractAddress { -// let mut calldata = array![PUBKEY]; -// utils::deploy(CamelAccountMock::TEST_CLASS_HASH, calldata) -// } - -// -// initializer & constructor -// - -#[test] -#[available_gas(20000000)] -fn test_constructor() { - let (world, mut state) = STATE(); - ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); - - assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance should be one'); - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'OWNER should be owner'); -// assert( -// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), -// 'missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), -// 'missing interface ID' -// ); -} - -#[test] -#[available_gas(10000000)] -fn test_initializer() { - let (world, mut state) = STATE(); - InternalImpl::initializer(ref state, NAME, SYMBOL, URI); - - assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); - assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); - - assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance should be zero'); -// assert( -// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), -// 'missing interface ID' -// ); -// assert( -// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), -// 'missing interface ID' -// ); -} - - -// -// Getters -// - -#[test] -#[available_gas(20000000)] -fn test_balance_of() { - let state = setup(); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Should return balance'); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid account',))] -fn test_balance_of_zero() { - let state = setup(); - ERC721Impl::balance_of(@state, ZERO()); -} - -#[test] -#[available_gas(20000000)] -fn test_owner_of() { - let state = setup(); - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Should return owner'); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_owner_of_non_minted() { - let state = setup(); - ERC721Impl::owner_of(@state, u256_from_felt252(7)); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_token_uri_non_minted() { - let state = setup(); - ERC721MetadataImpl::token_uri(@state, u256_from_felt252(7)); -} - -#[test] -#[available_gas(20000000)] -fn test_get_approved() { - let mut state = setup(); - let spender = SPENDER(); - let token_id = TOKEN_ID; - - assert(ERC721Impl::get_approved(@state, token_id) == ZERO(), 'Should return non-approval'); - InternalImpl::_approve(ref state, spender, token_id); - assert(ERC721Impl::get_approved(@state, token_id) == spender, 'Should return approval'); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_get_approved_nonexistent() { - let mut state = setup(); - ERC721Impl::get_approved(@state, u256_from_felt252(7)); -} - -#[test] -#[available_gas(20000000)] -fn test__exists() { - let (world, mut state) = STATE(); - let token_id = TOKEN_ID; - - assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); - assert( - WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' - ); - - InternalImpl::_mint(ref state, RECIPIENT(), token_id); - - assert(InternalImpl::_exists(@state, token_id), 'Token should exist'); - assert( - WorldInteractionsImpl::get_owner_of(@state, token_id).address == RECIPIENT(), - 'Invalid owner' - ); - - InternalImpl::_burn(ref state, token_id); - - assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); - assert( - WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' - ); -} - - -// -// approve & _approve -// - -#[test] -#[available_gas(20000000)] -fn test_approve_from_owner() { - let mut state = setup(); - - testing::set_caller_address(OWNER()); - ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); - assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); - - assert( - ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' - ); -} - -#[test] -#[available_gas(20000000)] -fn test_approve_from_operator() { - let mut state = setup(); - - testing::set_caller_address(OWNER()); - ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); - assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); - - assert( - ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: unauthorized caller',))] -fn test_approve_from_unauthorized() { - let mut state = setup(); - - testing::set_caller_address(OTHER()); - ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: approval to owner',))] -fn test_approve_to_owner() { - let mut state = setup(); - - testing::set_caller_address(OWNER()); - ERC721Impl::approve(ref state, OWNER(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_approve_nonexistent() { - // let mut state = STATE(); - let (world, mut state) = STATE(); - ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -fn test__approve() { - let mut state = setup(); - InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); - assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); - - assert( - ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: approval to owner',))] -fn test__approve_to_owner() { - let mut state = setup(); - InternalImpl::_approve(ref state, OWNER(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test__approve_nonexistent() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); -} - -// -// set_approval_for_all & _set_approval_for_all -// - -#[test] -#[available_gas(20000000)] -fn test_set_approval_for_all() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - - assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); - - ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); - assert_event_approval_for_all(OWNER(), OPERATOR(), true); - - assert( - ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); - - ERC721Impl::set_approval_for_all(ref state, OPERATOR(), false); - assert_event_approval_for_all(OWNER(), OPERATOR(), false); - - assert( - !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Approval not revoked correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: self approval',))] -fn test_set_approval_for_all_owner_equal_operator_true() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - ERC721Impl::set_approval_for_all(ref state, OWNER(), true); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: self approval',))] -fn test_set_approval_for_all_owner_equal_operator_false() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - testing::set_caller_address(OWNER()); - ERC721Impl::set_approval_for_all(ref state, OWNER(), false); -} - -#[test] -#[available_gas(20000000)] -fn test__set_approval_for_all() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); - - InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); - assert_event_approval_for_all(OWNER(), OPERATOR(), true); - - assert( - ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); - - InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); - assert_event_approval_for_all(OWNER(), OPERATOR(), false); - - assert( - !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), - 'Operator not approved correctly' - ); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: self approval',))] -fn test__set_approval_for_all_owner_equal_operator_true() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: self approval',))] -fn test__set_approval_for_all_owner_equal_operator_false() { - // let mut state = STATE(); - let (world, mut state) = STATE(); - InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); -} - - -// -// transfer_from & transferFrom -// - -#[test] -#[available_gas(60000000)] -fn test_transfer_from_owner() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - // set approval to check reset - InternalImpl::_approve(ref state, OTHER(), token_id); - utils::drop_event(ZERO()); - - assert_state_before_transfer(@state, owner, recipient, token_id); - assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); - - testing::set_caller_address(owner); - ERC721Impl::transfer_from(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state, owner, recipient, token_id); -} - -#[test] -#[available_gas(50000000)] -fn test_transferFrom_owner() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - // set approval to check reset - InternalImpl::_approve(ref state, OTHER(), token_id); - utils::drop_event(ZERO()); - - assert_state_before_transfer(@state, owner, recipient, token_id); - assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); - - testing::set_caller_address(owner); - ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state, owner, recipient, token_id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_transfer_from_nonexistent() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - ERC721Impl::transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test_transferFrom_nonexistent() { - //let mut state = STATE(); - let (world, mut state) = STATE(); - ERC721CamelOnlyImpl::transferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid receiver',))] -fn test_transfer_from_to_zero() { - let mut state = setup(); - testing::set_caller_address(OWNER()); - ERC721Impl::transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid receiver',))] -fn test_transferFrom_to_zero() { - let mut state = setup(); - - testing::set_caller_address(OWNER()); - ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), ZERO(), TOKEN_ID); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_to_owner() { - let mut state = setup(); - - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); - - testing::set_caller_address(OWNER()); - ERC721Impl::transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID); - assert_event_transfer(OWNER(), OWNER(), TOKEN_ID); - - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); -} - -#[test] -#[available_gas(50000000)] -fn test_transferFrom_to_owner() { - let mut state = setup(); - - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); - - testing::set_caller_address(OWNER()); - ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), OWNER(), TOKEN_ID); - assert_event_transfer( OWNER(), OWNER(), TOKEN_ID); - - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_approved() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - assert_state_before_transfer(@state, owner, recipient, token_id); - - testing::set_caller_address(owner); - ERC721Impl::approve(ref state, OPERATOR(), token_id); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC721Impl::transfer_from(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state, owner, recipient, token_id); -} - -#[test] -#[available_gas(50000000)] -fn test_transferFrom_approved() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - assert_state_before_transfer(@state,owner, recipient, token_id); - - testing::set_caller_address(owner); - ERC721Impl::approve(ref state, OPERATOR(), token_id); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state,owner, recipient, token_id); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_approved_for_all() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, token_id); - - testing::set_caller_address(owner); - ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC721Impl::transfer_from(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state, owner, recipient, token_id); -} - -#[test] -#[available_gas(50000000)] -fn test_transferFrom_approved_for_all() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state,owner, recipient, token_id); - - testing::set_caller_address(owner); - ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); - utils::drop_event(ZERO()); - - testing::set_caller_address(OPERATOR()); - ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state,owner, recipient, token_id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: unauthorized caller',))] -fn test_transfer_from_unauthorized() { - let mut state = setup(); - testing::set_caller_address(OTHER()); - ERC721Impl::transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: unauthorized caller',))] -fn test_transferFrom_unauthorized() { - let mut state = setup(); - testing::set_caller_address(OTHER()); - ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID); -} - -// // -// // safe_transfer_from & safeTransferFrom -// // - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_account() { -// let mut state = setup(); -// let account = setup_account(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, account, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); -// assert_event_transfer(owner, account, token_id); - -// assert_state_after_transfer(@state,owner, account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_account() { -// let mut state = setup(); -// let account = setup_account(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, account, token_id); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); -// assert_event_transfer(owner, account, token_id); - -// assert_state_after_transfer(@state,owner, account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_account_camel() { -// let mut state = setup(); -// let account = setup_camel_account(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, account, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); -// assert_event_transfer(owner, account, token_id); - -// assert_state_after_transfer(@state,owner, account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_account_camel() { -// let mut state = setup(); -// let account = setup_camel_account(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, account, token_id); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); -// assert_event_transfer(owner, account, token_id); - -// assert_state_after_transfer(@state,owner, account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_receiver() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_receiver() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_receiver_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_receiver_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe transfer failed',))] -// fn test_safe_transfer_from_to_receiver_failure() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe transfer failed',))] -// fn test_safeTransferFrom_to_receiver_failure() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe transfer failed',))] -// fn test_safe_transfer_from_to_receiver_failure_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe transfer failed',))] -// fn test_safeTransferFrom_to_receiver_failure_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -// fn test_safe_transfer_from_to_non_receiver() { -// let mut state = setup(); -// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, recipient, token_id, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -// fn test_safeTransferFrom_to_non_receiver() { -// let mut state = setup(); -// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, token_id, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid token ID',))] -// fn test_safe_transfer_from_nonexistent() { -// let mut state = STATE(); -// ERC721Impl::safe_transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid token ID',))] -// fn test_safeTransferFrom_nonexistent() { -// let mut state = STATE(); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid receiver',))] -// fn test_safe_transfer_from_to_zero() { -// let mut state = setup(); -// testing::set_caller_address(OWNER()); -// ERC721Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid receiver',))] -// fn test_safeTransferFrom_to_zero() { -// let mut state = setup(); -// testing::set_caller_address(OWNER()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_owner() { -// let mut state = STATE(); -// let token_id = TOKEN_ID; -// let owner = setup_receiver(); -// InternalImpl::initializer(ref state, NAME, SYMBOL); -// InternalImpl::_mint(ref state, owner, token_id); -// utils::drop_event(ZERO()); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); -// assert_event_transfer(owner, owner, token_id); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_owner() { -// let mut state = STATE(); -// let token_id = TOKEN_ID; -// let owner = setup_receiver(); -// InternalImpl::initializer(ref state, NAME, SYMBOL); -// InternalImpl::_mint(ref state, owner, token_id); -// utils::drop_event(ZERO()); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); -// assert_event_transfer(owner, owner, token_id); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_to_owner_camel() { -// let mut state = STATE(); -// let token_id = TOKEN_ID; -// let owner = setup_camel_receiver(); -// InternalImpl::initializer(ref state, NAME, SYMBOL); -// InternalImpl::_mint(ref state, owner, token_id); -// utils::drop_event(ZERO()); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); - -// testing::set_caller_address(owner); -// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); -// assert_event_transfer(owner, owner, token_id); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_to_owner_camel() { -// let mut state = STATE(); -// let token_id = TOKEN_ID; -// let owner = setup_camel_receiver(); -// InternalImpl::initializer(ref state, NAME, SYMBOL); -// InternalImpl::_mint(ref state, owner, token_id); -// utils::drop_event(ZERO()); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); - -// testing::set_caller_address(owner); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); -// assert_event_transfer(owner, owner, token_id); - -// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); -// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_approved() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::approve(ref state, OPERATOR(), token_id); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_approved() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::approve(ref state, OPERATOR(), token_id); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_approved_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::approve(ref state, OPERATOR(), token_id); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_approved_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::approve(ref state, OPERATOR(), token_id); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_approved_for_all() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_approved_for_all() { -// let mut state = setup(); -// let receiver = setup_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safe_transfer_from_approved_for_all_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test_safeTransferFrom_approved_for_all_camel() { -// let mut state = setup(); -// let receiver = setup_camel_receiver(); -// let token_id = TOKEN_ID; -// let owner = OWNER(); - -// assert_state_before_transfer(@state,owner, receiver, token_id); - -// testing::set_caller_address(owner); -// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); -// utils::drop_event(ZERO()); - -// testing::set_caller_address(OPERATOR()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); -// assert_event_transfer(owner, receiver, token_id); - -// assert_state_after_transfer(@state,owner, receiver, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: unauthorized caller',))] -// fn test_safe_transfer_from_unauthorized() { -// let mut state = setup(); -// testing::set_caller_address(OTHER()); -// ERC721Impl::safe_transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: unauthorized caller',))] -// fn test_safeTransferFrom_unauthorized() { -// let mut state = setup(); -// testing::set_caller_address(OTHER()); -// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); -// } - -// -// _transfer -// - -#[test] -#[available_gas(50000000)] -fn test__transfer() { - let mut state = setup(); - let token_id = TOKEN_ID; - let owner = OWNER(); - let recipient = RECIPIENT(); - - assert_state_before_transfer(@state, owner, recipient, token_id); - - InternalImpl::_transfer(ref state, owner, recipient, token_id); - assert_event_transfer(owner, recipient, token_id); - - assert_state_after_transfer(@state, owner, recipient, token_id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test__transfer_nonexistent() { - let (world, mut state) = STATE(); - InternalImpl::_transfer(ref state, ZERO(), RECIPIENT(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid receiver',))] -fn test__transfer_to_zero() { - let mut state = setup(); - InternalImpl::_transfer(ref state, OWNER(), ZERO(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: wrong sender',))] -fn test__transfer_from_invalid_owner() { - let mut state = setup(); - InternalImpl::_transfer(ref state, RECIPIENT(), OWNER(), TOKEN_ID); -} - -// -// _mint -// - -#[test] -#[available_gas(20000000)] -fn test__mint() { - let (world, mut state) = STATE(); - let recipient = RECIPIENT(); - let token_id = TOKEN_ID; - - assert_state_before_mint(@state, recipient); - InternalImpl::_mint(ref state, recipient, TOKEN_ID); - assert_event_transfer(ZERO(), recipient, token_id); - - assert_state_after_mint(@state, recipient, token_id); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: invalid receiver',))] -fn test__mint_to_zero() { - let (world, mut state) = STATE(); - InternalImpl::_mint(ref state, ZERO(), TOKEN_ID); -} - -#[test] -#[available_gas(20000000)] -#[should_panic(expected: ('ERC721: token already minted',))] -fn test__mint_already_exist() { - let mut state = setup(); - InternalImpl::_mint(ref state, RECIPIENT(), TOKEN_ID); -} - -// // -// // _safe_mint -// // - -// #[test] -// #[available_gas(20000000)] -// fn test__safe_mint_to_receiver() { -// let mut state = STATE(); -// let recipient = setup_receiver(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,recipient); -// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); -// assert_event_transfer(ZERO(), recipient, token_id); - -// assert_state_after_mint(@state,recipient, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test__safe_mint_to_receiver_camel() { -// let mut state = STATE(); -// let recipient = setup_camel_receiver(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,recipient); -// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); -// assert_event_transfer(ZERO(), recipient, token_id); - -// assert_state_after_mint(@state,recipient, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test__safe_mint_to_account() { -// let mut state = STATE(); -// let account = setup_account(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,account); -// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); -// assert_event_transfer(ZERO(), account, token_id); - -// assert_state_after_mint(@state,account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// fn test__safe_mint_to_account_camel() { -// let mut state = STATE(); -// let account = setup_camel_account(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,account); -// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); -// assert_event_transfer(ZERO(), account, token_id); - -// assert_state_after_mint(@state,account, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -// fn test__safe_mint_to_non_receiver() { -// let mut state = STATE(); -// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,recipient); -// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); -// assert_state_after_mint(@state,recipient, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe mint failed',))] -// fn test__safe_mint_to_receiver_failure() { -// let mut state = STATE(); -// let recipient = setup_receiver(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,recipient); -// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); -// assert_state_after_mint(@state,recipient, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: safe mint failed',))] -// fn test__safe_mint_to_receiver_failure_camel() { -// let mut state = STATE(); -// let recipient = setup_camel_receiver(); -// let token_id = TOKEN_ID; - -// assert_state_before_mint(@state,recipient); -// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); -// assert_state_after_mint(@state,recipient, token_id); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid receiver',))] -// fn test__safe_mint_to_zero() { -// let mut state = STATE(); -// InternalImpl::_safe_mint(ref state, ZERO(), TOKEN_ID, DATA(true)); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: token already minted',))] -// fn test__safe_mint_already_exist() { -// let mut state = setup(); -// InternalImpl::_safe_mint(ref state, RECIPIENT(), TOKEN_ID, DATA(true)); -// } - -// -// _burn -// - -#[test] -#[available_gas(25000000)] -fn test__burn() { - let mut state = setup(); - - InternalImpl::_approve(ref state, OTHER(), TOKEN_ID); - utils::drop_event(ZERO()); - - assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); - assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); - assert(ERC721Impl::get_approved(@state, TOKEN_ID) == OTHER(), 'Approval before'); - - InternalImpl::_burn(ref state, TOKEN_ID); - assert_event_transfer(OWNER(), ZERO(), TOKEN_ID); - - assert( - WorldInteractionsImpl::get_owner_of(@state, TOKEN_ID).address == ZERO(), 'Ownership after' - ); - assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance of owner after'); - assert( - WorldInteractionsImpl::get_token_approval(@state, TOKEN_ID).address == ZERO(), - 'Approval after' - ); -} - -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test__burn_nonexistent() { - let (mut world, mut state) = STATE(); - InternalImpl::_burn(ref state, TOKEN_ID); -} - -// -// _set_token_uri -// - -// #[test] -// #[available_gas(20000000)] -// fn test__set_token_uri() { -// let mut state = setup(); - -// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == 0, 'URI should be 0'); -// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); -// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == URI, 'URI should be set'); -// } - -// #[test] -// #[available_gas(20000000)] -// #[should_panic(expected: ('ERC721: invalid token ID',))] -// fn test__set_token_uri_nonexistent() { -// let mut state = STATE(); -// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); -// } - -// -// Helpers -// - -fn assert_state_before_transfer( - state: @ERC721::ContractState, - owner: ContractAddress, - recipient: ContractAddress, - token_id: u256 -) { - assert(ERC721Impl::owner_of(state, token_id) == owner, 'Ownership before'); - assert(ERC721Impl::balance_of(state, owner) == 1, 'Balance of owner before'); - assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); -} - -fn assert_state_after_transfer( - state: @ERC721::ContractState, - owner: ContractAddress, - recipient: ContractAddress, - token_id: u256 -) { - assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); - assert(ERC721Impl::balance_of(state, owner) == 0, 'Balance of owner after'); - assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); - assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval not implicitly reset'); -} - -fn assert_state_before_mint(state: @ERC721::ContractState, recipient: ContractAddress) { - assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); -} - -fn assert_state_after_mint( - state: @ERC721::ContractState, recipient: ContractAddress, token_id: u256 -) { - assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); - assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); - assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval implicitly set'); -} - -fn assert_event_approval_for_all( - owner: ContractAddress, operator: ContractAddress, approved: bool -) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.owner == owner, 'Invalid `owner`'); - assert(event.operator == operator, 'Invalid `operator`'); - assert(event.approved == approved, 'Invalid `approved`'); - utils::assert_no_events_left(ZERO()); -} - -fn assert_event_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.owner == owner, 'Invalid `owner`'); - assert(event.approved == approved, 'Invalid `approved`'); - assert(event.token_id == token_id, 'Invalid `token_id`'); - utils::assert_no_events_left(ZERO()); -} - -fn assert_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.from == from, 'Invalid `from`'); - assert(event.to == to, 'Invalid `to`'); - assert(event.token_id == token_id, 'Invalid `token_id`'); - utils::assert_no_events_left(ZERO()); -} diff --git a/crates/dojo-erc/src/tests/test_erc1155.cairo b/crates/dojo-erc/src/tests/test_erc1155.cairo deleted file mode 100644 index 5ce73b8230..0000000000 --- a/crates/dojo-erc/src/tests/test_erc1155.cairo +++ /dev/null @@ -1,629 +0,0 @@ -use zeroable::Zeroable; -use traits::{Into, Default, IndexView}; -use option::OptionTrait; -use array::ArrayTrait; -use serde::Serde; -use starknet::ContractAddress; -use starknet::testing::set_contract_address; - -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::tests::test_utils::impersonate; -use dojo_erc::tests::test_erc1155_utils::{ - spawn_world, deploy_erc1155, deploy_default, deploy_testcase1, ZERO, USER1, USER2, DEPLOYER, - PROXY -}; - -use dojo_erc::erc165::interface::IERC165_ID; -use dojo_erc::erc1155::interface::{ - IERC1155A, IERC1155ADispatcher, IERC1155ADispatcherTrait, IERC1155_ID, IERC1155_METADATA_ID, - IERC1155_RECEIVER_ID -}; - -use dojo_erc::erc1155::erc1155::ERC1155::{Event, TransferSingle, TransferBatch, ApprovalForAll}; - - -#[test] -#[available_gas(30000000)] -fn test_deploy() { - let world = spawn_world(DEPLOYER()); - let erc1155_address = deploy_erc1155(world, DEPLOYER(), 'uri', 'seed-42'); - let erc1155 = IERC1155ADispatcher { contract_address: erc1155_address }; - assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); -} - -#[test] -#[available_gas(30000000)] -fn test_deploy_default() { - let (world, erc1155) = deploy_default(); - assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); -} - - -// -// supports_interface -// - -#[test] -#[available_gas(30000000)] -fn test_should_support_interfaces() { - let (world, erc1155) = deploy_default(); - - assert(erc1155.supports_interface(IERC165_ID) == true, 'should support erc165'); - assert(erc1155.supports_interface(IERC1155_ID) == true, 'should support erc1155'); - assert( - erc1155.supports_interface(IERC1155_METADATA_ID) == true, 'should support erc1155_metadata' - ); -} - -// -// uri -// - -#[test] -#[available_gas(30000000)] -fn test_uri() { - let (world, erc1155) = deploy_default(); - assert(erc1155.uri(64) == 'uri', 'invalid uri'); -} - - -// -// behaves like an ERC1155 -// - -// -// balance_of -// -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED',))] -fn test_balance_of_zero_address() { - //reverts when queried about the zero address - - let (world, erc1155) = deploy_default(); - erc1155.balance_of(ZERO(), 0); // should panic -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_empty_balance() { - // when accounts don't own tokens - // returns zero for given addresses - let (world, erc1155) = deploy_default(); - assert(erc1155.balance_of(USER1(), 0) == 0, 'should be 0'); - assert(erc1155.balance_of(USER1(), 69) == 0, 'should be 0'); - assert(erc1155.balance_of(USER2(), 0) == 0, 'should be 0'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_with_tokens() { - // when accounts own some tokens - // returns the amount of tokens owned by the given addresses - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER1(), 69, 42, array![]); - erc1155.mint(USER2(), 69, 5, array![]); - - assert(erc1155.balance_of(USER1(), 0) == 1, 'should be 1'); - assert(erc1155.balance_of(USER1(), 69) == 42, 'should be 42'); - assert(erc1155.balance_of(USER2(), 69) == 5, 'should be 5'); -} - -// -// balance_of_batch -// - -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid length', 'ENTRYPOINT_FAILED',))] -fn test_balance_of_batch_with_invalid_input() { - // reverts when input arrays don't match up - let (world, erc1155) = deploy_default(); - erc1155.balance_of_batch(array![USER1(), USER2()], array![0]); - erc1155.balance_of_batch(array![USER1()], array![0, 1, 2]); -} - -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED',))] -fn test_balance_of_batch_address_zero() { - // reverts when input arrays don't match up - let (world, erc1155) = deploy_default(); - erc1155.balance_of_batch(array![USER1(), ZERO()], array![0, 1]); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_empty_account() { - // when accounts don't own tokens - // returns zeros for each account - let (world, erc1155) = deploy_default(); - let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER1()], array![0, 1, 5]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @0_u256, 'should be 0'); - assert(bals[1] == @0_u256, 'should be 0'); - assert(bals[2] == @0_u256, 'should be 0'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_with_tokens() { - // when accounts own some tokens - // returns amounts owned by each account in order passed - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER1(), 69, 42, array![]); - erc1155.mint(USER2(), 69, 2, array![]); - - let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER2()], array![0, 69, 69]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @1_u256, 'should be 1'); - assert(bals[1] == @42_u256, 'should be 42'); - assert(bals[2] == @2_u256, 'should be 2'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_with_tokens_2() { - // when accounts own some tokens - // returns multiple times the balance of the same address when asked - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER2(), 69, 2, array![]); - - let balances = erc1155.balance_of_batch(array![USER1(), USER2(), USER1()], array![0, 69, 0]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @1_u256, 'should be 1'); - assert(bals[1] == @2_u256, 'should be 2'); - assert(bals[2] == @1_u256, 'should be 1'); -} - - -// -// balance_of_batch -// - -#[test] -#[available_gas(30000000)] -fn test_set_approval_for_all() { - // sets approval status which can be queried via is_approved_for_all - let (world, erc1155) = deploy_default(); - impersonate(USER1()); - - erc1155.set_approval_for_all(PROXY(), true); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); -} - -#[test] -#[available_gas(30000000)] -fn test_set_approval_for_all_emit_event() { - // set_approval_for_all emits ApprovalForAll event - let (world, erc1155) = deploy_default(); - impersonate(USER1()); - - erc1155.set_approval_for_all(PROXY(), true); - - // ApprovalForAll - assert( - @starknet::testing::pop_log(erc1155.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER1(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - - -#[test] -#[available_gas(30000000)] -fn test_set_unset_approval_for_all() { - // sets approval status which can be queried via is_approved_for_all - let (world, erc1155) = deploy_default(); - impersonate(USER1()); - - erc1155.set_approval_for_all(PROXY(), true); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); - erc1155.set_approval_for_all(PROXY(), false); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == false, 'should be false'); -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_set_approval_for_all_on_self() { - // reverts if attempting to approve self as an operator - let (world, erc1155) = deploy_default(); - impersonate(USER1()); - - erc1155.set_approval_for_all(USER1(), true); // should panic -} - -// -// safe_transfer_from -// - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_from_more_than_balance() { - // reverts when transferring more than balance - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_transfer_from(USER1(), USER2(), 1, 999, array![]); // should panic -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_to_zero() { - // reverts when transferring to zero address - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_transfer_from(USER1(), ZERO(), 1, 1, array![]); // should panic -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_debit_sender() { - // debits transferred balance from sender - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_before = erc1155.balance_of(USER1(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after = erc1155.balance_of(USER1(), 1); - - assert(balance_after == balance_before - 1, 'invalid balance after'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_credit_receiver() { - // credits transferred balance to receiver - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_before = erc1155.balance_of(USER2(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after = erc1155.balance_of(USER2(), 1); - - assert(balance_after == balance_before + 1, 'invalid balance after'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_preserve_existing_balances() { - // preserves existing balances which are not transferred by multiTokenHolder - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - impersonate(USER1()); - - let balance_before_2 = erc1155.balance_of(USER2(), 2); - let balance_before_3 = erc1155.balance_of(USER2(), 3); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after_2 = erc1155.balance_of(USER2(), 2); - let balance_after_3 = erc1155.balance_of(USER2(), 3); - - assert(balance_after_2 == balance_before_2, 'should be equal'); - assert(balance_after_3 == balance_before_3, 'should be equal'); -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_from_unapproved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is not approved by multiTokenHolder - - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER2()); - - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); // should panic -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_approved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is approved by multiTokenHolder - let (world, erc1155) = deploy_testcase1(); - - impersonate(PROXY()); - - let balance_before = erc1155.balance_of(USER1(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); - let balance_after = erc1155.balance_of(USER1(), 1); - - assert(balance_after == balance_before - 2, 'invalid balance'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_approved_operator_preserve_operator_balance() { - // when called by an operator on behalf of the multiTokenHolder - // preserves operator's balances not involved in the transfer - let (world, erc1155) = deploy_testcase1(); - - impersonate(PROXY()); - - let balance_before_1 = erc1155.balance_of(PROXY(), 1); - let balance_before_2 = erc1155.balance_of(PROXY(), 2); - let balance_before_3 = erc1155.balance_of(PROXY(), 3); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); - let balance_after_1 = erc1155.balance_of(PROXY(), 1); - let balance_after_2 = erc1155.balance_of(PROXY(), 2); - let balance_after_3 = erc1155.balance_of(PROXY(), 3); - - assert(balance_before_1 == balance_after_1, 'should be equal'); - assert(balance_before_2 == balance_after_2, 'should be equal'); - assert(balance_before_3 == balance_after_3, 'should be equal'); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_transfer_from_zero_address() { - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_transfer_from(ZERO(), USER1(), 1, 1, array![]); -} - -// -// safe_batch_transfer_from -// - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_more_than_balance() { - // reverts when transferring amount more than any of balances - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 999, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_mismatching_array_len() { - // reverts when ids array length doesn't match amounts array length - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_to_zero_address() { - // reverts when transferring to zero address - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_batch_transfer_from(USER1(), ZERO(), array![1, 2], array![1, 1], array![]); -} - - -#[test] -#[available_gas(60000000)] -fn test_safe_batch_transfer_from_debits_sender() { - // debits transferred balances from sender - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_before_1 = erc1155.balance_of(USER1(), 1); - let balance_before_2 = erc1155.balance_of(USER1(), 2); - let balance_before_3 = erc1155.balance_of(USER1(), 3); - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - let balance_after_1 = erc1155.balance_of(USER1(), 1); - let balance_after_2 = erc1155.balance_of(USER1(), 2); - let balance_after_3 = erc1155.balance_of(USER1(), 3); - - assert(balance_before_1 - 1 == balance_after_1, 'invalid balance'); - assert(balance_before_2 - 10 == balance_after_2, 'invalid balance'); - assert(balance_before_3 - 20 == balance_after_3, 'invalid balance'); -} - - -#[test] -#[available_gas(60000000)] -fn test_safe_batch_transfer_from_credits_recipient() { - // credits transferred balances to receiver - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_before_1 = erc1155.balance_of(USER2(), 1); - let balance_before_2 = erc1155.balance_of(USER2(), 2); - let balance_before_3 = erc1155.balance_of(USER2(), 3); - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - let balance_after_1 = erc1155.balance_of(USER2(), 1); - let balance_after_2 = erc1155.balance_of(USER2(), 2); - let balance_after_3 = erc1155.balance_of(USER2(), 3); - - assert(balance_before_1 + 1 == balance_after_1, 'invalid balance'); - assert(balance_before_2 + 10 == balance_after_2, 'invalid balance'); - assert(balance_before_1 + 20 == balance_after_3, 'invalid balance'); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_unapproved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is not approved by multiTokenHolder - - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER2()); - - erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2], array![1, 10], array![]); -} - -#[test] -#[available_gas(60000000)] -fn test_safe_batch_transfer_from_approved_operator_preserve_operator_balance() { - // when called by an operator on behalf of the multiTokenHolder - // preserves operator's balances not involved in the transfer - - let (world, erc1155) = deploy_testcase1(); - - impersonate(PROXY()); - - let balance_before_1 = erc1155.balance_of(PROXY(), 1); - let balance_before_2 = erc1155.balance_of(PROXY(), 2); - let balance_before_3 = erc1155.balance_of(PROXY(), 3); - - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - - let balance_after_1 = erc1155.balance_of(PROXY(), 1); - let balance_after_2 = erc1155.balance_of(PROXY(), 2); - let balance_after_3 = erc1155.balance_of(PROXY(), 3); - - assert(balance_before_1 == balance_after_1, 'should be equal'); - assert(balance_before_2 == balance_after_2, 'should be equal'); - assert(balance_before_3 == balance_after_3, 'should be equal'); -} - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_zero_address() { - let (world, erc1155) = deploy_testcase1(); - - impersonate(USER1()); - - erc1155.safe_batch_transfer_from(ZERO(), USER1(), array![1, 2], array![1, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_emit_transfer_batch_event() { - let (world, erc1155) = deploy_default(); - - // user1 token_id 1 x 10 - erc1155.mint(USER1(), 1, 10, array![]); - // user1 token_id 2 x 20 - erc1155.mint(USER1(), 2, 20, array![]); - - impersonate(USER1()); - - erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2], array![1, 10], array![]); - - let _: Event = starknet::testing::pop_log(erc1155.contract_address) - .unwrap(); // unpop erc1155.mint(USER1(), 1, 10, array![]); - let _: Event = starknet::testing::pop_log(erc1155.contract_address) - .unwrap(); // unpop erc1155.mint(USER1(), 2, 20, array![]); - - // TransferBatch - assert( - @starknet::testing::pop_log(erc1155.contract_address) - .unwrap() == @Event::TransferBatch( - TransferBatch { - operator: USER1(), - from: USER1(), - to: USER2(), - ids: array![1, 2], - values: array![1, 10] - } - ), - 'invalid TransferBatch event' - ); -} - - -// -// burn -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_non_existing_token_id() { - //reverts when burning a non-existent token id - let (world, erc1155) = deploy_default(); - - impersonate(USER1()); - erc1155.burn(USER1(), 69, 1); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_burn_emit_transfer_single_event() { - // burn should emit event - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 69, 5, array![]); - assert(erc1155.balance_of(USER1(), 69) == 5, 'invalid balance'); - - impersonate(USER1()); - - erc1155.burn(USER1(), 69, 1); - assert(erc1155.balance_of(USER1(), 69) == 4, 'invalid balance'); - - let _: Event = starknet::testing::pop_log(erc1155.contract_address) - .unwrap(); // unpop erc1155.mint(USER1(), 69,5,array![]) - - // TransferSingle - assert( - @starknet::testing::pop_log(erc1155.contract_address) - .unwrap() == @Event::TransferSingle( - TransferSingle { operator: USER1(), from: USER1(), to: ZERO(), id: 69, value: 1 } - ), - 'invalid TransferSingle event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_more_than_owned() { - // reverts when burning more tokens than owned - let (world, erc1155) = deploy_default(); - erc1155.mint(USER1(), 69, 10, array![]); - - impersonate(USER1()); - - erc1155.burn(USER1(), 69, 1); - erc1155.burn(USER1(), 69, 10); // should panic -} -// TODO : to be continued - -// TODO : add test if we support IERC1155Receiver - - diff --git a/crates/dojo-erc/src/tests/test_erc721.cairo b/crates/dojo-erc/src/tests/test_erc721.cairo deleted file mode 100644 index fc23abc4f9..0000000000 --- a/crates/dojo-erc/src/tests/test_erc721.cairo +++ /dev/null @@ -1,862 +0,0 @@ -use core::zeroable::Zeroable; -use core::traits::{Into, Default}; -use array::ArrayTrait; -use serde::Serde; -use starknet::ContractAddress; -use starknet::testing::set_contract_address; -use option::OptionTrait; - -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::tests::test_utils::impersonate; -use dojo_erc::tests::test_erc721_utils::{ - spawn_world, deploy_erc721, deploy_default, deploy_testcase1, USER1, USER2, USER3, DEPLOYER, - ZERO, PROXY -}; - - -use dojo_erc::erc165::interface::IERC165_ID; -use dojo_erc::erc721::interface::{ - IERC721, IERC721ADispatcher, IERC721ADispatcherTrait, IERC721_ID, IERC721_METADATA_ID -}; -use dojo_erc::erc721::erc721::ERC721::{Event, Transfer, Approval, ApprovalForAll}; -// actually it's possible to mint -> burn -> mint -> ... -// todo : add Minted component to keep track of minted ids - -#[test] -#[available_gas(30000000)] -fn test_deploy() { - let world = spawn_world(DEPLOYER()); - let erc721_address = deploy_erc721(world, DEPLOYER(), 'name', 'symbol', 'uri', 'seed-42'); - let erc721 = IERC721ADispatcher { contract_address: erc721_address }; - - assert(erc721.owner() == DEPLOYER(), 'invalid owner'); - assert(erc721.name() == 'name', 'invalid name'); - assert(erc721.symbol() == 'symbol', 'invalid symbol'); -} - - -#[test] -#[available_gas(30000000)] -fn test_deploy_default() { - let (world, erc721) = deploy_default(); - assert(erc721.name() == 'name', 'invalid name'); -} - -// -// supports_interface -// - -#[test] -#[available_gas(30000000)] -fn test_should_support_interfaces() { - let (world, erc721) = deploy_default(); - - assert(erc721.supports_interface(IERC165_ID) == true, 'should support erc165'); - assert(erc721.supports_interface(IERC721_ID) == true, 'should support erc721'); - assert( - erc721.supports_interface(IERC721_METADATA_ID) == true, 'should support erc721_metadata' - ); -} - - -// -// behaves like an ERC721 -// - -// -// balance_of -// - -use debug::PrintTrait; - -#[test] -#[available_gas(60000000)] -fn test_balance_of_with_tokens() { - // returns the amount of tokens owned by the given address - - let (world, erc721) = deploy_testcase1(); - assert(erc721.balance_of(USER1()) == 3, 'should be 3'); - assert(erc721.balance_of(PROXY()) == 4, 'should be 4'); -} - -#[test] -#[available_gas(60000000)] -fn test_balance_of_with_no_tokens() { - // when the given address does not own any tokens - - let (world, erc721) = deploy_testcase1(); - assert(erc721.balance_of(USER3()) == 0, 'should be 0'); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_balance_of_zero_address() { - // when querying the zero address - - let (world, erc721) = deploy_testcase1(); - erc721.balance_of(ZERO()); -} - -// -// owner_of -// - -#[test] -#[available_gas(90000000)] -fn test_owner_of_existing_id() { - // when the given token ID was tracked by this token = for existing id - - let (world, erc721) = deploy_testcase1(); - assert(erc721.owner_of(1) == USER1(), 'should be user1'); - assert(erc721.owner_of(2) == USER1(), 'should be user1'); - assert(erc721.owner_of(3) == USER1(), 'should be user1'); - - assert(erc721.owner_of(10) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(11) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(12) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(13) == PROXY(), 'should be proxy'); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_owner_of_non_existing_id() { - // when the given token ID was not tracked by this token = non existing id - - let (world, erc721) = deploy_testcase1(); - let owner_of_0 = erc721.owner_of(0); // should panic -} - -// -// transfers -// - -#[test] -#[available_gas(90000000)] -fn test_transfer_ownership() { - // transfers the ownership of the given token ID to the given address - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - let owner_of_1 = erc721.owner_of(1); - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - assert(erc721.owner_of(1) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_event() { - // emits a Transfer event - - let (world, erc721) = deploy_default(); - - // mint - erc721.mint(USER1(), 42); - - impersonate(USER1()); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 42); - - impersonate(USER2()); - erc721.burn(42); - - // mint - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 42 }), - 'invalid Transfer event' - ); - // transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: USER2(), token_id: 42 }), - 'invalid Transfer event' - ); - // burn - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER2(), to: ZERO(), token_id: 42 }), - 'invalid Transfer event' - ); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_clear_approval() { - // clears the approval for the token ID - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - erc721.approve(PROXY(), 1); - assert(erc721.get_approved(1) == PROXY(), 'should be proxy'); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - assert(erc721.get_approved(1).is_zero(), 'should be zero'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_adjusts_owners_balances() { - // adjusts owners balances - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_user1_before = erc721.balance_of(USER1()); - let balance_user2_before = erc721.balance_of(USER2()); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - - let balance_user1_after = erc721.balance_of(USER1()); - let balance_user2_after = erc721.balance_of(USER2()); - - assert(balance_user1_after == balance_user1_before - 1, 'invalid user1 balance'); - assert(balance_user2_after == balance_user2_before + 1, 'invalid user2 balance'); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_approved() { - // when called by the approved individual - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - //user1 approve user2 for token_id 2 - erc721.approve(USER2(), 2); - - impersonate(USER2()); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_approved_operator() { - // when called by the operator - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - //user1 set_approval_for_all for proxy - erc721.set_approval_for_all(PROXY(), true); - - impersonate(PROXY()); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_owner_without_approved() { - // when called by the owner without an approved user - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - erc721.approve(ZERO(), 2); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_to_owner() { - // when sent to the owner - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - let balance_before = erc721.balance_of(USER1()); - - assert(erc721.owner_of(3) == USER1(), 'invalid owner'); - erc721.transfer(USER1(), 3); - - // keeps ownership of the token - assert(erc721.owner_of(3) == USER1(), 'invalid owner'); - - // clears the approval for the token ID - assert(erc721.get_approved(3) == ZERO(), 'invalid approved'); - - //emits only a transfer event : cumbersome to test with pop_log - - //keeps the owner balance - let balance_after = erc721.balance_of(USER1()); - assert(balance_before == balance_after, 'invalid balance') -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_previous_owner_is_incorrect() { - // when the address of the previous owner is incorrect - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - //user2 owner token_id 10 - erc721.transfer_from(USER1(), PROXY(), 10); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_sender_not_authorized() { - // when the sender is not authorized for the token id - let (world, erc721) = deploy_testcase1(); - - impersonate(PROXY()); - - //proxy is not authorized for USER2 - erc721.transfer_from(USER2(), PROXY(), 20); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_token_id_doesnt_exists() { - // when the sender is not authorized for the token id - let (world, erc721) = deploy_testcase1(); - - impersonate(PROXY()); - - //proxy is authorized for USER1 but token_id 50 doesnt exists - erc721.transfer_from(USER1(), PROXY(), 50); // should panic -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_to_address_zero() { - // when the address to transfer the token to is the zero address - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - erc721.transfer(ZERO(), 1); // should panic -} - -// -// approval -// - -// when clearing approval - -#[test] -#[available_gas(90000000)] -fn test_approval_when_clearing_with_prior_approval() { - // -when there was a prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - impersonate(USER1()); - - erc721.approve(PROXY(), 42); - - //revoke approve - erc721.approve(ZERO(), 42); - - // clears approval for the token - assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop approve PROXY - - // approve ZERO - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), - 'invalid Approval event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_when_clearing_without_prior_approval() { - // when clearing approval - // -when there was no prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - impersonate(USER1()); - - //revoke approve - erc721.approve(ZERO(), 42); - - // updates approval for the token - assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - - // approve ZERO - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), - 'invalid Approval event' - ); -} - - -// when approving a non-zero address - -#[test] -#[available_gas(90000000)] -fn test_approval_non_zero_address_with_prior_approval() { - // -when there was a prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - impersonate(USER1()); - erc721.approve(PROXY(), 42); - - // user1 approves user3 - erc721.approve(USER3(), 42); - - // set approval for the token - assert(erc721.get_approved(42) == USER3(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop approve PROXY - - // approve USER3 - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), - 'invalid Approval event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_non_zero_address_with_no_prior_approval() { - // -when there was no prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - impersonate(USER1()); - - // user1 approves user3 - erc721.approve(USER3(), 42); - - // set approval for the token - assert(erc721.get_approved(42) == USER3(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - - // approve USER3 - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), - 'invalid Approval event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_self_approve() { - // when the address that receives the approval is the owner - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - impersonate(USER1()); - - // user1 approves user1 - erc721.approve(USER1(), 42); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_not_owned() { - // when the sender does not own the given token ID - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - // user1 approves user2 for token 20 - erc721.approve(USER2(), 20); // should panic -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_from_approved_sender() { - // when the sender is approved for the given token ID - - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - // user1 approve user3 - erc721.approve(USER3(), 1); - - impersonate(USER3()); - - // (ERC721: approve caller is not token owner or approved for all) - erc721.approve(USER2(), 1); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_approval_from_approved_operator() { - // when the sender is an operator - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 50); - - impersonate(USER1()); - - erc721.set_approval_for_all(PROXY(), true); - - impersonate(PROXY()); - - // proxy approves user2 for token 20 - erc721.approve(USER2(), 50); - - assert(erc721.get_approved(50) == USER2(), 'invalid approval'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all - - // approve - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER2(), token_id: 50 }), - 'invalid Approval event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_unexisting_id() { - // when the given token ID does not exist - let (world, erc721) = deploy_testcase1(); - - impersonate(USER1()); - - // user1 approve user3 - erc721.approve(USER3(), 69); // should panic -} - -// -// approval_for_all -// - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_no_operator_approval() { - // when the operator willing to approve is not the owner - // -when there is no operator approval set by the sender - let (world, erc721) = deploy_default(); - - impersonate(USER2()); - - // user2 set_approval_for_all PROXY - erc721.set_approval_for_all(PROXY(), true); - - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_from_not_approved() { - // when the operator willing to approve is not the owner - // -when the operator was set as not approved - let (world, erc721) = deploy_default(); - - impersonate(USER2()); - - erc721.set_approval_for_all(PROXY(), false); - - // user2 set_approval_for_all PROXY - erc721.set_approval_for_all(PROXY(), true); - - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), false) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_can_unset_approval_for_all() { - // when the operator willing to approve is not the owner - // can unset the operator approval - let (world, erc721) = deploy_default(); - - impersonate(USER2()); - - erc721.set_approval_for_all(PROXY(), false); - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - erc721.set_approval_for_all(PROXY(), false); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == false, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), false) - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), true) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: false } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_with_operator_already_approved() { - // when the operator willing to approve is not the owner - // when the operator was already approved - let (world, erc721) = deploy_default(); - - impersonate(USER2()); - - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), true) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_for_all_with_owner_as_operator() { - // when the operator is the owner - - let (world, erc721) = deploy_default(); - - impersonate(USER1()); - - erc721.set_approval_for_all(USER1(), true); // should panic -} - - -// -// get_approved -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_get_approved_unexisting_token() { - let (world, erc721) = deploy_default(); - - erc721.get_approved(420); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_get_approved_with_existing_token() { - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 420); - assert(erc721.get_approved(420) == ZERO(), 'invalid get_approved'); -} - - -#[test] -#[available_gas(90000000)] -fn test_get_approved_with_existing_token_and_approval() { - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 420); - - impersonate(USER1()); - - erc721.approve(PROXY(), 420); - assert(erc721.get_approved(420) == PROXY(), 'invalid get_approved'); -} - -// -// mint -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_mint_to_address_zero() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(ZERO(), 69); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_mint() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - - assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); - - // Transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 69 }), - 'invalid Transfer event' - ); -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_mint_existing_token_id() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - erc721.mint(USER1(), 69); //should panic -} - - -// -// burn -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_non_existing_token_id() { - //reverts when burning a non-existent token id - let (world, erc721) = deploy_default(); - erc721.burn(69); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_burn_emit_events() { - // burn should emit event - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); - - impersonate(USER1()); - - erc721.burn(69); - assert(erc721.balance_of(USER1()) == 0, 'invalid balance'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop erc721.mint(USER1(), 69) - - // Transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: ZERO(), token_id: 69 }), - 'invalid Transfer event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_same_id_twice() { - // reverts when burning a token id that has been deleted - let (world, erc721) = deploy_default(); - erc721.mint(USER1(), 69); - erc721.burn(69); - erc721.burn(69); // should panic -} - -// -// token_uri -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_token_uri_for_non_existing_token_id() { - // reverts when queried for non existent token id - let (world, erc721) = deploy_default(); - erc721.token_uri(1234); // should panic -} - diff --git a/crates/dojo-erc/src/tests/utils.cairo b/crates/dojo-erc/src/tests/utils.cairo deleted file mode 100644 index 99e956694f..0000000000 --- a/crates/dojo-erc/src/tests/utils.cairo +++ /dev/null @@ -1,32 +0,0 @@ -use core::result::ResultTrait; -use starknet::class_hash::Felt252TryIntoClassHash; -use starknet::ContractAddress; -use starknet::testing; - -fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAddress { - let (address, _) = starknet::deploy_syscall( - contract_class_hash.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); - address -} - -/// Pop the earliest unpopped logged event for the contract as the requested type -/// and checks there's no more data left on the event, preventing unaccounted params. -/// Indexed event members are currently not supported, so they are ignored. -fn pop_log, impl TEvent: starknet::Event>( - address: ContractAddress -) -> Option { - let (mut keys, mut data) = testing::pop_log_raw(address)?; - let ret = starknet::Event::deserialize(ref keys, ref data); - assert(data.is_empty(), 'Event has extra data'); - ret -} - -fn assert_no_events_left(address: ContractAddress) { - assert(testing::pop_log_raw(address).is_none(), 'Events remaining on queue'); -} - -fn drop_event(address: ContractAddress) { - testing::pop_log_raw(address); -} diff --git a/crates/dojo-erc/src/token/erc1155.cairo b/crates/dojo-erc/src/token/erc1155.cairo deleted file mode 100644 index af50183ea3..0000000000 --- a/crates/dojo-erc/src/token/erc1155.cairo +++ /dev/null @@ -1,5 +0,0 @@ -mod erc1155; -mod models; -mod interface; - -use erc1155::ERC1155; \ No newline at end of file diff --git a/crates/dojo-erc/src/token/erc1155/erc1155.cairo b/crates/dojo-erc/src/token/erc1155/erc1155.cairo deleted file mode 100644 index 26ba7f23bb..0000000000 --- a/crates/dojo-erc/src/token/erc1155/erc1155.cairo +++ /dev/null @@ -1,439 +0,0 @@ -#[starknet::contract] -mod ERC1155 { - use dojo_erc::token::erc1155::models::{ - ERC1155Meta, ERC1155OperatorApproval, ERC1155Balance - }; - use dojo_erc::token::erc1155::interface; - use dojo_erc::token::erc1155::interface::{IERC1155, IERC1155CamelOnly}; - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use starknet::ContractAddress; - use starknet::{get_caller_address, get_contract_address}; - use array::ArrayTCloneImpl; - use zeroable::Zeroable; - use debug::PrintTrait; - - #[storage] - struct Storage { - _world: ContractAddress, - } - - #[event] - #[derive(Clone, Drop, starknet::Event)] - enum Event { - TransferSingle: TransferSingle, - TransferBatch: TransferBatch, - ApprovalForAll: ApprovalForAll - } - - #[derive(Clone, Drop, starknet::Event)] - struct TransferSingle { - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - id: u256, - value: u256 - } - - #[derive(Clone, Drop, starknet::Event)] - struct TransferBatch { - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Array, - values: Array - } - - #[derive(Clone, Drop, starknet::Event)] - struct ApprovalForAll { - owner: ContractAddress, - operator: ContractAddress, - approved: bool - } - - mod Errors { - const INVALID_TOKEN_ID: felt252 = 'ERC1155: invalid token ID'; - const INVALID_ACCOUNT: felt252 = 'ERC1155: invalid account'; - const UNAUTHORIZED: felt252 = 'ERC1155: unauthorized caller'; - const SELF_APPROVAL: felt252 = 'ERC1155: self approval'; - const INVALID_RECEIVER: felt252 = 'ERC1155: invalid receiver'; - const WRONG_SENDER: felt252 = 'ERC1155: wrong sender'; - const SAFE_MINT_FAILED: felt252 = 'ERC1155: safe mint failed'; - const SAFE_TRANSFER_FAILED: felt252 = 'ERC1155: safe transfer failed'; - const INVALID_ARRAY_LENGTH: felt252 = 'ERC1155: invalid array length'; - const INSUFFICIENT_BALANCE: felt252 = 'ERC1155: insufficient balance'; - } - - #[constructor] - fn constructor( - ref self: ContractState, - world: ContractAddress, - name: felt252, - symbol: felt252, - base_uri: felt252, - ) { - self._world.write(world); - self.initializer(name, symbol, base_uri); - } - - // - // External - // - - // #[external(v0)] - // impl SRC5Impl of ISRC5 { - // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) - // } - // } - - // #[external(v0)] - // impl SRC5CamelImpl of ISRC5Camel { - // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) - // } - // } - - #[external(v0)] - impl ERC1155MetadataImpl of interface::IERC1155Metadata { - fn name(self: @ContractState) -> felt252 { - self.get_meta().name - } - - fn symbol(self: @ContractState) -> felt252 { - self.get_meta().symbol - } - - fn uri(self: @ContractState, token_id: u256) -> felt252 { - //assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - // TODO : concat with id - self.get_uri(token_id) - } - } - - - #[external(v0)] - impl ERC1155Impl of interface::IERC1155 { - fn balance_of(self: @ContractState, account: ContractAddress, id: u256) -> u256 { - assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); - self.get_balance(account, id).amount - } - - fn balance_of_batch( - self: @ContractState, accounts: Array, ids: Array - ) -> Array { - assert(ids.len() == accounts.len(), Errors::INVALID_ARRAY_LENGTH); - - let mut batch_balances = array![]; - let mut index = 0; - loop { - if index == ids.len() { - break batch_balances.clone(); - } - batch_balances.append(self.balance_of(*accounts.at(index), *ids.at(index))); - index += 1; - } - } - - fn set_approval_for_all( - ref self: ContractState, operator: ContractAddress, approved: bool - ) { - self._set_approval_for_all(get_caller_address(), operator, approved) - } - - fn is_approved_for_all( - self: @ContractState, account: ContractAddress, operator: ContractAddress - ) -> bool { - self.get_operator_approval(account, operator).approved - } - - fn safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ) { - assert(to.is_non_zero(), Errors::INVALID_RECEIVER); - assert(from.is_non_zero(), Errors::WRONG_SENDER); - assert( - self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED - ); - - self._safe_transfer_from(from, to, id, amount, data); - } - - fn safe_batch_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ) { - assert(to.is_non_zero(), Errors::INVALID_RECEIVER); - assert(from.is_non_zero(), Errors::WRONG_SENDER); - assert( - self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED - ); - - self._safe_batch_transfer_from(from, to, ids, amounts, data); - } - } - - #[external(v0)] - impl ERC1155CamelOnlyImpl of interface::IERC1155CamelOnly { - fn balanceOf(self: @ContractState, account: ContractAddress, id: u256) -> u256 { - ERC1155Impl::balance_of(self, account, id) - } - - fn balanceOfBatch( - self: @ContractState, accounts: Array, ids: Array - ) -> Array { - ERC1155Impl::balance_of_batch(self, accounts, ids) - } - - fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { - ERC1155Impl::set_approval_for_all(ref self, operator, approved); - } - fn isApprovedForAll( - self: @ContractState, account: ContractAddress, operator: ContractAddress - ) -> bool { - ERC1155Impl::is_approved_for_all(self, account, operator) - } - fn safeTransferFrom( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ) { - ERC1155Impl::safe_transfer_from(ref self, from, to, id, amount, data); - } - fn safeBatchTransferFrom( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ) { - ERC1155Impl::safe_batch_transfer_from(ref self, from, to, ids, amounts, data); - } - } - - // - // Internal - // - - #[generate_trait] - impl WorldInteractionsImpl of WorldInteractionsTrait { - fn world(self: @ContractState) -> IWorldDispatcher { - IWorldDispatcher { contract_address: self._world.read() } - } - - fn get_meta(self: @ContractState) -> ERC1155Meta { - get!(self.world(), get_contract_address(), ERC1155Meta) - } - - fn get_uri(self: @ContractState, token_id: u256) -> felt252 { - // TODO : concat with id when we have string type - self.get_meta().base_uri - } - - fn get_balance(self: @ContractState, account: ContractAddress, id: u256) -> ERC1155Balance { - get!(self.world(), (get_contract_address(), account, id), ERC1155Balance) - } - - fn get_operator_approval( - self: @ContractState, owner: ContractAddress, operator: ContractAddress - ) -> ERC1155OperatorApproval { - get!(self.world(), (get_contract_address(), owner, operator), ERC1155OperatorApproval) - } - - fn set_operator_approval( - ref self: ContractState, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ) { - set!( - self.world(), - ERC1155OperatorApproval { token: get_contract_address(), owner, operator, approved } - ); - self.emit_event(ApprovalForAll { owner, operator, approved }); - } - - fn set_balance(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) { - set!( - self.world(), ERC1155Balance { token: get_contract_address(), account, id, amount } - ); - } - - fn update_balances( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - ) { - self.set_balance(from, id, self.get_balance(from, id).amount - amount); - self.set_balance(to, id, self.get_balance(to, id).amount + amount); - } - - fn emit_event< - S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SClone: Clone - >( - ref self: ContractState, event: S - ) { - self.emit(event.clone()); - emit!(self.world(), event); - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { - let meta = ERC1155Meta { token: get_contract_address(), name, symbol, base_uri }; - set!(self.world(), (meta)); - // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); - // src5::SRC5::InternalImpl::register_interface( - // ref unsafe_state, interface::IERC721_METADATA_ID - // ); - } - - fn _is_approved_for_all_or_owner( - self: @ContractState, from: ContractAddress, caller: ContractAddress - ) -> bool { - caller == from || self.is_approved_for_all(from, caller) - } - - fn _set_approval_for_all( - ref self: ContractState, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ) { - assert(owner != operator, Errors::SELF_APPROVAL); - self.set_operator_approval(owner, operator, approved); - } - - fn _safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ) { - self.update_balances(from, to, id, amount); - // assert( - // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED - // ); - - self - .emit_event( - TransferSingle { operator: get_caller_address(), from, to, id, value: amount } - ); - } - - fn _safe_batch_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ) { - assert(ids.len() == amounts.len(), Errors::INVALID_ARRAY_LENGTH); - - let mut ids_span = ids.span(); - let mut amounts_span = amounts.span(); - - loop { - if ids_span.len() == 0 { - break (); - } - let id = *ids_span.pop_front().unwrap(); - let amount = *amounts_span.pop_front().unwrap(); - self.update_balances(from, to, id, amount); - // assert( - // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED - // ); - }; - - self - .emit_event( - TransferBatch { operator: get_caller_address(), from, to, ids, values: amounts } - ); - } - - fn _mint(ref self: ContractState, to: ContractAddress, id: u256, amount: u256) { - assert(to.is_non_zero(), Errors::INVALID_RECEIVER); - - self.set_balance(to, id, self.get_balance(to, id).amount + amount); - - self - .emit_event( - TransferSingle { - operator: get_caller_address(), - from: Zeroable::zero(), - to, - id, - value: amount - } - ); - } - - fn _burn(ref self: ContractState, id: u256, amount: u256) { - let caller = get_caller_address(); - assert(self.get_balance(caller, id).amount >= amount, Errors::INSUFFICIENT_BALANCE); - - self.set_balance(caller, id, self.get_balance(caller, id).amount - amount); - - self - .emit_event( - TransferSingle { - operator: get_caller_address(), - from: caller, - to: Zeroable::zero(), - id, - value: amount - } - ); - } - - fn _safe_mint( - ref self: ContractState, - to: ContractAddress, - id: u256, - amount: u256, - data: Span - ) { - self._mint(to, id, amount); - // assert( - // _check_on_erc1155_received(Zeroable::zero(), to, id, data), - // Errors::SAFE_MINT_FAILED - // ); - } - } -//#[internal] -// fn _check_on_erc1155_received( -// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span -// ) -> bool { -// if (DualCaseSRC5 { contract_address: to } -// .supports_interface(interface::IERC1155_RECEIVER_ID)) { -// DualCaseERC1155Receiver { contract_address: to } -// .on_erc1155_received( -// get_caller_address(), from, token_id, data -// ) == interface::IERC1155_RECEIVER_ID -// } else { -// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) -// } -// } - -} diff --git a/crates/dojo-erc/src/token/erc1155/interface.cairo b/crates/dojo-erc/src/token/erc1155/interface.cairo deleted file mode 100644 index ecce8fb978..0000000000 --- a/crates/dojo-erc/src/token/erc1155/interface.cairo +++ /dev/null @@ -1,62 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IERC1155 { - fn balance_of(self: @TState, account: ContractAddress, id: u256) -> u256; - fn balance_of_batch( - self: @TState, accounts: Array, ids: Array - ) -> Array; - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - fn is_approved_for_all( - self: @TState, account: ContractAddress, operator: ContractAddress - ) -> bool; - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ); - fn safe_batch_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ); -} - -#[starknet::interface] -trait IERC1155CamelOnly { - fn balanceOf(self: @TState, account: ContractAddress, id: u256) -> u256; - fn balanceOfBatch( - self: @TState, accounts: Array, ids: Array - ) -> Array; - fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); - fn isApprovedForAll(self: @TState, account: ContractAddress, operator: ContractAddress) -> bool; - fn safeTransferFrom( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ); - fn safeBatchTransferFrom( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ); -} - -#[starknet::interface] -trait IERC1155Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn uri(self: @TState, token_id: u256) -> felt252; -} diff --git a/crates/dojo-erc/src/token/erc1155/models.cairo b/crates/dojo-erc/src/token/erc1155/models.cairo deleted file mode 100644 index fa021aa987..0000000000 --- a/crates/dojo-erc/src/token/erc1155/models.cairo +++ /dev/null @@ -1,33 +0,0 @@ -use starknet::ContractAddress; - -#[derive(Model, Copy, Drop, Serde)] -struct ERC1155Meta { - #[key] - token: ContractAddress, - name: felt252, - symbol: felt252, - base_uri: felt252, -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC1155OperatorApproval { - #[key] - token: ContractAddress, - #[key] - owner: ContractAddress, - #[key] - operator: ContractAddress, - approved: bool -} - - -#[derive(Model, Copy, Drop, Serde)] -struct ERC1155Balance { - #[key] - token: ContractAddress, - #[key] - account: ContractAddress, - #[key] - id: u256, - amount: u256 -} \ No newline at end of file diff --git a/crates/dojo-erc/src/token/erc20.cairo b/crates/dojo-erc/src/token/erc20.cairo deleted file mode 100644 index 238e05a1e6..0000000000 --- a/crates/dojo-erc/src/token/erc20.cairo +++ /dev/null @@ -1,331 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IERC20 { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} - -trait IERC20CamelOnly { - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} - -#[starknet::contract] -mod ERC20 { - use dojo_erc::token::erc20_models::{ERC20Allowance, ERC20Balance, ERC20Meta}; - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use integer::BoundedInt; - use super::IERC20; - use super::IERC20CamelOnly; - use starknet::ContractAddress; - use starknet::{get_caller_address, get_contract_address}; - use zeroable::Zeroable; - use debug::PrintTrait; - - - #[storage] - struct Storage { - _world: ContractAddress, - } - - #[event] - #[derive(Copy, Drop, starknet::Event)] - enum Event { - Transfer: Transfer, - Approval: Approval, - } - - #[derive(Copy, Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: u256 - } - - #[derive(Copy, Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: u256 - } - - mod Errors { - const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; - const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; - const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; - const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; - const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; - const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; - } - - #[constructor] - fn constructor( - ref self: ContractState, - world: ContractAddress, - name: felt252, - symbol: felt252, - initial_supply: u256, - recipient: ContractAddress - ) { - self._world.write(world); - self.initializer(name, symbol); - self._mint(recipient, initial_supply); - } - - // - // External - // - - #[external(v0)] - impl ERC20Impl of IERC20 { - fn name(self: @ContractState) -> felt252 { - self.get_meta().name - } - - fn symbol(self: @ContractState) -> felt252 { - self.get_meta().symbol - } - - fn decimals(self: @ContractState) -> u8 { - 18 - } - - fn total_supply(self: @ContractState) -> u256 { - self.get_meta().total_supply - } - - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - self.get_balance(account).amount - } - - fn allowance( - self: @ContractState, owner: ContractAddress, spender: ContractAddress - ) -> u256 { - self.get_allowance(owner, spender).amount - } - - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - let sender = get_caller_address(); - self._transfer(sender, recipient, amount); - true - } - - fn transfer_from( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - let caller = get_caller_address(); - self._spend_allowance(sender, caller, amount); - self._transfer(sender, recipient, amount); - true - } - - fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { - let owner = get_caller_address(); - self - .set_allowance( - ERC20Allowance { token: get_contract_address(), owner, spender, amount } - ); - true - } - } - - #[external(v0)] - impl ERC20CamelOnlyImpl of IERC20CamelOnly { - fn totalSupply(self: @ContractState) -> u256 { - ERC20Impl::total_supply(self) - } - - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - ERC20Impl::balance_of(self, account) - } - - fn transferFrom( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - ERC20Impl::transfer_from(ref self, sender, recipient, amount) - } - } - - #[external(v0)] - fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) -> bool { - self.update_allowance(get_caller_address(), spender, 0, added_value); - true - } - - #[external(v0)] - fn increaseAllowance( - ref self: ContractState, spender: ContractAddress, addedValue: u256 - ) -> bool { - increase_allowance(ref self, spender, addedValue) - } - - #[external(v0)] - fn decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u256 - ) -> bool { - self.update_allowance(get_caller_address(), spender, subtracted_value, 0); - true - } - - #[external(v0)] - fn decreaseAllowance( - ref self: ContractState, spender: ContractAddress, subtractedValue: u256 - ) -> bool { - decrease_allowance(ref self, spender, subtractedValue) - } - - // - // Internal - // - - #[generate_trait] - impl WorldInteractionsImpl of WorldInteractionsTrait { - fn world(self: @ContractState) -> IWorldDispatcher { - IWorldDispatcher { contract_address: self._world.read() } - } - - fn get_meta(self: @ContractState) -> ERC20Meta { - get!(self.world(), get_contract_address(), ERC20Meta) - } - - // Helper function to update total_supply model - fn update_total_supply(ref self: ContractState, subtract: u256, add: u256) { - let mut meta = self.get_meta(); - // adding and subtracting is fewer steps than if - meta.total_supply = meta.total_supply - subtract; - meta.total_supply = meta.total_supply + add; - set!(self.world(), (meta)); - } - - // Helper function for balance model - fn get_balance(self: @ContractState, account: ContractAddress) -> ERC20Balance { - get!(self.world(), (get_contract_address(), account), ERC20Balance) - } - - fn update_balance( - ref self: ContractState, account: ContractAddress, subtract: u256, add: u256 - ) { - let mut balance: ERC20Balance = self.get_balance(account); - // adding and subtracting is fewer steps than if - balance.amount = balance.amount - subtract; - balance.amount = balance.amount + add; - set!(self.world(), (balance)); - } - - // Helper function for allowance model - fn get_allowance( - self: @ContractState, owner: ContractAddress, spender: ContractAddress, - ) -> ERC20Allowance { - get!(self.world(), (get_contract_address(), owner, spender), ERC20Allowance) - } - - fn update_allowance( - ref self: ContractState, - owner: ContractAddress, - spender: ContractAddress, - subtract: u256, - add: u256 - ) { - let mut allowance = self.get_allowance(owner, spender); - // adding and subtracting is fewer steps than if - allowance.amount = allowance.amount - subtract; - allowance.amount = allowance.amount + add; - self.set_allowance(allowance); - } - - fn set_allowance(ref self: ContractState, allowance: ERC20Allowance) { - assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); - assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); - set!(self.world(), (allowance)); - self - .emit_event( - Approval { - owner: allowance.owner, spender: allowance.spender, value: allowance.amount - } - ); - } - - fn emit_event< - S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy - >( - ref self: ContractState, event: S - ) { - self.emit(event); - emit!(self.world(), event); - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { - let meta = ERC20Meta { token: get_contract_address(), name, symbol, total_supply: 0 }; - set!(self.world(), (meta)); - } - - fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); - self.update_total_supply(0, amount); - self.update_balance(recipient, 0, amount); - self.emit_event(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); - } - - fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { - assert(!account.is_zero(), Errors::BURN_FROM_ZERO); - self.update_total_supply(amount, 0); - self.update_balance(account, amount, 0); - self.emit_event(Transfer { from: account, to: Zeroable::zero(), value: amount }); - } - - fn _approve( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - self - .set_allowance( - ERC20Allowance { token: get_contract_address(), owner, spender, amount } - ); - } - - fn _transfer( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); - assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); - self.update_balance(sender, amount, 0); - self.update_balance(recipient, 0, amount); - self.emit_event(Transfer { from: sender, to: recipient, value: amount }); - } - - fn _spend_allowance( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - let current_allowance = self.get_allowance(owner, spender).amount; - if current_allowance != BoundedInt::max() { - self.update_allowance(owner, spender, amount, 0); - } - } - } -} diff --git a/crates/dojo-erc/src/token/erc20_models.cairo b/crates/dojo-erc/src/token/erc20_models.cairo deleted file mode 100644 index ab68718dc9..0000000000 --- a/crates/dojo-erc/src/token/erc20_models.cairo +++ /dev/null @@ -1,30 +0,0 @@ -use starknet::ContractAddress; - -#[derive(Model, Copy, Drop, Serde)] -struct ERC20Balance { - #[key] - token: ContractAddress, - #[key] - account: ContractAddress, - amount: u256, -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC20Allowance { - #[key] - token: ContractAddress, - #[key] - owner: ContractAddress, - #[key] - spender: ContractAddress, - amount: u256, -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC20Meta { - #[key] - token: ContractAddress, - name: felt252, - symbol: felt252, - total_supply: u256, -} diff --git a/crates/dojo-erc/src/token/erc721.cairo b/crates/dojo-erc/src/token/erc721.cairo deleted file mode 100644 index 15b96462ac..0000000000 --- a/crates/dojo-erc/src/token/erc721.cairo +++ /dev/null @@ -1,5 +0,0 @@ -mod erc721; -mod models; -mod interface; - -use erc721::ERC721; diff --git a/crates/dojo-erc/src/token/erc721/erc721.cairo b/crates/dojo-erc/src/token/erc721/erc721.cairo deleted file mode 100644 index ffd7915d82..0000000000 --- a/crates/dojo-erc/src/token/erc721/erc721.cairo +++ /dev/null @@ -1,443 +0,0 @@ -#[starknet::contract] -mod ERC721 { - use dojo_erc::token::erc721::models::{ - ERC721Meta, ERC721OperatorApproval, ERC721Owner, ERC721Balance, ERC721TokenApproval - }; - use dojo_erc::token::erc721::interface; - use dojo_erc::token::erc721::interface::{IERC721, IERC721CamelOnly}; - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use integer::BoundedInt; - use starknet::ContractAddress; - use starknet::{get_caller_address, get_contract_address}; - use zeroable::Zeroable; - use debug::PrintTrait; - - - #[storage] - struct Storage { - _world: ContractAddress, - } - - #[event] - #[derive(Copy, Drop, starknet::Event)] - enum Event { - Transfer: Transfer, - Approval: Approval, - ApprovalForAll: ApprovalForAll - } - - #[derive(Copy, Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - token_id: u256 - } - - #[derive(Copy, Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - approved: ContractAddress, - token_id: u256 - } - - #[derive(Copy, Drop, starknet::Event)] - struct ApprovalForAll { - owner: ContractAddress, - operator: ContractAddress, - approved: bool - } - - mod Errors { - const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; - const INVALID_ACCOUNT: felt252 = 'ERC721: invalid account'; - const UNAUTHORIZED: felt252 = 'ERC721: unauthorized caller'; - const APPROVAL_TO_OWNER: felt252 = 'ERC721: approval to owner'; - const SELF_APPROVAL: felt252 = 'ERC721: self approval'; - const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; - const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; - const WRONG_SENDER: felt252 = 'ERC721: wrong sender'; - const SAFE_MINT_FAILED: felt252 = 'ERC721: safe mint failed'; - const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; - } - - #[constructor] - fn constructor( - ref self: ContractState, - world: ContractAddress, - name: felt252, - symbol: felt252, - base_uri: felt252, - recipient: ContractAddress, - token_id: u256 - ) { - self._world.write(world); - self.initializer(name, symbol, base_uri); - self._mint(recipient, token_id); - } - - // - // External - // - - // #[external(v0)] - // impl SRC5Impl of ISRC5 { - // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) - // } - // } - - // #[external(v0)] - // impl SRC5CamelImpl of ISRC5Camel { - // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) - // } - // } - - #[external(v0)] - impl ERC721MetadataImpl of interface::IERC721Metadata { - fn name(self: @ContractState) -> felt252 { - self.get_meta().name - } - - fn symbol(self: @ContractState) -> felt252 { - self.get_meta().symbol - } - - fn token_uri(self: @ContractState, token_id: u256) -> felt252 { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - // TODO : concat with id - self.get_uri(token_id) - } - } - - #[external(v0)] - impl ERC721MetadataCamelOnlyImpl of interface::IERC721MetadataCamelOnly { - fn tokenURI(self: @ContractState, tokenId: u256) -> felt252 { - assert(self._exists(tokenId), Errors::INVALID_TOKEN_ID); - self.get_uri(tokenId) - } - } - - #[external(v0)] - impl ERC721Impl of interface::IERC721 { - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); - self.get_balance(account).amount - } - - fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { - self._owner_of(token_id) - } - - fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - self.get_token_approval(token_id).address - } - - fn is_approved_for_all( - self: @ContractState, owner: ContractAddress, operator: ContractAddress - ) -> bool { - self.get_operator_approval(owner, operator).approved - } - - fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { - let owner = self._owner_of(token_id); - - let caller = get_caller_address(); - assert( - owner == caller || ERC721Impl::is_approved_for_all(@self, owner, caller), - Errors::UNAUTHORIZED - ); - self._approve(to, token_id); - } - - fn set_approval_for_all( - ref self: ContractState, operator: ContractAddress, approved: bool - ) { - self._set_approval_for_all(get_caller_address(), operator, approved) - } - - fn transfer_from( - ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 - ) { - assert( - self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED - ); - self._transfer(from, to, token_id); - } - - fn safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ) { - assert( - self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED - ); - self._safe_transfer(from, to, token_id, data); - } - } - - #[external(v0)] - impl ERC721CamelOnlyImpl of interface::IERC721CamelOnly { - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - ERC721Impl::balance_of(self, account) - } - - fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress { - ERC721Impl::owner_of(self, tokenId) - } - - fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress { - ERC721Impl::get_approved(self, tokenId) - } - - fn isApprovedForAll( - self: @ContractState, owner: ContractAddress, operator: ContractAddress - ) -> bool { - ERC721Impl::is_approved_for_all(self, owner, operator) - } - - fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { - ERC721Impl::set_approval_for_all(ref self, operator, approved) - } - - fn transferFrom( - ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256 - ) { - ERC721Impl::transfer_from(ref self, from, to, tokenId) - } - - fn safeTransferFrom( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ) { - ERC721Impl::safe_transfer_from(ref self, from, to, tokenId, data) - } - } - - // - // Internal - // - - #[generate_trait] - impl WorldInteractionsImpl of WorldInteractionsTrait { - fn world(self: @ContractState) -> IWorldDispatcher { - IWorldDispatcher { contract_address: self._world.read() } - } - - fn get_meta(self: @ContractState) -> ERC721Meta { - get!(self.world(), get_contract_address(), ERC721Meta) - } - - fn get_uri(self: @ContractState, token_id: u256) -> felt252 { - // TODO : concat with id when we have string type - self.get_meta().base_uri - } - - fn get_balance(self: @ContractState, account: ContractAddress) -> ERC721Balance { - get!(self.world(), (get_contract_address(), account), ERC721Balance) - } - - fn get_owner_of(self: @ContractState, token_id: u256) -> ERC721Owner { - get!(self.world(), (get_contract_address(), token_id), ERC721Owner) - } - - fn get_token_approval(self: @ContractState, token_id: u256) -> ERC721TokenApproval { - get!(self.world(), (get_contract_address(), token_id), ERC721TokenApproval) - } - - fn get_operator_approval( - self: @ContractState, owner: ContractAddress, operator: ContractAddress - ) -> ERC721OperatorApproval { - get!(self.world(), (get_contract_address(), owner, operator), ERC721OperatorApproval) - } - - fn set_token_approval( - ref self: ContractState, - owner: ContractAddress, - to: ContractAddress, - token_id: u256, - emit: bool - ) { - set!( - self.world(), - ERC721TokenApproval { token: get_contract_address(), token_id, address: to, } - ); - if emit { - self.emit_event(Approval { owner, approved: to, token_id }); - } - } - - fn set_operator_approval( - ref self: ContractState, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ) { - set!( - self.world(), - ERC721OperatorApproval { token: get_contract_address(), owner, operator, approved } - ); - self.emit_event(ApprovalForAll { owner, operator, approved }); - } - - fn set_balance(ref self: ContractState, account: ContractAddress, amount: u256) { - set!(self.world(), ERC721Balance { token: get_contract_address(), account, amount }); - } - - fn set_owner(ref self: ContractState, token_id: u256, address: ContractAddress) { - set!(self.world(), ERC721Owner { token: get_contract_address(), token_id, address }); - } - - fn emit_event< - S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy - >( - ref self: ContractState, event: S - ) { - self.emit(event); - emit!(self.world(), event); - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { - let meta = ERC721Meta { token: get_contract_address(), name, symbol, base_uri }; - set!(self.world(), (meta)); - // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); - // src5::SRC5::InternalImpl::register_interface( - // ref unsafe_state, interface::IERC721_METADATA_ID - // ); - } - - fn _owner_of(self: @ContractState, token_id: u256) -> ContractAddress { - let owner = self.get_owner_of(token_id).address; - match owner.is_zero() { - bool::False(()) => owner, - bool::True(()) => panic_with_felt252(Errors::INVALID_TOKEN_ID) - } - } - - fn _exists(self: @ContractState, token_id: u256) -> bool { - let owner = self.get_owner_of(token_id).address; - owner.is_non_zero() - } - - fn _is_approved_or_owner( - self: @ContractState, spender: ContractAddress, token_id: u256 - ) -> bool { - let owner = self._owner_of(token_id); - let is_approved_for_all = ERC721Impl::is_approved_for_all(self, owner, spender); - owner == spender - || is_approved_for_all - || spender == ERC721Impl::get_approved(self, token_id) - } - - fn _approve(ref self: ContractState, to: ContractAddress, token_id: u256) { - let owner = self._owner_of(token_id); - assert(owner != to, Errors::APPROVAL_TO_OWNER); - - self.set_token_approval(owner, to, token_id, true); - } - - fn _set_approval_for_all( - ref self: ContractState, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ) { - assert(owner != operator, Errors::SELF_APPROVAL); - self.set_operator_approval(owner, operator, approved); - } - - fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { - assert(!to.is_zero(), Errors::INVALID_RECEIVER); - assert(!self._exists(token_id), Errors::ALREADY_MINTED); - - self.set_balance(to, self.get_balance(to).amount + 1); - self.set_owner(token_id, to); - - self.emit_event(Transfer { from: Zeroable::zero(), to, token_id }); - } - - fn _transfer( - ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 - ) { - assert(!to.is_zero(), Errors::INVALID_RECEIVER); - let owner = self._owner_of(token_id); - assert(from == owner, Errors::WRONG_SENDER); - - // Implicit clear approvals, no need to emit an event - self.set_token_approval(owner, Zeroable::zero(), token_id, false); - - self.set_balance(from, self.get_balance(from).amount - 1); - self.set_balance(to, self.get_balance(to).amount + 1); - self.set_owner(token_id, to); - - self.emit_event(Transfer { from, to, token_id }); - } - - fn _burn(ref self: ContractState, token_id: u256) { - let owner = self._owner_of(token_id); - - // Implicit clear approvals, no need to emit an event - self.set_token_approval(owner, Zeroable::zero(), token_id, false); - - self.set_balance(owner, self.get_balance(owner).amount - 1); - self.set_owner(token_id, Zeroable::zero()); - - self.emit_event(Transfer { from: owner, to: Zeroable::zero(), token_id }); - } - - fn _safe_mint( - ref self: ContractState, to: ContractAddress, token_id: u256, data: Span - ) { - self._mint(to, token_id); - // assert( - // _check_on_erc721_received(Zeroable::zero(), to, token_id, data), - // Errors::SAFE_MINT_FAILED - // ); - } - - fn _safe_transfer( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ) { - self._transfer(from, to, token_id); - // assert( - // _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED - // ); - } - // fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { - // assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - // self._token_uri.write(token_id, token_uri) - // } - } - -//#[internal] -// fn _check_on_erc721_received( -// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span -// ) -> bool { -// if (DualCaseSRC5 { contract_address: to } -// .supports_interface(interface::IERC721_RECEIVER_ID)) { -// DualCaseERC721Receiver { contract_address: to } -// .on_erc721_received( -// get_caller_address(), from, token_id, data -// ) == interface::IERC721_RECEIVER_ID -// } else { -// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) -// } -// } -} diff --git a/crates/dojo-erc/src/token/erc721/interface.cairo b/crates/dojo-erc/src/token/erc721/interface.cairo deleted file mode 100644 index 730ad2e027..0000000000 --- a/crates/dojo-erc/src/token/erc721/interface.cairo +++ /dev/null @@ -1,60 +0,0 @@ -use starknet::ContractAddress; - -const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; -const IERC721_METADATA_ID: felt252 = - 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; -const IERC721_RECEIVER_ID: felt252 = - 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; - -#[starknet::interface] -trait IERC721 { - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn owner_of(self: @TState, token_id: u256) -> ContractAddress; - fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn approve(ref self: TState, to: ContractAddress, token_id: u256); - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - fn get_approved(self: @TState, token_id: u256) -> ContractAddress; - fn is_approved_for_all( - self: @TState, owner: ContractAddress, operator: ContractAddress - ) -> bool; -} - -#[starknet::interface] -trait IERC721CamelOnly { - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; - fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); - fn safeTransferFrom( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ); - fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); - fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; - fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; -} - -// -// IERC721Metadata -// - -#[starknet::interface] -trait IERC721Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; -} - -#[starknet::interface] -trait IERC721MetadataCamelOnly { - fn tokenURI(self: @TState, tokenId: u256) -> felt252; -} diff --git a/crates/dojo-erc/src/token/erc721/models.cairo b/crates/dojo-erc/src/token/erc721/models.cairo deleted file mode 100644 index 1bc4bcadd6..0000000000 --- a/crates/dojo-erc/src/token/erc721/models.cairo +++ /dev/null @@ -1,48 +0,0 @@ -use starknet::ContractAddress; - -#[derive(Model, Copy, Drop, Serde)] -struct ERC721Meta { - #[key] - token: ContractAddress, - name: felt252, - symbol: felt252, - base_uri: felt252, -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC721OperatorApproval { - #[key] - token: ContractAddress, - #[key] - owner: ContractAddress, - #[key] - operator: ContractAddress, - approved: bool -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC721Owner { - #[key] - token: ContractAddress, - #[key] - token_id: u256, - address: ContractAddress -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC721Balance { - #[key] - token: ContractAddress, - #[key] - account: ContractAddress, - amount: u256, -} - -#[derive(Model, Copy, Drop, Serde)] -struct ERC721TokenApproval { - #[key] - token: ContractAddress, - #[key] - token_id: u256, - address: ContractAddress, -} \ No newline at end of file diff --git a/crates/dojo-physics/.gitignore b/crates/dojo-physics/.gitignore deleted file mode 100644 index 1de565933b..0000000000 --- a/crates/dojo-physics/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/crates/dojo-physics/Scarb.toml b/crates/dojo-physics/Scarb.toml deleted file mode 100644 index c23523ebaf..0000000000 --- a/crates/dojo-physics/Scarb.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -cairo-version = "2.0.0-rc4" -description = "Implementations of a physics library for the Dojo framework" -name = "dojo_physics" -version = "0.3.11" - -[dependencies] -cubit = { git = "https://github.com/influenceth/cubit" } -dojo = { path = "../dojo-core" } - -[[target.dojo]] diff --git a/crates/dojo-physics/examples/projectile/Scarb.toml b/crates/dojo-physics/examples/projectile/Scarb.toml deleted file mode 100644 index c54bfcb9ee..0000000000 --- a/crates/dojo-physics/examples/projectile/Scarb.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "projectile" -version = "0.3.11" - -[dependencies] -cubit = { git = "https://github.com/influenceth/cubit" } -dojo_physics = { path = "../.." } diff --git a/crates/dojo-physics/examples/projectile/cairo_project.toml b/crates/dojo-physics/examples/projectile/cairo_project.toml deleted file mode 100644 index 5ec5b278ba..0000000000 --- a/crates/dojo-physics/examples/projectile/cairo_project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[crate_roots] -dojo_physics = "../../src" -projectile = "src" diff --git a/crates/dojo-physics/examples/projectile/src/lib.cairo b/crates/dojo-physics/examples/projectile/src/lib.cairo deleted file mode 100644 index ee069f413f..0000000000 --- a/crates/dojo-physics/examples/projectile/src/lib.cairo +++ /dev/null @@ -1 +0,0 @@ -mod projectile; \ No newline at end of file diff --git a/crates/dojo-physics/examples/projectile/src/projectile.cairo b/crates/dojo-physics/examples/projectile/src/projectile.cairo deleted file mode 100644 index f6be9b8101..0000000000 --- a/crates/dojo-physics/examples/projectile/src/projectile.cairo +++ /dev/null @@ -1,245 +0,0 @@ -use core::option::OptionTrait; -use array::ArrayTrait; -use debug::PrintTrait; -use array::ArrayTCloneImpl; -use array::SpanTrait; -use clone::Clone; -use traits::PartialOrd; - -use cubit::test::helpers::assert_precise; -use cubit::types::fixed::{Fixed, FixedPartialOrd, FixedTrait, ONE_u128}; -use cubit::math::trig; - -use dojo_physics::vec2::{Vec2, Vec2Trait}; - -fn main() -> (usize, Array::, Array::) { - // to be inputs for #[view] function - // v_0_mag_felt: felt252, theta_0_deg_felt: felt252, x_0_felt: felt252, y_0_felt: felt252 - - // - // Projectile parameters - // - /// Inputs: to be contract inputs for view function `main` - /// Launch velocity magnitude, 0 <= v_0_felt <= 100 - let v_0_mag_felt = 100; - /// Launch angle in degrees, -180 <= theta_0_deg_felt <= 180 - let theta_0_deg_felt = 65; - /// Initial horizontal position, x_min <= x_0_felt <= x_max - let x_0_felt = 0; - /// Initial vertical position, y_min <= y_0_felt <= y_max - let y_0_felt = 0; - /// Convert inputs to signed fixed-point - let v_0_mag = FixedTrait::from_unscaled_felt(v_0_mag_felt); - let theta_0_deg = FixedTrait::from_unscaled_felt(theta_0_deg_felt); - let x_0 = FixedTrait::from_unscaled_felt(x_0_felt); - let y_0 = FixedTrait::from_unscaled_felt(y_0_felt); - - /// Convert theta_0_deg to radians - let theta_0 = deg_to_rad(theta_0_deg); - - // Gravitational acceleration magnitude - let g = FixedTrait::new(98 * ONE_u128 / 10, false); // 9.8 - // Plot parameters - let x_max = FixedTrait::from_unscaled_felt(1000); - let x_min = FixedTrait::from_unscaled_felt(-1000); - let y_max = FixedTrait::from_unscaled_felt(500); - let y_min = FixedTrait::from_unscaled_felt(-500); - // Check that inputs are within required ranges - assert(v_0_mag.mag <= 100 * ONE_u128, 'need v_0_mag_felt <= 100'); - assert(v_0_mag.mag > 0 * ONE_u128, 'need v_0_mag_felt > 0'); - assert(v_0_mag.sign == false, 'need v_0_mag_felt > 0'); - // `theta_0_deg.mag` not exact after conversion, so use 180.0000001 - assert(theta_0_deg.mag <= 180000001 * ONE_u128 / 1000000, '-180 <= theta_0_deg_felt <= 180'); - assert(FixedPartialOrd::le(x_0, x_max), 'need x_0 <= x_max'); - assert(FixedPartialOrd::ge(x_0, x_min), 'need x_0 >= x_min'); - assert(FixedPartialOrd::le(y_0, y_max), 'need y_0 <= y_max'); - assert(FixedPartialOrd::ge(y_0, y_min), 'need y_0 >= y_min'); - // Initial position vector - let r_0 = Vec2Trait::::new(x_0, y_0); - // Initial velocity vector - let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); - - // Time interval between plotted points - let delta_t = FixedTrait::from_unscaled_felt(2); // arbitrary value 2 chosen - - // Tuples to pass to functions - let plot_params = (x_max, x_min, y_max, y_min); - let motion_params = (r_0, v_0, g, delta_t); - let (mut x_s, mut y_s) = fill_position_s(plot_params, motion_params); - (x_s.len(), x_s, y_s) -} - -fn deg_to_rad(theta_deg: Fixed) -> Fixed { - let pi = FixedTrait::new(trig::PI_u128, false); - let one_eighty = FixedTrait::new(180 * ONE_u128, false); - theta_deg * pi / one_eighty -} - -// Creates Fixed type Vec2 from magnitude, theta in radians -fn vec2_from_mag_theta(mag: Fixed, theta: Fixed) -> Vec2 { - let x_comp = mag * trig::cos(theta); // trig::cos works only for Fixed type - let y_comp = mag * trig::sin(theta); // trig::sin works only for Fixed type - Vec2:: { x: x_comp, y: y_comp } -} - -fn fill_position_s( - plot_params: (Fixed, Fixed, Fixed, Fixed), - motion_params: (Vec2, Vec2, Fixed, Fixed) -) -> (Array::, Array::) { - let (x_max, x_min, _y_max, y_min) = plot_params; - let (r_0, v_0, g, delta_t) = motion_params; - let mut x_s = ArrayTrait::::new(); - let mut y_s = ArrayTrait::::new(); - - let one = FixedTrait::new(ONE_u128, false); - let mut n = FixedTrait::new(0, false); - - loop { - // match withdraw_gas() { - // Option::Some(_) => {}, - // Option::None(_) => { - // let mut data = ArrayTrait::new(); - // data.append('Out of gas'); - // panic(data); - // }, - // } - let t = n * delta_t; - // 'n'.print(); - // n.mag.print(); - let x = calc_x(r_0.x, v_0.x, t); - // 'x'.print(); - // x.mag.print(); - // x.sign.print(); - let y = calc_y(r_0.y, v_0.y, g, t); - // 'y'.print(); - // y.mag.print(); - // y.sign.print(); - if x >= x_max | x <= x_min | y <= y_min { - break (); - } - - x_s.append(x); - y_s.append(y); - - n += one; - }; - - (x_s, y_s) -} - -fn calc_x(x_0: Fixed, v_0x: Fixed, t: Fixed) -> Fixed { - x_0 + v_0x * t -} - -fn calc_y(y_0: Fixed, v_0y: Fixed, g: Fixed, t: Fixed) -> Fixed { - let half = FixedTrait::new(5 * ONE_u128 / 10, false); - y_0 + v_0y * t - half * g * t * t -} - -#[test] -#[available_gas(2000000)] -fn test_deg_to_rad() { - let sixty = FixedTrait::new(60 * ONE_u128, false); - let theta = deg_to_rad(sixty); - assert_precise(theta, 19317385221538994246, 'invalid PI/3', Option::None(())); - assert(theta.sign == false, 'invalid sign'); - - let minus_120 = FixedTrait::new(120 * ONE_u128, true); - let theta = deg_to_rad(minus_120); - assert_precise(theta, -38634770443077988493, 'invalid -2*PI/3', Option::None(())); - assert(theta.sign == true, 'invalid sign'); -} - -#[test] -#[available_gas(20000000)] -fn test_vec2_from_mag_theta() { - let mag = FixedTrait::new(100 * ONE_u128, false); - let sixty = FixedTrait::new(60 * ONE_u128, false); - let theta = deg_to_rad(sixty); - let vec2 = vec2_from_mag_theta(mag, theta); - assert_precise(vec2.x, 922337203685477580800, 'invalid vec2.x mag', Option::None(())); // 50 - assert(vec2.x.sign == false, 'invalid vec2.x.sign'); - assert_precise(vec2.y, 1597534898494251510150, 'invalid vec2.y mag', Option::None(())); // 86.6 - assert(vec2.y.sign == false, 'invalid vec2.y.sig'); - - let minus_120 = FixedTrait::new(120 * ONE_u128, true); - let theta = deg_to_rad(minus_120); - let vec2 = vec2_from_mag_theta(mag, theta); - assert_precise(vec2.x, -922337203685477580800, 'invalid vec2.x mag', Option::None(())); // -50 - assert(vec2.x.sign == true, 'invalid vec2.x.sign'); - assert_precise( - vec2.y, -1597534898494251510150, 'invalid vec2.y mag', Option::None(()) - ); // -86.6 - assert(vec2.y.sign == true, 'invalid vec2.y.sig'); -} - -#[test] -#[available_gas(20000000)] -fn test_fill_position_s() { - let v_0_mag = FixedTrait::from_unscaled_felt(100); - let theta_0_deg = FixedTrait::from_unscaled_felt(65); - let theta_0 = deg_to_rad(theta_0_deg); - let x_0 = FixedTrait::from_unscaled_felt(0); - let y_0 = FixedTrait::from_unscaled_felt(0); - - let x_max = FixedTrait::from_unscaled_felt(1000); - let x_min = FixedTrait::from_unscaled_felt(-1000); - let y_max = FixedTrait::from_unscaled_felt(500); - let y_min = FixedTrait::from_unscaled_felt(-500); - - let r_0 = Vec2Trait::::new(x_0, y_0); - let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); - let g = FixedTrait::new(98 * ONE_u128 / 10, false); - let delta_t = FixedTrait::from_unscaled_felt(2); - - let plot_params = (x_max, x_min, y_max, y_min); - let motion_params = (r_0, v_0, g, delta_t); - - let mut position_s: (Array, Array) = fill_position_s(plot_params, motion_params); - - let (x_s, y_s) = position_s; - let length = x_s.len(); - assert(length == 12, 'invalid length'); - - assert_precise( - *x_s[5], 7795930915206679528264, 'invalid x_s[5]', Option::None(()) - ); // 422.61826174069944 - assert(*x_s.at(5).sign == false, 'invalid sign'); - assert_precise( - *y_s[5], 7679523203357457794972, 'invalid y_s[5]', Option::None(()) - ); // 416.3077870366498 - assert(*y_s.at(5).sign == false, 'invalid sign'); - - assert_precise( - *x_s[10], 15591861830413359425462, 'invalid x_s[10]', Option::None(()) - ); // 845.2365234813989, custom precision 1e-6 - assert(*x_s.at(10).sign == false, 'invalid sign'); - assert_precise( - *y_s[10], -2718762785520446838411, 'invalid y_s[10]', Option::None(()) - ); // -147.3844259267005, custom precision 1e-6 - assert(*y_s.at(10).sign == true, 'invalid sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_calc_x() { - let x_0 = FixedTrait::new(100 * ONE_u128, false); - let v_0x = FixedTrait::new(50 * ONE_u128, false); - let t = FixedTrait::new(16 * ONE_u128, false); - let x = calc_x(x_0, v_0x, t); - assert(x.mag == 900 * ONE_u128, 'invalid mag'); - assert(x.sign == false, 'invalid sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_calc_y() { - let y_0 = FixedTrait::new(100 * ONE_u128, false); - let v_0y = FixedTrait::new(50 * ONE_u128, false); - let t = FixedTrait::new(16 * ONE_u128, false); - let g = FixedTrait::new(98 * ONE_u128 / 10, false); - - let y = calc_y(y_0, v_0y, g, t); - assert_precise(y, -6537526099722665092710, 'invalid y', Option::None(())); // -354.4 - assert(y.sign == true, 'invalid sign'); -} diff --git a/crates/dojo-physics/src/lib.cairo b/crates/dojo-physics/src/lib.cairo deleted file mode 100644 index f26f218845..0000000000 --- a/crates/dojo-physics/src/lib.cairo +++ /dev/null @@ -1 +0,0 @@ -mod vec2; diff --git a/crates/dojo-physics/src/vec2.cairo b/crates/dojo-physics/src/vec2.cairo deleted file mode 100644 index edf4501a10..0000000000 --- a/crates/dojo-physics/src/vec2.cairo +++ /dev/null @@ -1,261 +0,0 @@ -use cubit::types::fixed::{Fixed, FixedTrait, ONE_u128}; - -struct Vec2 { - x: T, - y: T -} - -impl Vec2Copy> of Copy>; -impl Vec2Drop> of Drop>; - -trait Vec2Trait { - // Constructors - fn new(x: T, y: T) -> Vec2; - fn splat(self: T) -> Vec2; - // Masks - fn select(mask: Vec2, if_true: Vec2, if_false: Vec2) -> Vec2; - // Math - fn dot, impl TAdd: Add>(self: Vec2, rhs: Vec2) -> T; - fn dot_into_vec, impl TAdd: Add>(self: Vec2, rhs: Vec2) -> Vec2; - // Swizzles - fn xy(self: Vec2) -> Vec2; - fn xx(self: Vec2) -> Vec2; - fn yx(self: Vec2) -> Vec2; - fn yy(self: Vec2) -> Vec2; -} - -impl Vec2Impl, impl TDrop: Drop> of Vec2Trait { - // Constructors - - /// Creates a new vector. - #[inline(always)] - fn new(x: T, y: T) -> Vec2 { - Vec2 { x: x, y: y } - } - /// Creates a vector with all elements set to `v`. - #[inline(always)] - fn splat(self: T) -> Vec2 { - Vec2 { x: self, y: self } - } - - // Masks - - /// Creates a vector from the elements in `if_true` and `if_false`, - /// selecting which to use for each element of `self`. - /// - /// A true element in the mask uses the corresponding element from - /// `if_true`, and false uses the element from `if_false`. - #[inline(always)] - fn select(mask: Vec2, if_true: Vec2, if_false: Vec2) -> Vec2 { - Vec2 { - x: if mask.x { - if_true.x - } else { - if_false.x - }, - y: if mask.y { - if_true.y - } else { - if_false.y - }, - } - } - - // Math - - /// Computes the dot product of `self` and `rhs` . - // #[inline(always)] is not allowed for functions with impl generic parameters. - fn dot, impl TAdd: Add>(self: Vec2, rhs: Vec2) -> T { - (self.x * rhs.x) + (self.y * rhs.y) - } - /// Returns a vector where every component is the dot product - /// of `self` and `rhs`. - fn dot_into_vec, impl TAdd: Add>(self: Vec2, rhs: Vec2) -> Vec2 { - Vec2Trait::splat(Vec2Trait::dot(self, rhs)) - } - - // Swizzles - /// Vec2 -> Vec2 - #[inline(always)] - fn xx(self: Vec2) -> Vec2 { - Vec2 { x: self.x, y: self.x, } - } - - #[inline(always)] - fn xy(self: Vec2) -> Vec2 { - Vec2 { x: self.x, y: self.y, } - } - - #[inline(always)] - fn yx(self: Vec2) -> Vec2 { - Vec2 { x: self.y, y: self.x, } - } - - #[inline(always)] - fn yy(self: Vec2) -> Vec2 { - Vec2 { x: self.y, y: self.y, } - } -} - -#[test] -#[available_gas(2000000)] -fn test_new() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - - // with Fixed type - let vec2 = Vec2Trait::new(var1_pos, var2_neg); - assert(vec2.x.mag == ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == false, 'invalid x.sign'); - assert(vec2.y.mag == 2 * ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == true, 'invalid y.sign'); - - // with bool - let bvec2tf = Vec2Trait::new(true, false); - assert(bvec2tf.x == true, 'invalid new x'); - assert(bvec2tf.y == false, 'invalid new y'); -} - -#[test] -#[available_gas(2000000)] -fn test_splat() { - let var = FixedTrait::new(ONE_u128, false); - - // with Fixed type - let vec2 = Vec2Trait::splat(var); - assert(vec2.x.mag == ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == false, 'invalid x.sign'); - assert(vec2.y.mag == ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == false, 'invalid y.sign'); - - // with bool - let bvec2tt = Vec2Trait::splat(true); - assert(bvec2tt.x == true, 'invalid x'); - assert(bvec2tt.y == true, 'invalid y'); -} - -#[test] -#[available_gas(2000000)] -fn test_select() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let var3_neg = FixedTrait::new(3 * ONE_u128, true); - let var4_pos = FixedTrait::new(4 * ONE_u128, false); - - let vec2a = Vec2Trait::new(var1_pos, var2_neg); - let vec2b = Vec2Trait::new(var3_neg, var4_pos); - - let mask = Vec2Trait::new(true, false); - let vec2 = Vec2Trait::select(mask, vec2a, vec2b); - assert(vec2.x.mag == ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == false, 'invalid x.sign'); - assert(vec2.y.mag == 4 * ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == false, 'invalid y.sign'); - - let mask = Vec2Trait::new(false, true); - let vec2 = Vec2Trait::select(mask, vec2a, vec2b); - assert(vec2.x.mag == 3 * ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == true, 'invalid x.sign'); - assert(vec2.y.mag == 2 * ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == true, 'invalid y.sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_dot() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let var3_neg = FixedTrait::new(3 * ONE_u128, true); - let var4_pos = FixedTrait::new(4 * ONE_u128, false); - - let vec2a = Vec2Trait::new(var1_pos, var2_neg); - let vec2b = Vec2Trait::new(var3_neg, var4_pos); - - let a_dot_b = vec2a.dot(vec2b); - assert(a_dot_b.mag == 11 * ONE_u128, 'invalid mag'); - assert(a_dot_b.sign == true, 'invalid sign'); - - let a_dot_b = Vec2Trait::dot(vec2a, vec2b); // alt syntax - assert(a_dot_b.mag == 11 * ONE_u128, 'invalid mag'); - assert(a_dot_b.sign == true, 'invalid sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_dot_into_vec() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let var3_neg = FixedTrait::new(3 * ONE_u128, true); - let var4_pos = FixedTrait::new(4 * ONE_u128, false); - - let vec2a = Vec2Trait::new(var1_pos, var2_neg); - let vec2b = Vec2Trait::new(var3_neg, var4_pos); - - let vec2 = vec2a.dot_into_vec(vec2b); - assert(vec2.x.mag == 11 * ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == true, 'invalid x.sign'); - assert(vec2.y.mag == 11 * ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == true, 'invalid y.sign'); - - let vec2 = Vec2Trait::dot_into_vec(vec2a, vec2b); // alt syntax - assert(vec2.x.mag == 11 * ONE_u128, 'invalid x.mag'); - assert(vec2.x.sign == true, 'invalid x.sign'); - assert(vec2.y.mag == 11 * ONE_u128, 'invalid y.mag'); - assert(vec2.y.sign == true, 'invalid y.sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_xx() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let vec2 = Vec2Trait::new(var1_pos, var2_neg); - - let vec2xx = vec2.xx(); - assert(vec2xx.x.mag == ONE_u128, 'invalid x.mag'); - assert(vec2xx.x.sign == false, 'invalid x.sign'); - assert(vec2xx.y.mag == ONE_u128, 'invalid y.mag'); - assert(vec2xx.y.sign == false, 'invalid y.sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_xy() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let vec2 = Vec2Trait::new(var1_pos, var2_neg); - - let vec2xy = vec2.xy(); - assert(vec2xy.x.mag == ONE_u128, 'invalid x.mag'); - assert(vec2xy.x.sign == false, 'invalid x.sign'); - assert(vec2xy.y.mag == 2 * ONE_u128, 'invalid xy.mag'); - assert(vec2xy.y.sign == true, 'invalid y.sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_yx() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let vec2 = Vec2Trait::new(var1_pos, var2_neg); - - let vec2yx = vec2.yx(); - assert(vec2yx.x.mag == 2 * ONE_u128, 'invalid x.mag'); - assert(vec2yx.x.sign == true, 'invalid x.sign'); - assert(vec2yx.y.mag == ONE_u128, 'invalid y.mag'); - assert(vec2yx.y.sign == false, 'invalid y.sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_yy() { - let var1_pos = FixedTrait::new(ONE_u128, false); - let var2_neg = FixedTrait::new(2 * ONE_u128, true); - let vec2 = Vec2Trait::new(var1_pos, var2_neg); - - let vec2yy = vec2.yy(); - assert(vec2yy.x.mag == 2 * ONE_u128, 'invalid x.mag'); - assert(vec2yy.x.sign == true, 'invalid x.sign'); - assert(vec2yy.y.mag == 2 * ONE_u128, 'invalid y.mag'); - assert(vec2yy.y.sign == true, 'invalid y.sign'); -} diff --git a/crates/dojo-primitives/Scarb.lock b/crates/dojo-primitives/Scarb.lock deleted file mode 100644 index 193cddd288..0000000000 --- a/crates/dojo-primitives/Scarb.lock +++ /dev/null @@ -1,21 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "dojo" -version = "0.3.11" -dependencies = [ - "dojo_plugin", -] - -[[package]] -name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" - -[[package]] -name = "dojo_primitives" -version = "0.3.11" -dependencies = [ - "dojo", -] diff --git a/crates/dojo-primitives/Scarb.toml b/crates/dojo-primitives/Scarb.toml deleted file mode 100644 index b25e0045fb..0000000000 --- a/crates/dojo-primitives/Scarb.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -cairo-version = "2.3.1" -description = "Implementations of common primitives for the Dojo games" -name = "dojo_primitives" -version = "0.3.11" - -[lib] - -[dependencies] -dojo = { path = "../dojo-core" } - -[[target.dojo]] diff --git a/crates/dojo-primitives/src/commit_reveal.cairo b/crates/dojo-primitives/src/commit_reveal.cairo deleted file mode 100644 index eda95a130c..0000000000 --- a/crates/dojo-primitives/src/commit_reveal.cairo +++ /dev/null @@ -1,31 +0,0 @@ -use serde::Serde; -use poseidon::poseidon_hash_span; - -#[derive(Copy, Drop, Default, Serde, Introspect)] -struct Commitment { - hash: felt252 -} - -trait CommitmentTrait { - fn new() -> Commitment; - fn commit(ref self: Commitment, hash: felt252); - fn reveal, impl TDrop: Drop>(self: @Commitment, reveal: T) -> bool; -} - -impl CommitmentImpl of CommitmentTrait { - fn new() -> Commitment { - Commitment { hash: 0 } - } - - fn commit(ref self: Commitment, hash: felt252) { - assert(hash.is_non_zero(), 'can not commit zero'); - self.hash = hash; - } - - fn reveal, impl TDrop: Drop>(self: @Commitment, reveal: T) -> bool { - let mut serialized = array![]; - reveal.serialize(ref serialized); - let hash = poseidon_hash_span(serialized.span()); - return hash == *self.hash; - } -} diff --git a/crates/dojo-primitives/src/commit_reveal_test.cairo b/crates/dojo-primitives/src/commit_reveal_test.cairo deleted file mode 100644 index 44a6044f74..0000000000 --- a/crates/dojo-primitives/src/commit_reveal_test.cairo +++ /dev/null @@ -1,15 +0,0 @@ -use serde::Serde; -use poseidon::poseidon_hash_span; -use dojo_primitives::commit_reveal::{Commitment, CommitmentTrait}; - -#[test] -#[available_gas(1000000)] -fn test_commit_reveal() { - let mut commitment = CommitmentTrait::new(); - - let value = array!['ohayo'].span(); - let hash = poseidon_hash_span(value); - commitment.commit(hash); - let valid = commitment.reveal('ohayo'); - assert(valid, 'invalid reveal for commitment') -} diff --git a/crates/dojo-primitives/src/lib.cairo b/crates/dojo-primitives/src/lib.cairo deleted file mode 100644 index 67120026d5..0000000000 --- a/crates/dojo-primitives/src/lib.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod commit_reveal; - -#[cfg(test)] -mod commit_reveal_test; diff --git a/scripts/cairo_test.sh b/scripts/cairo_test.sh index 70b241064a..b4f57ad916 100755 --- a/scripts/cairo_test.sh +++ b/scripts/cairo_test.sh @@ -2,8 +2,4 @@ set -euxo pipefail cargo run -r --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test $@ -# cargo run --bin sozo -- test crates/dojo-physics -cargo run -r --bin sozo -- --manifest-path crates/dojo-erc/Scarb.toml test $@ -cargo run -r --bin sozo -- --manifest-path crates/dojo-defi/Scarb.toml test $@ -cargo run -r --bin sozo -- --manifest-path crates/dojo-primitives/Scarb.toml test $@ cargo run -r --bin sozo -- --manifest-path examples/spawn-and-move/Scarb.toml test $@ From 0fa1b2ab2585fa18723872376896666a9a1552b1 Mon Sep 17 00:00:00 2001 From: Shramee Srivastav Date: Tue, 21 Nov 2023 22:34:06 +0800 Subject: [PATCH 022/192] core: delete entity models (#1191) * core: delete entity models * test: delete entity * chore: pass layout to delete * chore: fix manifest test * Fix records contract address --------- Co-authored-by: Tarrence van As --- crates/dojo-core/src/world.cairo | 29 ++++-- crates/dojo-core/src/world_test.cairo | 94 ++++++++++++------- .../dojo-lang/src/manifest_test_data/manifest | 6 +- crates/torii/graphql/src/tests/mod.rs | 2 +- 4 files changed, 86 insertions(+), 45 deletions(-) diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index 100a9a92d7..d38df35d3f 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -35,7 +35,7 @@ trait IWorld { fn set_executor(ref self: T, contract_address: ContractAddress); fn executor(self: @T) -> ContractAddress; fn base(self: @T) -> ClassHash; - fn delete_entity(ref self: T, model: felt252, keys: Span); + fn delete_entity(ref self: T, model: felt252, keys: Span, layout: Span); fn is_owner(self: @T, address: ContractAddress, resource: felt252) -> bool; fn grant_owner(ref self: T, address: ContractAddress, resource: felt252); fn revoke_owner(ref self: T, address: ContractAddress, resource: felt252); @@ -247,9 +247,7 @@ mod world { self.metadata_uri.write(i, *item); i += 1; }, - Option::None(_) => { - break; - } + Option::None(_) => { break; } }; }; } @@ -491,12 +489,27 @@ mod world { /// /// * `model` - The name of the model to be deleted. /// * `query` - The query to be used to find the entity. - fn delete_entity(ref self: ContractState, model: felt252, keys: Span) { - let system = get_caller_address(); - assert(system.is_non_zero(), 'must be called thru system'); - assert_can_write(@self, model, system); + fn delete_entity( + ref self: ContractState, model: felt252, keys: Span, layout: Span + ) { + assert_can_write(@self, model, get_caller_address()); + + let model_class_hash = self.models.read(model); + + let mut empty_values = ArrayTrait::new(); + let mut i = 0; + + loop { + if (i == layout.len()) { + break; + } + empty_values.append(0); + i += 1; + }; let key = poseidon::poseidon_hash_span(keys); + database::set(model, key, 0, empty_values.span(), layout); + // this deletes the index database::del(model, key); EventEmitter::emit(ref self, StoreDelRecord { table: model, keys }); diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index 36d260763b..8612168548 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -32,12 +32,13 @@ struct Fizz { #[starknet::interface] trait Ibar { fn set_foo(self: @TContractState, a: felt252, b: u128); + fn delete_foo(self: @TContractState); fn set_char(self: @TContractState, a: felt252, b: u32); } #[starknet::contract] mod bar { - use super::{Foo, IWorldDispatcher, IWorldDispatcherTrait}; + use super::{Foo, IWorldDispatcher, IWorldDispatcherTrait, SchemaIntrospection}; use super::benchmarks::{Character, Abilities, Stats, Weapon, Sword}; use traits::Into; use starknet::{get_caller_address, ContractAddress}; @@ -57,6 +58,15 @@ mod bar { set!(self.world.read(), Foo { caller: get_caller_address(), a, b }); } + fn delete_foo(self: @ContractState) { + let mut layout = array![]; + SchemaIntrospection::::layout(ref layout); + self + .world + .read() + .delete_entity('Foo', array![get_caller_address().into()].span(), layout.span()); + } + fn set_char(self: @ContractState, a: felt252, b: u32) { set!( self.world.read(), @@ -82,16 +92,12 @@ mod bar { finished: true, romances: 0x1234, }, - weapon: Weapon::DualWield(( - Sword { - swordsmith: get_caller_address(), - damage: 0x12345678, - }, - Sword { - swordsmith: get_caller_address(), - damage: 0x12345678, - } - )), + weapon: Weapon::DualWield( + ( + Sword { swordsmith: get_caller_address(), damage: 0x12345678, }, + Sword { swordsmith: get_caller_address(), damage: 0x12345678, } + ) + ), gold: b, } ); @@ -101,6 +107,19 @@ mod bar { // Tests +fn deploy_world_and_bar() -> (IWorldDispatcher, IbarDispatcher) { + // Spawn empty world + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + // System contract + let bar_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) + }; + + (world, bar_contract) +} + #[test] #[available_gas(2000000)] fn test_model() { @@ -111,14 +130,7 @@ fn test_model() { #[test] #[available_gas(6000000)] fn test_system() { - // Spawn empty world - let world = deploy_world(); - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - - // System contract - let bar_contract = IbarDispatcher { - contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) - }; + let (world, bar_contract) = deploy_world_and_bar(); bar_contract.set_foo(1337, 1337); @@ -127,6 +139,24 @@ fn test_system() { assert(stored.b == 1337, 'data not stored'); } +#[test] +#[available_gas(6000000)] +fn test_delete() { + let (world, bar_contract) = deploy_world_and_bar(); + + // set model + bar_contract.set_foo(1337, 1337); + let stored: Foo = get!(world, get_caller_address(), Foo); + assert(stored.a == 1337, 'data not stored'); + assert(stored.b == 1337, 'data not stored'); + + // delete model + bar_contract.delete_foo(); + let deleted: Foo = get!(world, get_caller_address(), Foo); + assert(deleted.a == 0, 'data not deleted'); + assert(deleted.b == 0, 'data not deleted'); +} + #[test] #[available_gas(6000000)] fn test_model_class_hash_getter() { @@ -153,13 +183,7 @@ fn test_emit() { #[test] #[available_gas(9000000)] fn test_set_entity_admin() { - // Spawn empty world - let world = deploy_world(); - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - - let bar_contract = IbarDispatcher { - contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) - }; + let (world, bar_contract) = deploy_world_and_bar(); let alice = starknet::contract_address_const::<0x1337>(); starknet::testing::set_contract_address(alice); @@ -267,14 +291,14 @@ fn test_entities() { let ids = world.entity_ids('Foo'); assert(keys.len() == ids.len(), 'result differs in entity_ids'); assert(keys.len() == 0, 'found value for unindexed'); - // query_keys.append(0x1337); - // let (keys, values) = world.entities('Foo', 42, query_keys.span(), 2, layout); - // assert(keys.len() == 1, 'No keys found!'); - - // let mut query_keys = ArrayTrait::new(); - // query_keys.append(0x1338); - // let (keys, values) = world.entities('Foo', 42, query_keys.span(), 2, layout); - // assert(keys.len() == 0, 'Keys found!'); +// query_keys.append(0x1337); +// let (keys, values) = world.entities('Foo', 42, query_keys.span(), 2, layout); +// assert(keys.len() == 1, 'No keys found!'); + +// let mut query_keys = ArrayTrait::new(); +// query_keys.append(0x1338); +// let (keys, values) = world.entities('Foo', 42, query_keys.span(), 2, layout); +// assert(keys.len() == 0, 'Keys found!'); } #[test] @@ -486,4 +510,4 @@ fn bench_execute_complex() { end(gas, 'char get macro'); assert(data.heigth == 1337, 'data not stored'); -} \ No newline at end of file +} diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 0d9f8519d6..1393fe2cdf 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -8,7 +8,7 @@ test_manifest_file "world": { "name": "world", "address": null, - "class_hash": "0x6441e7fc96d795ec94fca66634e6d87edf84362e564ef80eb13f459914b8b", + "class_hash": "0x481a9c7b128847a520bb234d93ef8a83b462de03fce7089eb4ed9135d2f31d9", "abi": [ { "type": "impl", @@ -357,6 +357,10 @@ test_manifest_file { "name": "keys", "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" } ], "outputs": [], diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 83c7a3839c..cabad47c4f 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -289,7 +289,7 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); // Execute `create` and insert 10 records into storage - let records_contract = "0x2753d30656b393ecea156189bf0acf5e1063f3ac978fb5c3cebe7a4570bbc78"; + let records_contract = "0x58eaab9779694efb92b43ccad6dc9b40cd8fc69e3efa97137f1c83af69ca8a1"; let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], From d955c1a2881571e01bbe0665a525a8a24ada0f85 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:13:54 +0100 Subject: [PATCH 023/192] Upgradeable component (#1147) * core: upgradeable component wip * #[dojo::contract] merge Event, update upgradeable * update base_test.cairo & dojo-lang plugin_test_data * add Upgraded event in upgradeable component * move erc20, fix build issue, delete old tests * update erc20 to use #[dojo::contract] * revert changes on erc20 * revert changes on erc20 * fully qualify component * handle when Event is not defined in contract * follow clippy recomandation * torii: update records_contract address * trigger CI * trigger CI * rebase main * update manifest_test_data * use right records_contract address --- crates/dojo-core/src/base.cairo | 39 ++- crates/dojo-core/src/base_test.cairo | 33 ++- crates/dojo-core/src/components.cairo | 1 + .../src/components/upgradeable.cairo | 56 ++++ crates/dojo-core/src/lib.cairo | 5 +- crates/dojo-core/src/upgradable.cairo | 19 -- crates/dojo-core/src/world.cairo | 14 +- crates/dojo-lang/src/contract.rs | 106 ++++--- .../dojo-lang/src/manifest_test_data/manifest | 156 ++++++++-- crates/dojo-lang/src/plugin_test_data/system | 280 +++++++++++++++--- crates/torii/graphql/src/tests/mod.rs | 2 +- 11 files changed, 531 insertions(+), 180 deletions(-) create mode 100644 crates/dojo-core/src/components.cairo create mode 100644 crates/dojo-core/src/components/upgradeable.cairo delete mode 100644 crates/dojo-core/src/upgradable.cairo diff --git a/crates/dojo-core/src/base.cairo b/crates/dojo-core/src/base.cairo index 9b6a58f621..d5265b31bd 100644 --- a/crates/dojo-core/src/base.cairo +++ b/crates/dojo-core/src/base.cairo @@ -1,20 +1,26 @@ use dojo::world::IWorldDispatcher; -#[starknet::interface] -trait IBase { - fn world(self: @T) -> IWorldDispatcher; -} - #[starknet::contract] mod base { use starknet::{ClassHash, get_caller_address}; - - use dojo::upgradable::{IUpgradeable, UpgradeableTrait}; use dojo::world::IWorldDispatcher; + use dojo::world::IWorldProvider; + + use dojo::components::upgradeable::upgradeable as upgradeable_component; + + component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent); + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: upgradeable_component::Event + } #[storage] struct Storage { world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: upgradeable_component::Storage, } #[constructor] @@ -23,19 +29,12 @@ mod base { } #[external(v0)] - fn world(self: @ContractState) -> IWorldDispatcher { - self.world_dispatcher.read() - } - - #[external(v0)] - impl Upgradeable of IUpgradeable { - /// Upgrade contract implementation to new_class_hash - /// - /// # Arguments - /// - /// * `new_class_hash` - The new implementation class hahs. - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - UpgradeableTrait::upgrade(new_class_hash); + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() } } + + #[abi(embed_v0)] + impl UpgradableImpl = upgradeable_component::UpgradableImpl; } diff --git a/crates/dojo-core/src/base_test.cairo b/crates/dojo-core/src/base_test.cairo index 691ad3bac6..2a86667870 100644 --- a/crates/dojo-core/src/base_test.cairo +++ b/crates/dojo-core/src/base_test.cairo @@ -3,8 +3,10 @@ use starknet::ClassHash; use traits::TryInto; use dojo::base::base; -use dojo::upgradable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; -use dojo::test_utils::deploy_contract; +use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use dojo::test_utils::{spawn_test_world}; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + #[starknet::contract] mod contract_upgrade { @@ -29,15 +31,34 @@ mod contract_upgrade { use contract_upgrade::{IQuantumLeapDispatcher, IQuantumLeapDispatcherTrait}; +// Utils +fn deploy_world() -> IWorldDispatcher { + spawn_test_world(array![]) +} + #[test] #[available_gas(6000000)] -fn test_upgrade() { - let base_address = deploy_contract(base::TEST_CLASS_HASH, array![].span()); - let upgradable_dispatcher = IUpgradeableDispatcher { contract_address: base_address }; +fn test_upgrade_from_world() { + let world = deploy_world(); + let base_address = world.deploy_contract('salt', base::TEST_CLASS_HASH.try_into().unwrap()); let new_class_hash: ClassHash = contract_upgrade::TEST_CLASS_HASH.try_into().unwrap(); - upgradable_dispatcher.upgrade(new_class_hash); + + world.upgrade_contract(base_address, new_class_hash); let quantum_dispatcher = IQuantumLeapDispatcher { contract_address: base_address }; assert(quantum_dispatcher.plz_more_tps() == 'daddy', 'quantum leap failed'); } + +#[test] +#[available_gas(6000000)] +#[should_panic(expected: ('must be called by world', 'ENTRYPOINT_FAILED'))] +fn test_upgrade_direct() { + let world = deploy_world(); + + let base_address = world.deploy_contract('salt', base::TEST_CLASS_HASH.try_into().unwrap()); + let new_class_hash: ClassHash = contract_upgrade::TEST_CLASS_HASH.try_into().unwrap(); + + let upgradeable_dispatcher = IUpgradeableDispatcher { contract_address: base_address }; + upgradeable_dispatcher.upgrade(new_class_hash); +} diff --git a/crates/dojo-core/src/components.cairo b/crates/dojo-core/src/components.cairo new file mode 100644 index 0000000000..bd830eac21 --- /dev/null +++ b/crates/dojo-core/src/components.cairo @@ -0,0 +1 @@ +mod upgradeable; \ No newline at end of file diff --git a/crates/dojo-core/src/components/upgradeable.cairo b/crates/dojo-core/src/components/upgradeable.cairo new file mode 100644 index 0000000000..c5a030f12f --- /dev/null +++ b/crates/dojo-core/src/components/upgradeable.cairo @@ -0,0 +1,56 @@ +use starknet::ClassHash; + +#[starknet::interface] +trait IUpgradeable { + fn upgrade(ref self: T, new_class_hash: ClassHash); +} + +#[starknet::component] +mod upgradeable { + use starknet::ClassHash; + use starknet::ContractAddress; + use starknet::get_caller_address; + use starknet::syscalls::replace_class_syscall; + use dojo::world::{IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher}; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Upgraded: Upgraded, + } + + #[derive(Drop, starknet::Event)] + struct Upgraded { + class_hash: ClassHash + } + + mod Errors { + const INVALID_CLASS: felt252 = 'class_hash cannot be zero'; + const INVALID_CALLER: felt252 = 'must be called by world'; + const INVALID_WORLD_ADDRESS: felt252 = 'invalid world address'; + } + + #[embeddable_as(UpgradableImpl)] + impl Upgradable< + TContractState, +HasComponent, +IWorldProvider + > of super::IUpgradeable> { + fn upgrade(ref self: ComponentState, new_class_hash: ClassHash) { + assert( + self.get_contract().world().contract_address.is_non_zero(), + Errors::INVALID_WORLD_ADDRESS + ); + assert( + get_caller_address() == self.get_contract().world().contract_address, + Errors::INVALID_CALLER + ); + assert(new_class_hash.is_non_zero(), Errors::INVALID_CLASS); + + replace_class_syscall(new_class_hash).unwrap(); + + self.emit(Upgraded { class_hash: new_class_hash }); + } + } +} diff --git a/crates/dojo-core/src/lib.cairo b/crates/dojo-core/src/lib.cairo index 6e3301c6ee..8a8ba0918d 100644 --- a/crates/dojo-core/src/lib.cairo +++ b/crates/dojo-core/src/lib.cairo @@ -14,10 +14,11 @@ mod packing_test; mod world; #[cfg(test)] mod world_test; -mod upgradable; #[cfg(test)] mod test_utils; #[cfg(test)] -mod benchmarks; \ No newline at end of file +mod benchmarks; + +mod components; \ No newline at end of file diff --git a/crates/dojo-core/src/upgradable.cairo b/crates/dojo-core/src/upgradable.cairo deleted file mode 100644 index 1a446770d8..0000000000 --- a/crates/dojo-core/src/upgradable.cairo +++ /dev/null @@ -1,19 +0,0 @@ -use starknet::{ClassHash, SyscallResult, SyscallResultTrait}; -use zeroable::Zeroable; -use result::ResultTrait; - -#[starknet::interface] -trait IUpgradeable { - fn upgrade(ref self: T, new_class_hash: ClassHash); -} - -trait UpgradeableTrait { - fn upgrade(new_class_hash: ClassHash); -} - -impl UpgradeableTraitImpl of UpgradeableTrait { - fn upgrade(new_class_hash: ClassHash) { - assert(new_class_hash.is_non_zero(), 'class_hash cannot be zero'); - starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); - } -} diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index d38df35d3f..53094323c8 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -45,6 +45,12 @@ trait IWorld { fn revoke_writer(ref self: T, model: felt252, system: ContractAddress); } +#[starknet::interface] +trait IWorldProvider { + fn world(self: @T) -> IWorldDispatcher; +} + + #[starknet::contract] mod world { use core::traits::TryInto; @@ -66,9 +72,9 @@ mod world { use dojo::database; use dojo::database::index::WhereCondition; use dojo::executor::{IExecutorDispatcher, IExecutorDispatcherTrait}; - use dojo::upgradable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use dojo::world::{IWorldDispatcher, IWorld}; - + + use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; const NAME_ENTRYPOINT: felt252 = 0x0361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60; @@ -404,8 +410,8 @@ mod world { self.contract_base.read(), salt, array![].span(), false ) .unwrap_syscall(); - let upgradable_dispatcher = IUpgradeableDispatcher { contract_address }; - upgradable_dispatcher.upgrade(class_hash); + let upgradeable_dispatcher = IUpgradeableDispatcher { contract_address }; + upgradeable_dispatcher.upgrade(class_hash); self.owners.write((contract_address.into(), get_caller_address()), true); diff --git a/crates/dojo-lang/src/contract.rs b/crates/dojo-lang/src/contract.rs index 8436eeb735..9880b4efc6 100644 --- a/crates/dojo-lang/src/contract.rs +++ b/crates/dojo-lang/src/contract.rs @@ -6,7 +6,7 @@ use cairo_lang_defs::plugin::{ }; // use cairo_lang_syntax::node::ast::{MaybeModuleBody, Param}; use cairo_lang_syntax::node::ast::MaybeModuleBody; -use cairo_lang_syntax::node::ast::OptionReturnTypeClause::ReturnTypeClause; +// use cairo_lang_syntax::node::ast::OptionReturnTypeClause::ReturnTypeClause; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; @@ -23,16 +23,18 @@ impl DojoContract { pub fn from_module(db: &dyn SyntaxGroup, module_ast: ast::ItemModule) -> PluginResult { let name = module_ast.name(db).text(db); let mut system = DojoContract { diagnostics: vec![], dependencies: HashMap::new() }; + let mut has_event = false; if let MaybeModuleBody::Some(body) = module_ast.body(db) { - let body_nodes = body + let mut body_nodes: Vec<_> = body .items(db) .elements(db) .iter() .flat_map(|el| { - if let ast::Item::FreeFunction(fn_ast) = el { - if fn_ast.declaration(db).name(db).text(db).to_string() == "execute" { - return system.handle_execute(db, fn_ast.clone()); + if let ast::Item::Enum(enum_ast) = el { + if enum_ast.name(db).text(db).to_string() == "Event" { + has_event = true; + return system.merge_event(db, enum_ast.clone()); } } @@ -40,6 +42,10 @@ impl DojoContract { }) .collect(); + if !has_event { + body_nodes.append(&mut system.create_event()) + } + let mut builder = PatchBuilder::new(db); builder.add_modified(RewriteNode::interpolate_patched( " @@ -48,10 +54,17 @@ impl DojoContract { use dojo::world; use dojo::world::IWorldDispatcher; use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + + component!(path: dojo::components::upgradeable::upgradeable, storage: \ + upgradeable, event: UpgradeableEvent); + #[storage] struct Storage { world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, } #[external(v0)] @@ -60,17 +73,16 @@ impl DojoContract { } #[external(v0)] - impl Upgradeable of dojo::upgradable::IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { - let caller = starknet::get_caller_address(); - assert( - self.world_dispatcher.read().contract_address == caller, 'only \ - World can upgrade' - ); - dojo::upgradable::UpgradeableTrait::upgrade(new_class_hash); + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() } } + #[abi(embed_v0)] + impl UpgradableImpl = \ + dojo::components::upgradeable::upgradeable::UpgradableImpl; + $body$ } ", @@ -101,60 +113,44 @@ impl DojoContract { PluginResult::default() } - pub fn handle_execute( + pub fn merge_event( &mut self, db: &dyn SyntaxGroup, - function_ast: ast::FunctionWithBody, + enum_ast: ast::ItemEnum, ) -> Vec { let mut rewrite_nodes = vec![]; - let signature = function_ast.declaration(db).signature(db); - - let parameters = signature.parameters(db); - let elements = parameters.elements(db); - - // let mut context = "_ctx: dojo::world::Context".to_string(); - // if let Some(first) = elements.first() { - // // If context is first, move it to last. - // if is_context(db, first) { - // let ctx = elements.remove(0); - // context = ctx.as_syntax_node().get_text(db); - // } - // } else if let Some(param) = elements.iter().find(|p| is_context(db, p)) { - // // Context not the first element, but exists. - // self.diagnostics.push(PluginDiagnostic { - // message: "Context must be first parameter when provided".into(), - // stable_ptr: param.stable_ptr().untyped(), - // }); - // } - - let params = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); - // params.push(context); - let params = params.join(", "); - - let ret_clause = if let ReturnTypeClause(clause) = signature.ret_ty(db) { - RewriteNode::new_trimmed(clause.as_syntax_node()) - } else { - RewriteNode::Text("".to_string()) - }; + let elements = enum_ast.variants(db).elements(db); + + let variants = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); + let variants = variants.join(", "); rewrite_nodes.push(RewriteNode::interpolate_patched( " - #[external(v0)] - fn execute(self: @ContractState, $params$) $ret_clause$ $body$ + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + $variants$ + } ", - &UnorderedHashMap::from([ - ("params".to_string(), RewriteNode::Text(params)), - ( - "body".to_string(), - RewriteNode::new_trimmed(function_ast.body(db).as_syntax_node()), - ), - ("ret_clause".to_string(), ret_clause), - ]), + &UnorderedHashMap::from([("variants".to_string(), RewriteNode::Text(variants))]), )); - rewrite_nodes } + + pub fn create_event(&mut self) -> Vec { + vec![RewriteNode::Text( + " + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + } + " + .to_string(), + )] + } } // fn is_context(db: &dyn SyntaxGroup, param: &Param) -> bool { diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 1393fe2cdf..5a491c57f8 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -8,7 +8,7 @@ test_manifest_file "world": { "name": "world", "address": null, - "class_hash": "0x481a9c7b128847a520bb234d93ef8a83b462de03fce7089eb4ed9135d2f31d9", + "class_hash": "0xb3e374b8087dca92601afbb9881fed855ac0d568e3bf878a876fca5ffcb479", "abi": [ { "type": "impl", @@ -811,16 +811,48 @@ test_manifest_file }, "base": { "name": "base", - "class_hash": "0x5a2c567ed06c8059c8d1199684796a0a0ef614f9a2ab628700e804524816b5c", + "class_hash": "0x77638e9a645209ac1e32e143bfdbfe9caf723c4f7645fcf465c38967545ea2f", "abi": [ { "type": "impl", - "name": "Upgradeable", - "interface_name": "dojo::upgradable::IUpgradeable" + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" }, { "type": "interface", - "name": "dojo::upgradable::IUpgradeable", + "name": "dojo::components::upgradeable::IUpgradeable", "items": [ { "type": "function", @@ -842,31 +874,40 @@ test_manifest_file "inputs": [] }, { - "type": "struct", - "name": "dojo::world::IWorldDispatcher", + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", "members": [ { - "name": "contract_address", - "type": "core::starknet::contract_address::ContractAddress" + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" } ] }, { - "type": "function", - "name": "world", - "inputs": [], - "outputs": [ + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ { - "type": "dojo::world::IWorldDispatcher" + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" } - ], - "state_mutability": "view" + ] }, { "type": "event", "name": "dojo::base::base::Event", "kind": "enum", - "variants": [] + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + } + ] } ] }, @@ -874,28 +915,37 @@ test_manifest_file { "name": "actions", "address": null, - "class_hash": "0x308ad5fcd288ea5ded168bbc502261d75458c13fa1df552bd41ca3d86347dfe", + "class_hash": "0x1f740b30fc835ecf509a40e8dc8e4eb7ada046243833d2060f17ab961e4e154", "abi": [ { "type": "impl", - "name": "Upgradeable", - "interface_name": "dojo::upgradable::IUpgradeable" + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] }, { "type": "interface", - "name": "dojo::upgradable::IUpgradeable", + "name": "dojo::world::IWorldProvider", "items": [ { "type": "function", - "name": "upgrade", - "inputs": [ + "name": "world", + "inputs": [], + "outputs": [ { - "name": "new_class_hash", - "type": "core::starknet::class_hash::ClassHash" + "type": "dojo::world::IWorldDispatcher" } ], - "outputs": [], - "state_mutability": "external" + "state_mutability": "view" } ] }, @@ -955,6 +1005,29 @@ test_manifest_file } ] }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, { "type": "function", "name": "dojo_resource", @@ -1026,6 +1099,30 @@ test_manifest_file ], "state_mutability": "view" }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" + } + ] + }, { "type": "event", "name": "dojo_examples::actions::actions::Moved", @@ -1048,6 +1145,11 @@ test_manifest_file "name": "dojo_examples::actions::actions::Event", "kind": "enum", "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + }, { "name": "Moved", "type": "dojo_examples::actions::actions::Moved", diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index e588abd684..bb252973ac 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -31,6 +31,26 @@ mod ctxnamed { } } +#[dojo::contract] +mod withevent { + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + TestEvent: TestEvent, + } + + #[derive(Drop, starknet::Event)] + struct TestEvent { + address: ContractAddress, + } + + #[external(v0)] + fn test(value: felt252) -> value { + value + } +} + //! > generated_cairo_code #[starknet::contract] mod spawn { @@ -157,64 +177,154 @@ error: Unsupported attribute. ^*******************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:8:21 + --> test_src/lib.cairo[withevent]:2:17 + #[starknet::contract] + ^*******************^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[spawn]:9:21 + component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); + ^**********************************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[spawn]:12:21 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:13:21 + --> test_src/lib.cairo[spawn]:15:25 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[spawn]:19:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:18:21 + --> test_src/lib.cairo[spawn]:24:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:32:17 - #[external(v0)] - ^*************^ + --> test_src/lib.cairo[spawn]:31:21 + #[abi(embed_v0)] + ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:8:21 + --> test_src/lib.cairo[spawn]:41:13 + #[event] + ^******^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[proxy]:9:21 + component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); + ^**********************************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[proxy]:12:21 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:13:21 + --> test_src/lib.cairo[proxy]:15:25 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[proxy]:19:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:18:21 + --> test_src/lib.cairo[proxy]:24:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:30:17 - #[external(v0)] - ^*************^ + --> test_src/lib.cairo[proxy]:31:21 + #[abi(embed_v0)] + ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:8:21 + --> test_src/lib.cairo[proxy]:38:13 + #[event] + ^******^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[ctxnamed]:9:21 + component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); + ^**********************************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:12:21 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:13:21 + --> test_src/lib.cairo[ctxnamed]:15:25 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:19:21 + #[external(v0)] + ^*************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:24:21 + #[external(v0)] + ^*************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:31:21 + #[abi(embed_v0)] + ^**************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:41:13 + #[event] + ^******^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[withevent]:9:21 + component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); + ^**********************************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:12:21 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:15:25 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:19:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:18:21 + --> test_src/lib.cairo[withevent]:24:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:32:17 - #[external(v0)] - ^*************^ + --> test_src/lib.cairo[withevent]:31:21 + #[abi(embed_v0)] + ^**************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:35:13 + #[event] + ^******^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:47:5 + #[external(v0)] + ^*************^ //! > expanded_cairo_code #[starknet::contract] @@ -222,10 +332,14 @@ error: Unsupported attribute. use dojo::world; use dojo::world::IWorldDispatcher; use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + #[storage] struct Storage { world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, } #[external(v0)] @@ -234,23 +348,28 @@ error: Unsupported attribute. } #[external(v0)] - impl Upgradeable of dojo::upgradable::IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { - let caller = starknet::get_caller_address(); - assert( - self.world_dispatcher.read().contract_address == caller, 'only World can upgrade' - ); - dojo::upgradable::UpgradeableTrait::upgrade(new_class_hash); + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() } } + #[abi(embed_v0)] + impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; + use traits::Into; use dojo::world::Context; - #[external(v0)] - fn execute(self: @ContractState, ctx: Context, name: felt252) { + fn execute(ctx: Context, name: felt252) { return (); } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + } +impl EventDrop of Drop::; } @@ -259,10 +378,14 @@ error: Unsupported attribute. use dojo::world; use dojo::world::IWorldDispatcher; use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + #[storage] struct Storage { world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, } #[external(v0)] @@ -271,21 +394,25 @@ error: Unsupported attribute. } #[external(v0)] - impl Upgradeable of dojo::upgradable::IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { - let caller = starknet::get_caller_address(); - assert( - self.world_dispatcher.read().contract_address == caller, 'only World can upgrade' - ); - dojo::upgradable::UpgradeableTrait::upgrade(new_class_hash); + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() } } - - #[external(v0)] - fn execute(self: @ContractState, value: felt252) -> felt252 { + #[abi(embed_v0)] + impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; + + fn execute(value: felt252) -> felt252 { value } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + } +impl EventDrop of Drop::; } @@ -294,10 +421,14 @@ error: Unsupported attribute. use dojo::world; use dojo::world::IWorldDispatcher; use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + #[storage] struct Storage { world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, } #[external(v0)] @@ -306,22 +437,79 @@ error: Unsupported attribute. } #[external(v0)] - impl Upgradeable of dojo::upgradable::IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { - let caller = starknet::get_caller_address(); - assert( - self.world_dispatcher.read().contract_address == caller, 'only World can upgrade' - ); - dojo::upgradable::UpgradeableTrait::upgrade(new_class_hash); + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() } } + #[abi(embed_v0)] + impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; + use traits::Into; use dojo::world::Context; - #[external(v0)] - fn execute(self: @ContractState, ctx2: Context, name: felt252) { + fn execute(ctx2: Context, name: felt252) { return (); } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + } +impl EventDrop of Drop::; } + + #[starknet::contract] + mod withevent { + use dojo::world; + use dojo::world::IWorldDispatcher; + use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } + + #[external(v0)] + fn dojo_resource(self: @ContractState) -> felt252 { + 'withevent' + } + + #[external(v0)] + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() + } + } + + #[abi(embed_v0)] + impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; + + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + TestEvent: TestEvent + } + + #[derive(Drop, starknet::Event)] + struct TestEvent { + address: ContractAddress, + } + + #[external(v0)] + fn test(value: felt252) -> value { + value + } +impl EventDrop of Drop::; +impl TestEventDrop of Drop::; + + } diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index cabad47c4f..2576c5227c 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -289,7 +289,7 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); // Execute `create` and insert 10 records into storage - let records_contract = "0x58eaab9779694efb92b43ccad6dc9b40cd8fc69e3efa97137f1c83af69ca8a1"; + let records_contract = "0x27f701de7d71a2a6ee670bc1ff47a901fdc671cca26fe234ca1a42273aa7f7d"; let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], From db2811d03ba7271fc78b6409dccff15f50ae0192 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 21 Nov 2023 15:27:30 -0500 Subject: [PATCH 024/192] Prepare v0.3.12 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 6 ++-- crates/dojo-core/Scarb.toml | 4 +-- crates/dojo-lang/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 8 ++--- examples/spawn-and-move/Scarb.toml | 2 +- scripts/prepare_release.sh | 3 +- 9 files changed, 29 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30dc4f0f66..58a4fc18a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2265,7 +2265,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2312,7 +2312,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2334,7 +2334,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "starknet", @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "assert_fs", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.11" +version = "0.3.12" dependencies = [ "crypto-bigint", "hex", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "assert_fs", @@ -4816,7 +4816,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.11" +version = "0.3.12" dependencies = [ "assert_matches", "clap", @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "assert_matches", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "assert_matches", @@ -7236,7 +7236,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "assert_fs", @@ -8267,7 +8267,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.11" +version = "0.3.12" dependencies = [ "async-trait", "camino", @@ -8293,7 +8293,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "async-trait", @@ -8328,7 +8328,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "async-graphql", @@ -8366,7 +8366,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.11" +version = "0.3.12" dependencies = [ "bytes", "dojo-types", @@ -8403,7 +8403,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.11" +version = "0.3.12" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index b398a41b1f..957b0458b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.11" +version = "0.3.12" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index a6f55141e4..13286a04f2 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,12 +3,12 @@ version = 1 [[package]] name = "dojo" -version = "0.3.11" +version = "0.3.12" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#adab82da604669393bf5391439ed4ab1825923d1" +version = "0.3.12" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 076c4523b2..d1f9ec3818 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,8 +2,8 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.11" +version = "0.3.12" [dependencies] -dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } +dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.12" } starknet = "2.3.1" diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index d6108e0735..0f7e1b1532 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.11" +version = "0.3.12" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 167b5875ab..3acc1969d9 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.11" +version = "0.3.12" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 8c1d889a10..40830134bd 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,19 +3,19 @@ version = 1 [[package]] name = "dojo" -version = "0.3.11" +version = "0.3.12" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.11" +version = "0.3.12" dependencies = [ "dojo", ] [[package]] name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" +version = "0.3.12" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 2e9d836793..01191e1edb 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.11" +version = "0.3.12" [cairo] sierra-replace-ids = true diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 2659a79cca..def15d4949 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -9,7 +9,6 @@ find . -type f -name "*.toml" -exec sed -i "" "s/dojo_plugin = \"$prev_version\" scripts/clippy.sh -git checkout -b release/v$1 git commit -am "Prepare v$1" git tag -a "v$1" -m "Version $1" -git push origin release/v$1 --tags \ No newline at end of file +# git push origin --tags \ No newline at end of file From 9c9e1d686bf20cdedf62c631c4e286191d1506e0 Mon Sep 17 00:00:00 2001 From: Shramee Srivastav Date: Wed, 22 Nov 2023 23:24:30 +0800 Subject: [PATCH 025/192] chore: test on localhost (#1201) Don't listen on all IP addresses when testing --- crates/dojo-test-utils/src/sequencer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index c44c7ee4f7..1ed8704c05 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -36,7 +36,7 @@ impl TestSequencer { Arc::clone(&sequencer), ServerConfig { port: 0, - host: "0.0.0.0".into(), + host: "127.0.0.1".into(), max_connections: 100, apis: vec![ApiKind::Starknet, ApiKind::Katana], }, From b3f49977fe921564725c30b912126aea57859bbd Mon Sep 17 00:00:00 2001 From: Yun Date: Wed, 22 Nov 2023 07:33:41 -0800 Subject: [PATCH 026/192] Torii grpc entities query (#1196) * Refactor torii grpc * error freeeee! * Refactor torii grpc * rebase changes * clean up * Move model to individual query clause * Refactor subscription argument to keys * Add Torii grpc retrieve entities endpoint * Add cache to store schema ty and query * map rows to proto model * add grpc limit offset * refactor query logic * clippy & fmt * remove unused * fix tests * move limit offset to query clause --------- Co-authored-by: Tarrence van As --- Cargo.lock | 1 + crates/dojo-types/src/primitive.rs | 172 ++++++-------- crates/dojo-types/src/schema.rs | 10 + crates/torii/core/Cargo.toml | 1 + crates/torii/core/src/cache.rs | 56 +++++ crates/torii/core/src/error.rs | 14 ++ crates/torii/core/src/lib.rs | 1 + crates/torii/core/src/model.rs | 200 +++++++++++++++- .../torii/graphql/src/tests/entities_test.rs | 2 +- crates/torii/graphql/src/tests/models_test.rs | 2 +- .../src/tests/types-test/src/contracts.cairo | 5 +- .../src/tests/types-test/src/models.cairo | 7 + crates/torii/grpc/proto/types.proto | 36 ++- crates/torii/grpc/proto/world.proto | 14 +- crates/torii/grpc/src/server/mod.rs | 219 ++++++++++++++++-- crates/torii/grpc/src/types.rs | 8 +- 16 files changed, 627 insertions(+), 121 deletions(-) create mode 100644 crates/torii/core/src/cache.rs diff --git a/Cargo.lock b/Cargo.lock index 58a4fc18a9..e5157272aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8300,6 +8300,7 @@ dependencies = [ "base64 0.21.5", "camino", "chrono", + "crypto-bigint", "dojo-test-utils", "dojo-types", "dojo-world", diff --git a/crates/dojo-types/src/primitive.rs b/crates/dojo-types/src/primitive.rs index d5a159bad9..2e91d0fdbb 100644 --- a/crates/dojo-types/src/primitive.rs +++ b/crates/dojo-types/src/primitive.rs @@ -33,6 +33,8 @@ pub enum PrimitiveError { NotEnoughFieldElements, #[error("Unsupported CairoType for SQL formatting")] UnsupportedType, + #[error("Set value type mismatch")] + TypeMismatch, #[error(transparent)] ValueOutOfRange(#[from] ValueOutOfRangeError), } @@ -44,97 +46,60 @@ pub enum SqlType { Text, } -impl Primitive { - /// If the `Primitive` is a u8, returns the associated [`u8`]. Returns `None` otherwise. - pub fn as_u8(&self) -> Option { - match self { - Primitive::U8(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a u16, returns the associated [`u16`]. Returns `None` otherwise. - pub fn as_u16(&self) -> Option { - match self { - Primitive::U16(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a u32, returns the associated [`u32`]. Returns `None` otherwise. - pub fn as_u32(&self) -> Option { - match self { - Primitive::U32(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a u64, returns the associated [`u64`]. Returns `None` otherwise. - pub fn as_u64(&self) -> Option { - match self { - Primitive::U64(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a u128, returns the associated [`u128`]. Returns `None` otherwise. - pub fn as_u128(&self) -> Option { - match self { - Primitive::U128(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a u256, returns the associated [`U256`]. Returns `None` otherwise. - pub fn as_u256(&self) -> Option { - match self { - Primitive::U256(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a felt252, returns the associated [`FieldElement`]. Returns `None` - /// otherwise. - pub fn as_felt252(&self) -> Option { - match self { - Primitive::Felt252(value) => *value, - _ => None, - } - } - - /// If the `Primitive` is a ClassHash, returns the associated [`FieldElement`]. Returns `None` - /// otherwise. - pub fn as_class_hash(&self) -> Option { - match self { - Primitive::ClassHash(value) => *value, - _ => None, +/// Macro to generate setter methods for Primitive enum variants. +macro_rules! set_primitive { + ($method_name:ident, $variant:ident, $type:ty) => { + /// Sets the inner value of the `Primitive` enum if variant matches. + pub fn $method_name(&mut self, value: Option<$type>) -> Result<(), PrimitiveError> { + match self { + Primitive::$variant(_) => { + *self = Primitive::$variant(value); + Ok(()) + } + _ => Err(PrimitiveError::TypeMismatch), + } } - } + }; +} - /// If the `Primitive` is a ContractAddress, returns the associated [`FieldElement`]. Returns - /// `None` otherwise. - pub fn as_contract_address(&self) -> Option { - match self { - Primitive::ContractAddress(value) => *value, - _ => None, +/// Macro to generate getter methods for Primitive enum variants. +macro_rules! as_primitive { + ($method_name:ident, $variant:ident, $type:ty) => { + /// If the `Primitive` is variant type, returns the associated vartiant value. Returns + /// `None` otherwise. + pub fn $method_name(&self) -> Option<$type> { + match self { + Primitive::$variant(value) => *value, + _ => None, + } } - } + }; +} - /// If the `Primitive` is a usize, returns the associated [`u32`]. Returns `None` otherwise. - pub fn as_usize(&self) -> Option { - match self { - Primitive::USize(value) => *value, - _ => None, - } - } +impl Primitive { + as_primitive!(as_u8, U8, u8); + as_primitive!(as_u16, U16, u16); + as_primitive!(as_u32, U32, u32); + as_primitive!(as_u64, U64, u64); + as_primitive!(as_u128, U128, u128); + as_primitive!(as_u256, U256, U256); + as_primitive!(as_bool, Bool, bool); + as_primitive!(as_usize, USize, u32); + as_primitive!(as_felt252, Felt252, FieldElement); + as_primitive!(as_class_hash, ClassHash, FieldElement); + as_primitive!(as_contract_address, ContractAddress, FieldElement); - /// If the `Primitive` is a bool, returns the associated [`bool`]. Returns `None` otherwise. - pub fn as_bool(&self) -> Option { - match self { - Primitive::Bool(value) => *value, - _ => None, - } - } + set_primitive!(set_u8, U8, u8); + set_primitive!(set_u16, U16, u16); + set_primitive!(set_u32, U32, u32); + set_primitive!(set_u64, U64, u64); + set_primitive!(set_u128, U128, u128); + set_primitive!(set_u256, U256, U256); + set_primitive!(set_bool, Bool, bool); + set_primitive!(set_usize, USize, u32); + set_primitive!(set_felt252, Felt252, FieldElement); + set_primitive!(set_class_hash, ClassHash, FieldElement); + set_primitive!(set_contract_address, ContractAddress, FieldElement); pub fn to_sql_type(&self) -> SqlType { match self { @@ -333,28 +298,39 @@ mod tests { } #[test] - fn as_inner_value() { - let primitive = Primitive::U8(Some(1u8)); + fn inner_value_getter_setter() { + let mut primitive = Primitive::U8(None); + primitive.set_u8(Some(1u8)).unwrap(); assert_eq!(primitive.as_u8(), Some(1u8)); - let primitive = Primitive::U16(Some(1u16)); + let mut primitive = Primitive::U16(None); + primitive.set_u16(Some(1u16)).unwrap(); assert_eq!(primitive.as_u16(), Some(1u16)); - let primitive = Primitive::U32(Some(1u32)); + let mut primitive = Primitive::U32(None); + primitive.set_u32(Some(1u32)).unwrap(); assert_eq!(primitive.as_u32(), Some(1u32)); - let primitive = Primitive::U64(Some(1u64)); + let mut primitive = Primitive::U64(None); + primitive.set_u64(Some(1u64)).unwrap(); assert_eq!(primitive.as_u64(), Some(1u64)); - let primitive = Primitive::U128(Some(1u128)); + let mut primitive = Primitive::U128(None); + primitive.set_u128(Some(1u128)).unwrap(); assert_eq!(primitive.as_u128(), Some(1u128)); - let primitive = Primitive::U256(Some(U256::from(1u128))); + let mut primitive = Primitive::U256(None); + primitive.set_u256(Some(U256::from(1u128))).unwrap(); assert_eq!(primitive.as_u256(), Some(U256::from(1u128))); - let primitive = Primitive::USize(Some(1u32)); + let mut primitive = Primitive::USize(None); + primitive.set_usize(Some(1u32)).unwrap(); assert_eq!(primitive.as_usize(), Some(1u32)); - let primitive = Primitive::Bool(Some(true)); + let mut primitive = Primitive::Bool(None); + primitive.set_bool(Some(true)).unwrap(); assert_eq!(primitive.as_bool(), Some(true)); - let primitive = Primitive::Felt252(Some(FieldElement::from(1u128))); + let mut primitive = Primitive::Felt252(None); + primitive.set_felt252(Some(FieldElement::from(1u128))).unwrap(); assert_eq!(primitive.as_felt252(), Some(FieldElement::from(1u128))); - let primitive = Primitive::ClassHash(Some(FieldElement::from(1u128))); + let mut primitive = Primitive::ClassHash(None); + primitive.set_class_hash(Some(FieldElement::from(1u128))).unwrap(); assert_eq!(primitive.as_class_hash(), Some(FieldElement::from(1u128))); - let primitive = Primitive::ContractAddress(Some(FieldElement::from(1u128))); + let mut primitive = Primitive::ContractAddress(None); + primitive.set_contract_address(Some(FieldElement::from(1u128))).unwrap(); assert_eq!(primitive.as_contract_address(), Some(FieldElement::from(1u128))); } } diff --git a/crates/dojo-types/src/schema.rs b/crates/dojo-types/src/schema.rs index ec86efd39c..f791c7bb96 100644 --- a/crates/dojo-types/src/schema.rs +++ b/crates/dojo-types/src/schema.rs @@ -269,6 +269,16 @@ impl Enum { Ok(self.options[option].name.clone()) } + pub fn set_option(&mut self, name: &str) -> Result<(), EnumError> { + match self.options.iter().position(|option| option.name == name) { + Some(index) => { + self.option = Some(index as u8); + Ok(()) + } + None => Err(EnumError::OptionInvalid), + } + } + pub fn to_sql_value(&self) -> Result { self.option() } diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml index d6020e930d..9ad2757856 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/core/Cargo.toml @@ -13,6 +13,7 @@ anyhow.workspace = true async-trait.workspace = true base64.workspace = true chrono.workspace = true +crypto-bigint = { version = "0.5.3", features = [ "serde" ] } dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world", features = [ "contracts", "manifest" ] } futures-channel = "0.3.0" diff --git a/crates/torii/core/src/cache.rs b/crates/torii/core/src/cache.rs new file mode 100644 index 0000000000..dc8d39bc63 --- /dev/null +++ b/crates/torii/core/src/cache.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use dojo_types::schema::Ty; +use sqlx::SqlitePool; +use tokio::sync::RwLock; + +use crate::error::{Error, QueryError}; +use crate::model::{parse_sql_model_members, SqlModelMember}; + +type ModelName = String; + +pub struct ModelCache { + pool: SqlitePool, + schemas: RwLock>, +} + +impl ModelCache { + pub fn new(pool: SqlitePool) -> Self { + Self { pool, schemas: RwLock::new(HashMap::new()) } + } + + pub async fn schema(&self, model: &str) -> Result { + { + let schemas = self.schemas.read().await; + if let Some(schema) = schemas.get(model) { + return Ok(schema.clone()); + } + } + + self.update_schema(model).await + } + + async fn update_schema(&self, model: &str) -> Result { + let model_members: Vec = sqlx::query_as( + "SELECT id, model_idx, member_idx, name, type, type_enum, enum_options, key FROM \ + model_members WHERE model_id = ? ORDER BY model_idx ASC, member_idx ASC", + ) + .bind(model) + .fetch_all(&self.pool) + .await?; + + if model_members.is_empty() { + return Err(QueryError::ModelNotFound(model.into()).into()); + } + + let ty = parse_sql_model_members(model, &model_members); + let mut schemas = self.schemas.write().await; + schemas.insert(model.into(), ty.clone()); + + Ok(ty) + } + + pub async fn clear(&self) { + self.schemas.write().await.clear(); + } +} diff --git a/crates/torii/core/src/error.rs b/crates/torii/core/src/error.rs index cdcf6b9b95..0d73633076 100644 --- a/crates/torii/core/src/error.rs +++ b/crates/torii/core/src/error.rs @@ -1,3 +1,7 @@ +use std::num::ParseIntError; + +use dojo_types::primitive::PrimitiveError; +use dojo_types::schema::EnumError; use starknet::core::types::{FromByteSliceError, FromStrError}; use starknet::core::utils::CairoShortStringToFeltError; @@ -9,6 +13,10 @@ pub enum Error { Sql(#[from] sqlx::Error), #[error(transparent)] QueryError(#[from] QueryError), + #[error(transparent)] + PrimitiveError(#[from] PrimitiveError), + #[error(transparent)] + EnumError(#[from] EnumError), } #[derive(Debug, thiserror::Error)] @@ -19,10 +27,16 @@ pub enum ParseError { CairoShortStringToFelt(#[from] CairoShortStringToFeltError), #[error(transparent)] FromByteSliceError(#[from] FromByteSliceError), + #[error(transparent)] + ParseIntError(#[from] ParseIntError), } #[derive(Debug, thiserror::Error)] pub enum QueryError { #[error("unsupported query")] UnsupportedQuery, + #[error("model not found: {0}")] + ModelNotFound(String), + #[error("exceeds sqlite `JOIN` limit (64)")] + SqliteJoinLimit, } diff --git a/crates/torii/core/src/lib.rs b/crates/torii/core/src/lib.rs index 877aab65a9..e36c3f2e3b 100644 --- a/crates/torii/core/src/lib.rs +++ b/crates/torii/core/src/lib.rs @@ -3,6 +3,7 @@ use sqlx::FromRow; use crate::types::SQLFieldElement; +pub mod cache; pub mod engine; pub mod error; pub mod model; diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index e701cf28e0..c05831c664 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -1,10 +1,16 @@ +use std::str::FromStr; + use async_trait::async_trait; +use crypto_bigint::U256; +use dojo_types::primitive::Primitive; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; use dojo_world::contracts::model::ModelReader; -use sqlx::{Pool, Sqlite}; +use sqlx::sqlite::SqliteRow; +use sqlx::{Pool, Row, Sqlite}; use starknet::core::types::FieldElement; use super::error::{self, Error}; +use crate::error::{ParseError, QueryError}; pub struct ModelSQLReader { /// The name of the model @@ -144,11 +150,154 @@ pub fn parse_sql_model_members(model: &str, model_members_all: &[SqlModelMember] parse_sql_model_members_impl(model, model_members_all) } +/// Creates a query that fetches all models and their nested data. +pub fn build_sql_query(model_schemas: &Vec) -> Result { + fn parse_struct( + path: &str, + schema: &Struct, + selections: &mut Vec, + tables: &mut Vec, + ) { + for child in &schema.children { + match &child.ty { + Ty::Struct(s) => { + let table_name = format!("{}${}", path, s.name); + parse_struct(&table_name, s, selections, tables); + + tables.push(table_name); + } + _ => { + // alias selected columns to avoid conflicts in `JOIN` + selections.push(format!( + "{}.external_{} AS \"{}.{}\"", + path, child.name, path, child.name + )); + } + } + } + } + + let primary_table = model_schemas[0].name(); + let mut global_selections = Vec::new(); + let mut global_tables = model_schemas + .iter() + .enumerate() + .filter(|(index, _)| *index != 0) // primary_table don't `JOIN` itself + .map(|(_, schema)| schema.name()) + .collect::>(); + + for ty in model_schemas { + let schema = ty.as_struct().expect("schema should be struct"); + let model_table = &schema.name; + let mut selections = Vec::new(); + let mut tables = Vec::new(); + + parse_struct(model_table, schema, &mut selections, &mut tables); + + global_selections.push(selections.join(", ")); + global_tables.extend(tables); + } + + // TODO: Fallback to subqueries, SQLite has a max limit of 64 on 'table 'JOIN' + if global_tables.len() > 64 { + return Err(QueryError::SqliteJoinLimit.into()); + } + + let selections_clause = global_selections.join(", "); + let join_clause = global_tables + .into_iter() + .map(|table| format!(" LEFT JOIN {table} ON {primary_table}.entity_id = {table}.entity_id")) + .collect::>() + .join(" "); + + Ok(format!("SELECT {selections_clause} FROM {primary_table}{join_clause}")) +} + +/// Populate the values of a Ty (schema) from SQLite row. +pub fn map_row_to_ty(path: &str, struct_ty: &mut Struct, row: &SqliteRow) -> Result<(), Error> { + for member in struct_ty.children.iter_mut() { + let column_name = format!("{}.{}", path, member.name); + match &mut member.ty { + Ty::Primitive(primitive) => { + match &primitive { + Primitive::Bool(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_bool(Some(value))?; + } + Primitive::USize(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_usize(Some(value))?; + } + Primitive::U8(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_u8(Some(value))?; + } + Primitive::U16(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_u16(Some(value))?; + } + Primitive::U32(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_u32(Some(value))?; + } + Primitive::U64(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_u64(Some(value as u64))?; + } + Primitive::U128(_) => { + let value = row.try_get::(&column_name)?; + let hex_str = value.trim_start_matches("0x"); + primitive.set_u128(Some( + u128::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, + ))?; + } + Primitive::U256(_) => { + let value = row.try_get::(&column_name)?; + let hex_str = value.trim_start_matches("0x"); + primitive.set_u256(Some(U256::from_be_hex(hex_str)))?; + } + Primitive::Felt252(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_felt252(Some( + FieldElement::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } + Primitive::ClassHash(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_class_hash(Some( + FieldElement::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } + Primitive::ContractAddress(_) => { + let value = row.try_get::(&column_name)?; + primitive.set_contract_address(Some( + FieldElement::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } + }; + } + Ty::Enum(enum_ty) => { + let value = row.try_get::(&column_name)?; + enum_ty.set_option(&value)?; + } + Ty::Struct(struct_ty) => { + let path = [path, &struct_ty.name].join("$"); + map_row_to_ty(&path, struct_ty, row)?; + } + ty => { + unimplemented!("unimplemented type_enum: {ty}"); + } + }; + } + + Ok(()) +} + #[cfg(test)] mod tests { use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; - use super::SqlModelMember; + use super::{build_sql_query, SqlModelMember}; use crate::model::parse_sql_model_members; #[test] @@ -321,4 +470,51 @@ mod tests { assert_eq!(parse_sql_model_members("Moves", &model_members), expected_ty); } + + #[test] + fn struct_ty_to_query() { + let ty = Ty::Struct(Struct { + name: "Position".into(), + children: vec![ + dojo_types::schema::Member { + name: "name".into(), + key: false, + ty: Ty::Primitive("felt252".parse().unwrap()), + }, + dojo_types::schema::Member { + name: "age".into(), + key: false, + ty: Ty::Primitive("u8".parse().unwrap()), + }, + dojo_types::schema::Member { + name: "vec".into(), + key: false, + ty: Ty::Struct(Struct { + name: "Vec2".into(), + children: vec![ + Member { + name: "x".into(), + key: false, + ty: Ty::Primitive("u256".parse().unwrap()), + }, + Member { + name: "y".into(), + key: false, + ty: Ty::Primitive("u256".parse().unwrap()), + }, + ], + }), + }, + ], + }); + + let query = build_sql_query(&vec![ty]).unwrap(); + assert_eq!( + query, + "SELECT Position.external_name AS \"Position.name\", Position.external_age AS \ + \"Position.age\", Position$Vec2.external_x AS \"Position$Vec2.x\", \ + Position$Vec2.external_y AS \"Position$Vec2.y\" FROM Position LEFT JOIN \ + Position$Vec2 ON Position.entity_id = Position$Vec2.entity_id" + ); + } } diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index cf2850268e..ac42333c8d 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -98,7 +98,7 @@ mod tests { assert_eq!(connection.edges.len(), 10); assert_eq!(connection.total_count, 20); assert_eq!(&first_entity.node.model_names, "Subrecord"); - assert_eq!(&last_entity.node.model_names, "Record"); + assert_eq!(&last_entity.node.model_names, "Record,RecordSibling"); // first key param - returns all entities with `0x0` as first key let entities = entities_query(&schema, "(keys: [\"0x0\"])").await; diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index a27b97e314..c0942937bc 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -92,7 +92,7 @@ mod tests { assert_eq!(connection.total_count, 10); assert_eq!(connection.edges.len(), 10); assert_eq!(&record.node.__typename, "Record"); - assert_eq!(&entity.model_names, "Record"); + assert_eq!(&entity.model_names, "Record,RecordSibling"); assert_eq!(entity.keys.clone().unwrap(), vec!["0x0"]); assert_eq!(record.node.depth, "Zero"); assert_eq!(nested.depth, "One"); diff --git a/crates/torii/graphql/src/tests/types-test/src/contracts.cairo b/crates/torii/graphql/src/tests/types-test/src/contracts.cairo index 1e5a0a3dca..638df904a6 100644 --- a/crates/torii/graphql/src/tests/types-test/src/contracts.cairo +++ b/crates/torii/graphql/src/tests/types-test/src/contracts.cairo @@ -8,7 +8,7 @@ trait IRecords { #[dojo::contract] mod records { use starknet::{ContractAddress, get_caller_address}; - use types_test::models::{Record, Subrecord, Nested, NestedMore, NestedMoreMore, Depth}; + use types_test::models::{Record, RecordSibling, Subrecord, Nested, NestedMore, NestedMoreMore, Depth}; use types_test::{seed, random}; use super::IRecords; @@ -90,6 +90,9 @@ mod records { random_u8, random_u128 }, + RecordSibling { + record_id, random_u8 + }, Subrecord { record_id, subrecord_id, type_u8: record_idx.into(), random_u8, } diff --git a/crates/torii/graphql/src/tests/types-test/src/models.cairo b/crates/torii/graphql/src/tests/types-test/src/models.cairo index af855773d4..a68b11ae8e 100644 --- a/crates/torii/graphql/src/tests/types-test/src/models.cairo +++ b/crates/torii/graphql/src/tests/types-test/src/models.cairo @@ -21,6 +21,13 @@ struct Record { random_u128: u128, } +#[derive(Model, Copy, Drop, Serde)] +struct RecordSibling { + #[key] + record_id: u32, + random_u8: u8 +} + #[derive(Copy, Drop, Serde, Introspect)] struct Nested { depth: Depth, diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index 1c853c005b..80d9ae8da6 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -29,6 +29,38 @@ message ModelMetadata { bytes schema = 6; } +message Enum { + // Variant + uint32 option = 1; + // Variants of the enum + repeated string options = 2; +} + +message Member { + // Name of the member + string name = 1; + // Type of member + oneof member_type { + Value value = 2; + Enum enum = 3; + Model struct = 4; + } +} + +message Model { + // Name of the model + string name = 1; + // Members of the model + repeated Member members = 2; +} + +message Entity { + // The entity key + bytes key = 1; + // Models of the entity + repeated Model models = 2; +} + message StorageEntry { // The key of the changed value string key = 1; @@ -55,6 +87,8 @@ message EntityUpdate { message EntityQuery { Clause clause = 1; + uint32 limit = 2; + uint32 offset = 3; } message Clause { @@ -72,7 +106,7 @@ message KeysClause { message AttributeClause { string model = 1; - string attribute = 2; + string member = 2; ComparisonOperator operator = 3; Value value = 4; } diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 66f5fce09d..ec6dccc469 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -9,8 +9,11 @@ service World { rpc WorldMetadata (MetadataRequest) returns (MetadataResponse); - // Subscribes to entity updates. + // Subscribes to entities updates. rpc SubscribeEntities (SubscribeEntitiesRequest) returns (stream SubscribeEntitiesResponse); + + // Retrieve entity + rpc RetrieveEntities (RetrieveEntitiesRequest) returns (RetrieveEntitiesResponse); } @@ -33,3 +36,12 @@ message SubscribeEntitiesResponse { // List of entities that have been updated. types.EntityUpdate entity_update = 1; } + +message RetrieveEntitiesRequest { + // The entities to retrieve + types.EntityQuery query = 1; +} + +message RetrieveEntitiesResponse { + repeated types.Entity entities = 1; +} diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index d5ccac1680..8b7d484817 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -5,13 +5,18 @@ pub mod subscription; use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; +use std::str::FromStr; use std::sync::Arc; +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Struct, Ty}; use futures::Stream; use proto::world::{ - MetadataRequest, MetadataResponse, SubscribeEntitiesRequest, SubscribeEntitiesResponse, + MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, + SubscribeEntitiesRequest, SubscribeEntitiesResponse, }; -use sqlx::{Pool, Sqlite}; +use sqlx::sqlite::SqliteRow; +use sqlx::{Pool, Row, Sqlite}; use starknet::core::utils::cairo_short_string_to_felt; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -21,10 +26,12 @@ use tokio::sync::mpsc::Receiver; use tokio_stream::wrappers::{ReceiverStream, TcpListenerStream}; use tonic::transport::Server; use tonic::{Request, Response, Status}; -use torii_core::error::{Error, ParseError}; -use torii_core::model::{parse_sql_model_members, SqlModelMember}; +use torii_core::cache::ModelCache; +use torii_core::error::{Error, ParseError, QueryError}; +use torii_core::model::build_sql_query; use self::subscription::SubscribeRequest; +use crate::proto::types::clause::ClauseType; use crate::proto::world::world_server::WorldServer; use crate::proto::{self}; @@ -33,6 +40,7 @@ pub struct DojoWorld { world_address: FieldElement, pool: Pool, subscriber_manager: Arc, + model_cache: Arc, } impl DojoWorld { @@ -51,7 +59,9 @@ impl DojoWorld { Arc::clone(&subscriber_manager), )); - Self { pool, world_address, subscriber_manager } + let model_cache = Arc::new(ModelCache::new(pool.clone())); + + Self { pool, model_cache, world_address, subscriber_manager } } } @@ -78,7 +88,7 @@ impl DojoWorld { let mut models_metadata = Vec::with_capacity(models.len()); for model in models { - let schema = self.model_schema(&model.0).await?; + let schema = self.model_cache.schema(&model.0).await?; models_metadata.push(proto::types::ModelMetadata { name: model.0, class_hash: model.1, @@ -98,16 +108,79 @@ impl DojoWorld { }) } - async fn model_schema(&self, model: &str) -> Result { - let model_members: Vec = sqlx::query_as( - "SELECT id, model_idx, member_idx, name, type, type_enum, enum_options, key FROM \ - model_members WHERE model_id = ? ORDER BY model_idx ASC, member_idx ASC", + async fn entities_by_keys( + &self, + keys_clause: proto::types::KeysClause, + limit: u32, + offset: u32, + ) -> Result, Error> { + let keys = keys_clause + .keys + .iter() + .map(|bytes| { + if bytes.is_empty() { + return Ok("%".to_string()); + } + Ok(FieldElement::from_byte_slice_be(bytes) + .map(|felt| format!("{:#x}", felt)) + .map_err(ParseError::FromByteSliceError)?) + }) + .collect::, Error>>()?; + let keys_pattern = keys.join("/") + "/%"; + + let db_entities: Vec<(String, String)> = sqlx::query_as( + "SELECT id, model_names FROM entities WHERE keys LIKE ? ORDER BY event_id ASC LIMIT ? \ + OFFSET ?", ) - .bind(model) + .bind(&keys_pattern) + .bind(limit) + .bind(offset) .fetch_all(&self.pool) .await?; - Ok(parse_sql_model_members(model, &model_members)) + let mut entities = Vec::new(); + for (entity_id, models_str) in db_entities { + let model_names: Vec<&str> = models_str.split(',').collect(); + let mut schemas = Vec::new(); + for model in &model_names { + schemas.push(self.model_cache.schema(model).await?); + } + + let entity_query = + format!("{} WHERE {}.entity_id = ?", build_sql_query(&schemas)?, schemas[0].name()); + let row = sqlx::query(&entity_query).bind(&entity_id).fetch_one(&self.pool).await?; + + let mut models = Vec::new(); + for schema in schemas { + let struct_ty = schema.as_struct().expect("schema should be struct"); + models.push(Self::map_row_to_model(&schema.name(), struct_ty, &row)?); + } + + let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; + entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) + } + + Ok(entities) + } + + async fn entities_by_attribute( + &self, + _attribute: proto::types::AttributeClause, + _limit: u32, + _offset: u32, + ) -> Result, Error> { + // TODO: Implement + Err(QueryError::UnsupportedQuery.into()) + } + + async fn entities_by_composite( + &self, + _composite: proto::types::CompositeClause, + _limit: u32, + _offset: u32, + ) -> Result, Error> { + // TODO: Implement + Err(QueryError::UnsupportedQuery.into()) } pub async fn model_metadata(&self, model: &str) -> Result { @@ -124,7 +197,7 @@ impl DojoWorld { .fetch_one(&self.pool) .await?; - let schema = self.model_schema(model).await?; + let schema = self.model_cache.schema(model).await?; let layout = hex::decode(&layout).unwrap(); Ok(proto::types::ModelMetadata { @@ -161,6 +234,111 @@ impl DojoWorld { self.subscriber_manager.add_subscriber(subs).await } + + async fn retrieve_entities( + &self, + query: proto::types::EntityQuery, + ) -> Result { + let clause_type = query + .clause + .ok_or(QueryError::UnsupportedQuery)? + .clause_type + .ok_or(QueryError::UnsupportedQuery)?; + + let entities = match clause_type { + ClauseType::Keys(keys) => { + self.entities_by_keys(keys, query.limit, query.offset).await? + } + ClauseType::Attribute(attribute) => { + self.entities_by_attribute(attribute, query.limit, query.offset).await? + } + ClauseType::Composite(composite) => { + self.entities_by_composite(composite, query.limit, query.offset).await? + } + }; + + Ok(RetrieveEntitiesResponse { entities }) + } + + fn map_row_to_model( + path: &str, + struct_ty: &Struct, + row: &SqliteRow, + ) -> Result { + let members = struct_ty + .children + .iter() + .map(|member| { + let column_name = format!("{}.{}", path, member.name); + let name = member.name.clone(); + let member = match &member.ty { + Ty::Primitive(primitive) => { + let value_type = match primitive { + Primitive::Bool(_) => proto::types::value::ValueType::BoolValue( + row.try_get::(&column_name)?, + ), + Primitive::U8(_) + | Primitive::U16(_) + | Primitive::U32(_) + | Primitive::U64(_) + | Primitive::USize(_) => { + let value = row.try_get::(&column_name)?; + proto::types::value::ValueType::UintValue(value as u64) + } + Primitive::U128(_) + | Primitive::U256(_) + | Primitive::Felt252(_) + | Primitive::ClassHash(_) + | Primitive::ContractAddress(_) => { + let value = row.try_get::(&column_name)?; + proto::types::value::ValueType::StringValue(value) + } + }; + + proto::types::Member { + name, + member_type: Some(proto::types::member::MemberType::Value( + proto::types::Value { value_type: Some(value_type) }, + )), + } + } + Ty::Enum(enum_ty) => { + let value = row.try_get::(&column_name)?; + let options = enum_ty + .options + .iter() + .map(|e| e.name.to_string()) + .collect::>(); + let option = + options.iter().position(|o| o == &value).expect("wrong enum value") + as u32; + proto::types::Member { + name: enum_ty.name.clone(), + member_type: Some(proto::types::member::MemberType::Enum( + proto::types::Enum { option, options }, + )), + } + } + Ty::Struct(struct_ty) => { + let path = [path, &struct_ty.name].join("$"); + proto::types::Member { + name, + member_type: Some(proto::types::member::MemberType::Struct( + Self::map_row_to_model(&path, struct_ty, row)?, + )), + } + } + ty => { + unimplemented!("unimplemented type_enum: {ty}"); + } + }; + + Ok(member) + }) + .collect::, Error>>()?; + + Ok(proto::types::Model { name: struct_ty.name.clone(), members }) + } } type ServiceResult = Result, Status>; @@ -194,6 +372,21 @@ impl proto::world::world_server::World for DojoWorld { .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) } + + async fn retrieve_entities( + &self, + request: Request, + ) -> Result, Status> { + let query = request + .into_inner() + .query + .ok_or_else(|| Status::invalid_argument("Missing query argument"))?; + + let entities = + self.retrieve_entities(query).await.map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(entities)) + } } pub async fn new( diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types.rs index e62dc3b8a8..00efe50358 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types.rs @@ -13,6 +13,8 @@ use crate::proto; #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { pub clause: Clause, + pub limit: u32, + pub offset: u32, } #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] @@ -31,7 +33,7 @@ pub struct KeysClause { #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct AttributeClause { pub model: String, - pub attribute: String, + pub member: String, pub operator: ComparisonOperator, pub value: Value, } @@ -105,7 +107,7 @@ impl TryFrom for dojo_types::WorldMetadata { impl From for proto::types::EntityQuery { fn from(value: Query) -> Self { - Self { clause: Some(value.clause.into()) } + Self { clause: Some(value.clause.into()), limit: value.limit, offset: value.offset } } } @@ -152,7 +154,7 @@ impl From for proto::types::AttributeClause { fn from(value: AttributeClause) -> Self { Self { model: value.model, - attribute: value.attribute, + member: value.member, operator: value.operator as i32, value: Some(value.value.into()), } From d8a9a3f1ef9d923edef5e812a344f66a1855386b Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 23 Nov 2023 09:08:03 -0800 Subject: [PATCH 027/192] Support deriving Introspect for generics (#1082) --- crates/dojo-core/src/database.cairo | 4 +- .../{schema.cairo => introspect.cairo} | 2 +- .../src/database/introspect_test.cairo | 17 + crates/dojo-core/src/model.cairo | 2 +- crates/dojo-core/src/packing_test.cairo | 2 +- crates/dojo-core/src/world_test.cairo | 6 +- crates/dojo-lang/src/inline_macros/get.rs | 3 +- crates/dojo-lang/src/introspect.rs | 144 ++- .../dojo-lang/src/manifest_test_data/manifest | 28 +- crates/dojo-lang/src/model.rs | 12 +- .../dojo-lang/src/plugin_test_data/introspect | 1065 ++++------------- crates/dojo-lang/src/plugin_test_data/model | 431 ++++--- crates/dojo-lang/src/semantics/test_data/get | 2 +- crates/dojo-world/src/contracts/model_test.rs | 2 +- .../graphql/src/tests/types-test/Scarb.lock | 5 +- 15 files changed, 547 insertions(+), 1178 deletions(-) rename crates/dojo-core/src/database/{schema.cairo => introspect.cairo} (97%) create mode 100644 crates/dojo-core/src/database/introspect_test.cairo diff --git a/crates/dojo-core/src/database.cairo b/crates/dojo-core/src/database.cairo index 925b02cacf..86b6ec6136 100644 --- a/crates/dojo-core/src/database.cairo +++ b/crates/dojo-core/src/database.cairo @@ -7,7 +7,9 @@ use poseidon::poseidon_hash_span; mod index; #[cfg(test)] mod index_test; -mod schema; +mod introspect; +#[cfg(test)] +mod introspect_test; mod storage; #[cfg(test)] mod storage_test; diff --git a/crates/dojo-core/src/database/schema.cairo b/crates/dojo-core/src/database/introspect.cairo similarity index 97% rename from crates/dojo-core/src/database/schema.cairo rename to crates/dojo-core/src/database/introspect.cairo index 2aa0541f05..3d0af0d90b 100644 --- a/crates/dojo-core/src/database/schema.cairo +++ b/crates/dojo-core/src/database/introspect.cairo @@ -41,7 +41,7 @@ fn serialize_member_type(m: @Ty) -> Span { serialized.span() } -trait SchemaIntrospection { +trait Introspect { fn size() -> usize; fn layout(ref layout: Array); fn ty() -> Ty; diff --git a/crates/dojo-core/src/database/introspect_test.cairo b/crates/dojo-core/src/database/introspect_test.cairo new file mode 100644 index 0000000000..e366f8acd3 --- /dev/null +++ b/crates/dojo-core/src/database/introspect_test.cairo @@ -0,0 +1,17 @@ +use dojo::database::introspect::Introspect; + +#[derive(Drop, Introspect)] +struct Base { + value: u32, +} + +#[derive(Drop, Introspect)] +struct Generic { + value: T, +} + +#[test] +#[available_gas(2000000)] +fn test_generic_introspect() { + let generic = Generic { value: Base { value: 123 } }; +} diff --git a/crates/dojo-core/src/model.cairo b/crates/dojo-core/src/model.cairo index 2547a9275a..f8657cc666 100644 --- a/crates/dojo-core/src/model.cairo +++ b/crates/dojo-core/src/model.cairo @@ -10,5 +10,5 @@ trait Model { trait IModel { fn name(self: @T) -> felt252; fn layout(self: @T) -> Span; - fn schema(self: @T) -> Span; + fn schema(self: @T) -> Span; } diff --git a/crates/dojo-core/src/packing_test.cairo b/crates/dojo-core/src/packing_test.cairo index 38dfde0015..d4ffa6f380 100644 --- a/crates/dojo-core/src/packing_test.cairo +++ b/crates/dojo-core/src/packing_test.cairo @@ -5,7 +5,7 @@ use integer::U256BitAnd; use option::OptionTrait; use debug::PrintTrait; use traits::{Into, TryInto}; -use dojo::database::schema::SchemaIntrospection; +use dojo::database::introspect::Introspect; #[test] #[available_gas(9000000)] diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index 8612168548..de6ced047f 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -10,7 +10,7 @@ use starknet::syscalls::deploy_syscall; use dojo::benchmarks; use dojo::executor::executor; use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world}; -use dojo::database::schema::SchemaIntrospection; +use dojo::database::introspect::Introspect; use dojo::test_utils::{spawn_test_world, deploy_with_world_address}; use dojo::benchmarks::{Character, end}; @@ -38,7 +38,7 @@ trait Ibar { #[starknet::contract] mod bar { - use super::{Foo, IWorldDispatcher, IWorldDispatcherTrait, SchemaIntrospection}; + use super::{Foo, IWorldDispatcher, IWorldDispatcherTrait, Introspect}; use super::benchmarks::{Character, Abilities, Stats, Weapon, Sword}; use traits::Into; use starknet::{get_caller_address, ContractAddress}; @@ -60,7 +60,7 @@ mod bar { fn delete_foo(self: @ContractState) { let mut layout = array![]; - SchemaIntrospection::::layout(ref layout); + Introspect::::layout(ref layout); self .world .read() diff --git a/crates/dojo-lang/src/inline_macros/get.rs b/crates/dojo-lang/src/inline_macros/get.rs index 2717d01013..ecb3aec87a 100644 --- a/crates/dojo-lang/src/inline_macros/get.rs +++ b/crates/dojo-lang/src/inline_macros/get.rs @@ -106,8 +106,7 @@ impl InlineMacroExprPlugin for GetMacro { builder.add_str(&format!( "\n let mut __{model}_layout__ = array::ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::<{model}>::layout(ref \ - __{model}_layout__); + dojo::database::introspect::Introspect::<{model}>::layout(ref __{model}_layout__); let mut __{model}_layout_clone__ = __{model}_layout__.clone(); let mut __{model}_layout_span__ = array::ArrayTrait::span(@__{model}_layout__); let mut __{model}_layout_clone_span__ = \ diff --git a/crates/dojo-lang/src/introspect.rs b/crates/dojo-lang/src/introspect.rs index 8cd46e1967..662d8d4fbc 100644 --- a/crates/dojo-lang/src/introspect.rs +++ b/crates/dojo-lang/src/introspect.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use cairo_lang_defs::patcher::RewriteNode; use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_syntax::node::ast::{Expr, ItemEnum, ItemStruct, OptionTypeClause}; +use cairo_lang_syntax::node::ast::{ + Expr, GenericParam, ItemEnum, ItemStruct, OptionTypeClause, OptionWrappedGenericParamList, +}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; @@ -57,23 +59,23 @@ pub fn handle_introspect_struct(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> if primitive_sizes.get(&ty).is_some() { // It's a primitive type member_types.push(format!( - " - dojo::database::schema::serialize_member(@dojo::database::schema::Member {{ - name: '{name}', - ty: dojo::database::schema::Ty::Primitive('{ty}'), - attrs: array![{}].span() - }})\n", + "dojo::database::introspect::serialize_member(@\ + dojo::database::introspect::Member {{ + name: '{name}', + ty: dojo::database::introspect::Ty::Primitive('{ty}'), + attrs: array![{}].span() + }})", attrs.join(","), )); } else { // It's a custom struct/enum member_types.push(format!( - " - dojo::database::schema::serialize_member(@dojo::database::schema::Member {{ - name: '{name}', - ty: dojo::database::schema::SchemaIntrospection::<{ty}>::ty(), - attrs: array![{}].span() - }})\n", + "dojo::database::introspect::serialize_member(@\ + dojo::database::introspect::Member {{ + name: '{name}', + ty: dojo::database::introspect::Introspect::<{ty}>::ty(), + attrs: array![{}].span() + }})", attrs.join(","), )); } @@ -81,19 +83,17 @@ pub fn handle_introspect_struct(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> Member { name, ty, key } }) .collect::<_>(); - drop(primitive_sizes); let type_ty = format!( - " - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct {{ + "dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct {{ name: '{name}', attrs: array![].span(), children: array![{}].span() }})", - member_types.join(",\n") + member_types.join(", ") ); - handle_introspect_internal(db, name, vec![], 0, type_ty, members) + handle_introspect_internal(db, name, struct_ast.generic_params(db), vec![], 0, type_ty, members) } /// A handler for Dojo code derives Introspect for an enum @@ -108,6 +108,7 @@ pub fn handle_introspect_enum( enum_ast: ItemEnum, ) -> RewriteNode { let name = enum_ast.name(db).text(db).into(); + let variant_type = enum_ast.variants(db).elements(db).first().unwrap().type_clause(db); let variant_type_text = variant_type.as_syntax_node().get_text(db); let variant_type_text = variant_type_text.trim(); @@ -121,8 +122,8 @@ pub fn handle_introspect_enum( variant_type_arr.push(( // Not using Ty right now, but still keeping it for later. format!( - "dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('{}') + "dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('{}') )", ty_name ), @@ -134,8 +135,8 @@ pub fn handle_introspect_enum( variant_type_arr.push(( // Not using Ty right now, but still keeping it for later. format!( - "dojo::database::schema::serialize_member_type( - @dojo::database::schema::SchemaIntrospection::<{}>::ty() + "dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Introspect::<{}>::ty() )", ty_name ), @@ -170,17 +171,15 @@ pub fn handle_introspect_enum( // @TODO: Prepare type struct arms_ty.push(format!( - " - ( - '{member_name}', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![{}].span())) - )", + "( + '{member_name}', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![{}].span())) + )", if !variant_type_arr.is_empty() { let ty_cairo: Vec<_> = variant_type_arr.iter().map(|(ty_cairo, _)| ty_cairo.to_string()).collect(); - // format!("'{}'", &ty_cairo.join("', '")) - ty_cairo.join(",\n") + ty_cairo.join(", ") } else { "".to_string() } @@ -188,14 +187,11 @@ pub fn handle_introspect_enum( }); let type_ty = format!( - " - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum {{ + "dojo::database::introspect::Ty::Enum( + dojo::database::introspect::Enum {{ name: '{name}', attrs: array![].span(), - children: array![ - {} - ].span() + children: array![{}].span() }} )", arms_ty.join(",\n") @@ -204,12 +200,21 @@ pub fn handle_introspect_enum( // Enums have 1 size and 8 bit layout by default let layout = vec![RewriteNode::Text("layout.append(8);\n".into())]; let size_precompute = 1; - handle_introspect_internal(db, name, layout, size_precompute, type_ty, members) + handle_introspect_internal( + db, + name, + enum_ast.generic_params(db), + layout, + size_precompute, + type_ty, + members, + ) } fn handle_introspect_internal( - _db: &dyn SyntaxGroup, + db: &dyn SyntaxGroup, name: String, + generics: OptionWrappedGenericParamList, mut layout: Vec, mut size_precompute: usize, type_ty: String, @@ -218,6 +223,30 @@ fn handle_introspect_internal( let mut size = vec![]; let primitive_sizes = primitive_type_introspection(); + let generics = if let OptionWrappedGenericParamList::WrappedGenericParamList(params) = generics + { + params + .generic_params(db) + .elements(db) + .iter() + .filter_map(|el| { + if let GenericParam::Type(typ) = el { + Some(typ.name(db).text(db).to_string()) + } else { + None + } + }) + .collect::>() + } else { + vec![] + }; + + let generic_impls = generics + .iter() + .map(|g| format!("{g}, impl {g}Introspect: dojo::database::introspect::Introspect<{g}>")) + .collect::>() + .join(", "); + members.iter().for_each(|m| { let primitive_intro = primitive_sizes.get(&m.ty); let mut attrs = vec![]; @@ -237,12 +266,9 @@ fn handle_introspect_internal( if m.key { attrs.push("'key'"); } else { - size.push(format!( - "dojo::database::schema::SchemaIntrospection::<{}>::size()", - m.ty, - )); + size.push(format!("dojo::database::introspect::Introspect::<{}>::size()", m.ty,)); layout.push(RewriteNode::Text(format!( - "dojo::database::schema::SchemaIntrospection::<{}>::layout(ref layout);\n", + "dojo::database::introspect::Introspect::<{}>::layout(ref layout);\n", m.ty ))); } @@ -255,26 +281,28 @@ fn handle_introspect_internal( RewriteNode::interpolate_patched( " - impl $name$SchemaIntrospection of dojo::database::schema::SchemaIntrospection<$name$> { - - #[inline(always)] - fn size() -> usize { - $size$ - } +impl $name$Introspect<$generics$> of \ + dojo::database::introspect::Introspect<$name$<$generics_types$>> { + #[inline(always)] + fn size() -> usize { + $size$ + } - #[inline(always)] - fn layout(ref layout: Array) { - $layout$ - } + #[inline(always)] + fn layout(ref layout: Array) { + $layout$ + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - $ty$ - } - } + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + $ty$ + } +} ", &UnorderedHashMap::from([ ("name".to_string(), RewriteNode::Text(name)), + ("generics".to_string(), RewriteNode::Text(generic_impls)), + ("generics_types".to_string(), RewriteNode::Text(generics.join(", "))), ("size".to_string(), RewriteNode::Text(size.join(" + "))), ("layout".to_string(), RewriteNode::new_modified(layout)), ("ty".to_string(), RewriteNode::Text(type_ty)), diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 5a491c57f8..393e808895 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -1200,7 +1200,7 @@ test_manifest_file "key": false } ], - "class_hash": "0x2e5174b54aef0b99d4685827ffa51488447e1f5607908293d5c715d6bd22433", + "class_hash": "0x509a65bd8cc5516176a694a3b3c809011f1f0680959c567b3189e60ddab7ce1", "abi": [ { "type": "function", @@ -1278,7 +1278,7 @@ test_manifest_file }, { "type": "struct", - "name": "dojo::database::schema::Struct", + "name": "dojo::database::introspect::Struct", "members": [ { "name": "name", @@ -1306,7 +1306,7 @@ test_manifest_file }, { "type": "struct", - "name": "dojo::database::schema::Enum", + "name": "dojo::database::introspect::Enum", "members": [ { "name": "name", @@ -1324,7 +1324,7 @@ test_manifest_file }, { "type": "enum", - "name": "dojo::database::schema::Ty", + "name": "dojo::database::introspect::Ty", "variants": [ { "name": "Primitive", @@ -1332,11 +1332,11 @@ test_manifest_file }, { "name": "Struct", - "type": "dojo::database::schema::Struct" + "type": "dojo::database::introspect::Struct" }, { "name": "Enum", - "type": "dojo::database::schema::Enum" + "type": "dojo::database::introspect::Enum" }, { "name": "Tuple", @@ -1350,7 +1350,7 @@ test_manifest_file "inputs": [], "outputs": [ { - "type": "dojo::database::schema::Ty" + "type": "dojo::database::introspect::Ty" } ], "state_mutability": "view" @@ -1377,7 +1377,7 @@ test_manifest_file "key": false } ], - "class_hash": "0x6a11b5b3003a3aa0ae7f8f443e48314cc0bc51eaea7c3ed1c19beb909f5dda3", + "class_hash": "0x52a1da1853c194683ca5d6d154452d0654d23f2eacd4267c555ff2338e144d6", "abi": [ { "type": "function", @@ -1455,7 +1455,7 @@ test_manifest_file }, { "type": "struct", - "name": "dojo::database::schema::Struct", + "name": "dojo::database::introspect::Struct", "members": [ { "name": "name", @@ -1483,7 +1483,7 @@ test_manifest_file }, { "type": "struct", - "name": "dojo::database::schema::Enum", + "name": "dojo::database::introspect::Enum", "members": [ { "name": "name", @@ -1501,7 +1501,7 @@ test_manifest_file }, { "type": "enum", - "name": "dojo::database::schema::Ty", + "name": "dojo::database::introspect::Ty", "variants": [ { "name": "Primitive", @@ -1509,11 +1509,11 @@ test_manifest_file }, { "name": "Struct", - "type": "dojo::database::schema::Struct" + "type": "dojo::database::introspect::Struct" }, { "name": "Enum", - "type": "dojo::database::schema::Enum" + "type": "dojo::database::introspect::Enum" }, { "name": "Tuple", @@ -1527,7 +1527,7 @@ test_manifest_file "inputs": [], "outputs": [ { - "type": "dojo::database::schema::Ty" + "type": "dojo::database::introspect::Ty" } ], "state_mutability": "view" diff --git a/crates/dojo-lang/src/model.rs b/crates/dojo-lang/src/model.rs index 65a016b48e..2250db615e 100644 --- a/crates/dojo-lang/src/model.rs +++ b/crates/dojo-lang/src/model.rs @@ -96,7 +96,7 @@ pub fn handle_model_struct( #[inline(always)] fn layout(self: @$type_name$) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -128,13 +128,13 @@ pub fn handle_model_struct( #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::<$type_name$>::size() + dojo::database::introspect::Introspect::<$type_name$>::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -142,13 +142,13 @@ pub fn handle_model_struct( #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::<$type_name$>::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::<$type_name$>::ty() } } ", diff --git a/crates/dojo-lang/src/plugin_test_data/introspect b/crates/dojo-lang/src/plugin_test_data/introspect index f24bd429ed..7ee8be69f3 100644 --- a/crates/dojo-lang/src/plugin_test_data/introspect +++ b/crates/dojo-lang/src/plugin_test_data/introspect @@ -36,7 +36,7 @@ enum EnumCustom { Right: Vec2, } -#[derive(Model, Copy, Drop, Introspect)] +#[derive(Copy, Drop, Introspect)] struct Position { #[key] player: ContractAddress, @@ -45,525 +45,11 @@ struct Position { after: u16, } -//! > generated_cairo_code -use serde::Serde; - - #[derive(Copy, Drop, Serde, Introspect)] -struct Vec2 { - x: u32, - y: u32 -} - -impl Vec2SchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - 2 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(32); - layout.append(32); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { - name: 'Vec2', - attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'x', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'y', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - } - ) - ] - .span() - } - ) - } -} - - - -#[derive(Serde, Copy, Drop, Introspect)] -enum PlainEnum { - Left: (), - Right: (), -} - -impl PlainEnumSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - 1 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { - name: 'PlainEnum', - attrs: array![].span(), - children: array![ - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![].span()) - ) - ), - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![].span()) - ) - ) - ] - .span() - } - ) - } -} - - - -#[derive(Serde, Copy, Drop, Introspect)] -enum EnumPrimitive { - Left: (u16,), - Right: (u16,), -} - -impl EnumPrimitiveSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - 2 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - layout.append(16); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { - name: 'EnumPrimitive', - attrs: array![].span(), - children: array![ - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u16') - ) - ] - .span() - ) - ) - ), - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u16') - ) - ] - .span() - ) - ) - ) - ] - .span() - } - ) - } -} - - - -#[derive(Serde, Copy, Drop, Introspect)] -enum EnumTuple { - Left: (u8, u8), - Right: (u8, u8), +struct GenericStruct { + t: T, } -impl EnumTupleSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - 3 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - layout.append(8); - layout.append(8); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { - name: 'EnumTuple', - attrs: array![].span(), - children: array![ - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ), - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ) - ] - .span() - ) - ) - ), - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ), - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ) - ] - .span() - ) - ) - ) - ] - .span() - } - ) - } -} - - - -#[derive(Serde, Copy, Drop, Introspect)] -enum EnumCustom { - Left: Vec2, - Right: Vec2, -} - -impl EnumCustomSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 1 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { - name: 'EnumCustom', - attrs: array![].span(), - children: array![ - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::SchemaIntrospection::::ty() - ) - ] - .span() - ) - ) - ), - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple( - array![ - dojo::database::schema::serialize_member_type( - @dojo::database::schema::SchemaIntrospection::::ty() - ) - ] - .span() - ) - ) - ) - ] - .span() - } - ) - } -} - - - -#[derive(Model, Copy, Drop, Introspect)] -struct Position { - #[key] - player: ContractAddress, - before: u8, - vec: Vec2, - after: u16, -} -impl PositionModel of dojo::model::Model { - #[inline(always)] - fn name(self: @Position) -> felt252 { - 'Position' - } - - #[inline(always)] - fn keys(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.player, ref serialized); - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.before, ref serialized); - serde::Serde::serialize(self.vec, ref serialized); - serde::Serde::serialize(self.after, ref serialized); - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn layout(self: @Position) -> Span { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - array::ArrayTrait::span(@layout) - } - - #[inline(always)] - fn packed_size(self: @Position) -> usize { - let mut layout = self.layout(); - dojo::packing::calculate_packed_size(ref layout) - } -} - - -impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 2 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - layout.append(16); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { - name: 'Position', - attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), - attrs: array!['key'].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'before', - ty: dojo::database::schema::Ty::Primitive('u8'), - attrs: array![].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'vec', - ty: dojo::database::schema::SchemaIntrospection::::ty(), - attrs: array![].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'after', - ty: dojo::database::schema::Ty::Primitive('u16'), - attrs: array![].span() - } - ) - ] - .span() - } - ) - } -} - - -#[starknet::interface] -trait IPosition { - fn name(self: @T) -> felt252; -} - -#[starknet::contract] -mod position { - use super::Position; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - 'Position' - } - - #[external(v0)] - fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() - } - - #[external(v0)] - fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - let mut layout_span = layout.span(); - dojo::packing::calculate_packed_size(ref layout_span) - } - - #[external(v0)] - fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - array::ArrayTrait::span(@layout) - } - - #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() - } -} - -impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 2 - } - - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - layout.append(16); - } - - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { - name: 'Position', - attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), - attrs: array!['key'].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'before', - ty: dojo::database::schema::Ty::Primitive('u8'), - attrs: array![].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'vec', - ty: dojo::database::schema::SchemaIntrospection::::ty(), - attrs: array![].span() - } - ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { - name: 'after', - ty: dojo::database::schema::Ty::Primitive('u16'), - attrs: array![].span() - } - ) - ] - .span() - } - ) - } -} - -//! > expected_diagnostics -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:96:13 - #[starknet::contract] - ^*******************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:100:17 - #[storage] - ^********^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:103:17 - #[external(v0)] - ^*************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:108:17 - #[external(v0)] - ^*************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:113:17 - #[external(v0)] - ^*************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:121:17 - #[external(v0)] - ^*************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[Position]:128:17 - #[external(v0)] - ^*************^ - //! > expanded_cairo_code use serde::Serde; @@ -597,7 +83,7 @@ enum EnumCustom { Right: Vec2, } -#[derive(Model, Copy, Drop, Introspect)] +#[derive(Copy, Drop, Introspect)] struct Position { #[key] player: ContractAddress, @@ -605,6 +91,11 @@ struct Position { vec: Vec2, after: u16, } + +#[derive(Copy, Drop, Serde, Introspect)] +struct GenericStruct { + t: T, +} impl Vec2Copy of Copy::; impl Vec2Drop of Drop::; impl Vec2Serde of Serde:: { @@ -620,43 +111,36 @@ impl Vec2Serde of Serde:: { } } - impl Vec2SchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 2 - } +impl Vec2Introspect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 2 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(32); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(32); layout.append(32); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Vec2', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'x', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'y', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'x', + ty: dojo::database::introspect::Ty::Primitive('u32'), + attrs: array![].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'y', + ty: dojo::database::introspect::Ty::Primitive('u32'), + attrs: array![].span() + })].span() }) - } - } + } +} impl PlainEnumSerde of Serde:: { fn serialize(self: @PlainEnum, ref output: array::Array) { match self { @@ -676,44 +160,38 @@ impl PlainEnumSerde of Serde:: { impl PlainEnumCopy of Copy::; impl PlainEnumDrop of Drop::; - impl PlainEnumSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 1 - } +impl PlainEnumIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 1 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Enum( + dojo::database::introspect::Enum { name: 'PlainEnum', attrs: array![].span(), - children: array![ - - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![].span())) - ), - - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![].span())) - ) - ].span() + children: array![( + 'Left', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![].span())) + ), +( + 'Right', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![].span())) + )].span() } ) - } - } + } +} impl EnumPrimitiveSerde of Serde:: { fn serialize(self: @EnumPrimitive, ref output: array::Array) { match self { @@ -733,49 +211,43 @@ impl EnumPrimitiveSerde of Serde:: { impl EnumPrimitiveCopy of Copy::; impl EnumPrimitiveDrop of Drop::; - impl EnumPrimitiveSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 2 - } +impl EnumPrimitiveIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 2 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); layout.append(16); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Enum( + dojo::database::introspect::Enum { name: 'EnumPrimitive', attrs: array![].span(), - children: array![ - - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u16') + children: array![( + 'Left', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u16') )].span())) - ), - - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u16') + ), +( + 'Right', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u16') )].span())) - ) - ].span() + )].span() } ) - } - } + } +} impl EnumTupleSerde of Serde:: { fn serialize(self: @EnumTuple, ref output: array::Array) { match self { @@ -795,56 +267,48 @@ impl EnumTupleSerde of Serde:: { impl EnumTupleCopy of Copy::; impl EnumTupleDrop of Drop::; - impl EnumTupleSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 3 - } +impl EnumTupleIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 3 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); layout.append(8); layout.append(8); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Enum( + dojo::database::introspect::Enum { name: 'EnumTuple', attrs: array![].span(), - children: array![ - - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ), -dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') + children: array![( + 'Left', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u8') + ), dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u8') )].span())) - ), - - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') - ), -dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Primitive('u8') + ), +( + 'Right', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u8') + ), dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Primitive('u8') )].span())) - ) - ].span() + )].span() } ) - } - } + } +} impl EnumCustomSerde of Serde:: { fn serialize(self: @EnumCustom, ref output: array::Array) { match self { @@ -864,233 +328,122 @@ impl EnumCustomSerde of Serde:: { impl EnumCustomCopy of Copy::; impl EnumCustomDrop of Drop::; - impl EnumCustomSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 1 - } +impl EnumCustomIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + dojo::database::introspect::Introspect::::size() + 1 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); -dojo::database::schema::SchemaIntrospection::::layout(ref layout); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); +dojo::database::introspect::Introspect::::layout(ref layout); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Enum( - dojo::database::schema::Enum { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Enum( + dojo::database::introspect::Enum { name: 'EnumCustom', attrs: array![].span(), - children: array![ - - ( - 'Left', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::SchemaIntrospection::::ty() + children: array![( + 'Left', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Introspect::::ty() )].span())) - ), - - ( - 'Right', - dojo::database::schema::serialize_member_type( - @dojo::database::schema::Ty::Tuple(array![dojo::database::schema::serialize_member_type( - @dojo::database::schema::SchemaIntrospection::::ty() + ), +( + 'Right', + dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Ty::Tuple(array![dojo::database::introspect::serialize_member_type( + @dojo::database::introspect::Introspect::::ty() )].span())) - ) - ].span() + )].span() } ) - } - } + } +} impl PositionCopy of Copy::; impl PositionDrop of Drop::; - impl PositionModel of dojo::model::Model { - #[inline(always)] - fn name(self: @Position) -> felt252 { - 'Position' - } - - #[inline(always)] - fn keys(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.player, ref serialized); - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.before, ref serialized);serde::Serde::serialize(self.vec, ref serialized);serde::Serde::serialize(self.after, ref serialized); - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn layout(self: @Position) -> Span { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - array::ArrayTrait::span(@layout) - } - - #[inline(always)] - fn packed_size(self: @Position) -> usize { - let mut layout = self.layout(); - dojo::packing::calculate_packed_size(ref layout) - } - } - - - impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 2 - } +impl PositionIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + dojo::database::introspect::Introspect::::size() + 2 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); -dojo::database::schema::SchemaIntrospection::::layout(ref layout); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); +dojo::database::introspect::Introspect::::layout(ref layout); layout.append(16); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Position', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), - attrs: array!['key'].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'before', - ty: dojo::database::schema::Ty::Primitive('u8'), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'vec', - ty: dojo::database::schema::SchemaIntrospection::::ty(), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'after', - ty: dojo::database::schema::Ty::Primitive('u16'), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'player', + ty: dojo::database::introspect::Ty::Primitive('ContractAddress'), + attrs: array!['key'].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'before', + ty: dojo::database::introspect::Ty::Primitive('u8'), + attrs: array![].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'vec', + ty: dojo::database::introspect::Introspect::::ty(), + attrs: array![].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'after', + ty: dojo::database::introspect::Ty::Primitive('u16'), + attrs: array![].span() + })].span() }) - } - } - - - #[starknet::interface] - trait IPosition { - fn name(self: @T) -> felt252; - } - - #[starknet::contract] - mod position { - use super::Position; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - 'Position' - } - - #[external(v0)] - fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() - } - - #[external(v0)] - fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - let mut layout_span = layout.span(); - dojo::packing::calculate_packed_size(ref layout_span) - } - - #[external(v0)] - fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); - array::ArrayTrait::span(@layout) - } + } +} +impl GenericStructCopy> of Copy::>; +impl GenericStructDrop> of Drop::>; +impl GenericStructSerde, impl TDestruct: Destruct> of Serde::> { + fn serialize(self: @GenericStruct, ref output: array::Array) { + serde::Serde::serialize(self.t, ref output) + } + fn deserialize(ref serialized: array::Span) -> Option> { + Option::Some(GenericStruct { + t: serde::Serde::deserialize(ref serialized)?, + }) + } +} - #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() - } - } - - impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + 2 - } +impl GenericStructIntrospect> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + dojo::database::introspect::Introspect::::size() + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(8); -dojo::database::schema::SchemaIntrospection::::layout(ref layout); -layout.append(16); + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::introspect::Introspect::::layout(ref layout); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { - name: 'Position', + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { + name: 'GenericStruct', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), - attrs: array!['key'].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'before', - ty: dojo::database::schema::Ty::Primitive('u8'), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'vec', - ty: dojo::database::schema::SchemaIntrospection::::ty(), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'after', - ty: dojo::database::schema::Ty::Primitive('u16'), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 't', + ty: dojo::database::introspect::Introspect::::ty(), + attrs: array![].span() + })].span() }) - } - } + } +} + +//! > expected_diagnostics diff --git a/crates/dojo-lang/src/plugin_test_data/model b/crates/dojo-lang/src/plugin_test_data/model index c6fdecdb4f..00a549b83f 100644 --- a/crates/dojo-lang/src/plugin_test_data/model +++ b/crates/dojo-lang/src/plugin_test_data/model @@ -66,7 +66,7 @@ struct Vec3 { z: u8, } -impl Vec3SchemaIntrospection of dojo::database::schema::SchemaIntrospection { +impl Vec3Introspect of dojo::database::introspect::Introspect { #[inline(always)] fn size() -> usize { 3 @@ -80,30 +80,30 @@ impl Vec3SchemaIntrospection of dojo::database::schema::SchemaIntrospection dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct( + dojo::database::introspect::Struct { name: 'Vec3', attrs: array![].span(), children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'x', - ty: dojo::database::schema::Ty::Primitive('u32'), + ty: dojo::database::introspect::Ty::Primitive('u32'), attrs: array![].span() } ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'y', - ty: dojo::database::schema::Ty::Primitive('u32'), + ty: dojo::database::introspect::Ty::Primitive('u32'), attrs: array![].span() } ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'z', - ty: dojo::database::schema::Ty::Primitive('u8'), + ty: dojo::database::introspect::Ty::Primitive('u8'), attrs: array![].span() } ) @@ -146,7 +146,7 @@ impl PositionModel of dojo::model::Model { #[inline(always)] fn layout(self: @Position) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -158,35 +158,35 @@ impl PositionModel of dojo::model::Model { } -impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { +impl PositionIntrospect of dojo::database::introspect::Introspect { #[inline(always)] fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[inline(always)] fn layout(ref layout: Array) { - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); } #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct( + dojo::database::introspect::Struct { name: 'Position', attrs: array![].span(), children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'id', - ty: dojo::database::schema::Ty::Primitive('felt252'), + ty: dojo::database::introspect::Ty::Primitive('felt252'), attrs: array!['key'].span() } ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'v', - ty: dojo::database::schema::SchemaIntrospection::::ty(), + ty: dojo::database::introspect::Introspect::::ty(), attrs: array![].span() } ) @@ -217,13 +217,13 @@ mod position { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -231,13 +231,13 @@ mod position { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } @@ -291,7 +291,7 @@ impl RolesModel of dojo::model::Model { #[inline(always)] fn layout(self: @Roles) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -303,28 +303,28 @@ impl RolesModel of dojo::model::Model { } -impl RolesSchemaIntrospection of dojo::database::schema::SchemaIntrospection { +impl RolesIntrospect of dojo::database::introspect::Introspect { #[inline(always)] fn size() -> usize { - dojo::database::schema::SchemaIntrospection::>::size() + dojo::database::introspect::Introspect::>::size() } #[inline(always)] fn layout(ref layout: Array) { - dojo::database::schema::SchemaIntrospection::>::layout(ref layout); + dojo::database::introspect::Introspect::>::layout(ref layout); } #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct( + dojo::database::introspect::Struct { name: 'Roles', attrs: array![].span(), children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'role_ids', - ty: dojo::database::schema::SchemaIntrospection::>::ty(), + ty: dojo::database::introspect::Introspect::>::ty(), attrs: array![].span() } ) @@ -355,13 +355,13 @@ mod roles { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -369,13 +369,13 @@ mod roles { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } @@ -417,7 +417,7 @@ impl PlayerModel of dojo::model::Model { #[inline(always)] fn layout(self: @Player) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -429,7 +429,7 @@ impl PlayerModel of dojo::model::Model { } -impl PlayerSchemaIntrospection of dojo::database::schema::SchemaIntrospection { +impl PlayerIntrospect of dojo::database::introspect::Introspect { #[inline(always)] fn size() -> usize { 1 @@ -441,30 +441,30 @@ impl PlayerSchemaIntrospection of dojo::database::schema::SchemaIntrospection dojo::database::schema::Ty { - dojo::database::schema::Ty::Struct( - dojo::database::schema::Struct { + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct( + dojo::database::introspect::Struct { name: 'Player', attrs: array![].span(), children: array![ - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'game', - ty: dojo::database::schema::Ty::Primitive('felt252'), + ty: dojo::database::introspect::Ty::Primitive('felt252'), attrs: array!['key'].span() } ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), + ty: dojo::database::introspect::Ty::Primitive('ContractAddress'), attrs: array!['key'].span() } ), - dojo::database::schema::serialize_member( - @dojo::database::schema::Member { + dojo::database::introspect::serialize_member( + @dojo::database::introspect::Member { name: 'name', - ty: dojo::database::schema::Ty::Primitive('felt252'), + ty: dojo::database::introspect::Ty::Primitive('felt252'), attrs: array![].span() } ) @@ -495,13 +495,13 @@ mod player { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -509,13 +509,13 @@ mod player { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } @@ -526,107 +526,107 @@ struct Roles { ^***^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:80:13 + --> test_src/lib.cairo[Position]:73:13 #[starknet::contract] ^*******************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:73:13 + --> test_src/lib.cairo[Roles]:69:13 #[starknet::contract] ^*******************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:87:13 + --> test_src/lib.cairo[Player]:77:13 #[starknet::contract] ^*******************^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:84:17 + --> test_src/lib.cairo[Position]:77:17 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:87:17 + --> test_src/lib.cairo[Position]:80:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:92:17 + --> test_src/lib.cairo[Position]:85:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:97:17 + --> test_src/lib.cairo[Position]:90:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:105:17 + --> test_src/lib.cairo[Position]:98:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Position]:112:17 + --> test_src/lib.cairo[Position]:105:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:77:17 + --> test_src/lib.cairo[Roles]:73:17 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:80:17 + --> test_src/lib.cairo[Roles]:76:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:85:17 + --> test_src/lib.cairo[Roles]:81:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:90:17 + --> test_src/lib.cairo[Roles]:86:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:98:17 + --> test_src/lib.cairo[Roles]:94:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Roles]:105:17 + --> test_src/lib.cairo[Roles]:101:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:91:17 + --> test_src/lib.cairo[Player]:81:17 #[storage] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:94:17 + --> test_src/lib.cairo[Player]:84:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:99:17 + --> test_src/lib.cairo[Player]:89:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:104:17 + --> test_src/lib.cairo[Player]:94:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:112:17 + --> test_src/lib.cairo[Player]:102:17 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[Player]:119:17 + --> test_src/lib.cairo[Player]:109:17 #[external(v0)] ^*************^ @@ -698,51 +698,41 @@ impl Vec3Serde of Serde:: { } } - impl Vec3SchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 3 - } +impl Vec3Introspect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 3 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(32); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(32); layout.append(32); layout.append(8); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Vec3', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'x', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'y', - ty: dojo::database::schema::Ty::Primitive('u32'), - attrs: array![].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'z', - ty: dojo::database::schema::Ty::Primitive('u8'), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'x', + ty: dojo::database::introspect::Ty::Primitive('u32'), + attrs: array![].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'y', + ty: dojo::database::introspect::Ty::Primitive('u32'), + attrs: array![].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'z', + ty: dojo::database::introspect::Ty::Primitive('u8'), + attrs: array![].span() + })].span() }) - } - } + } +} impl PositionCopy of Copy::; impl PositionDrop of Drop::; impl PositionSerde of Serde:: { @@ -781,7 +771,7 @@ impl PositionSerde of Serde:: { #[inline(always)] fn layout(self: @Position) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -793,42 +783,35 @@ impl PositionSerde of Serde:: { } - impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::::size() - } +impl PositionIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + dojo::database::introspect::Introspect::::size() + } - #[inline(always)] - fn layout(ref layout: Array) { - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::introspect::Introspect::::layout(ref layout); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Position', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'id', - ty: dojo::database::schema::Ty::Primitive('felt252'), - attrs: array!['key'].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'v', - ty: dojo::database::schema::SchemaIntrospection::::ty(), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'id', + ty: dojo::database::introspect::Ty::Primitive('felt252'), + attrs: array!['key'].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'v', + ty: dojo::database::introspect::Introspect::::ty(), + attrs: array![].span() + })].span() }) - } - } + } +} #[starknet::interface] @@ -850,13 +833,13 @@ impl PositionSerde of Serde:: { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -864,13 +847,13 @@ impl PositionSerde of Serde:: { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } impl RolesSerde of Serde:: { @@ -907,7 +890,7 @@ impl RolesSerde of Serde:: { #[inline(always)] fn layout(self: @Roles) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -919,35 +902,31 @@ impl RolesSerde of Serde:: { } - impl RolesSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - dojo::database::schema::SchemaIntrospection::>::size() - } +impl RolesIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + dojo::database::introspect::Introspect::>::size() + } - #[inline(always)] - fn layout(ref layout: Array) { - dojo::database::schema::SchemaIntrospection::>::layout(ref layout); + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::introspect::Introspect::>::layout(ref layout); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Roles', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'role_ids', - ty: dojo::database::schema::SchemaIntrospection::>::ty(), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'role_ids', + ty: dojo::database::introspect::Introspect::>::ty(), + attrs: array![].span() + })].span() }) - } - } + } +} #[starknet::interface] @@ -969,13 +948,13 @@ impl RolesSerde of Serde:: { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -983,13 +962,13 @@ impl RolesSerde of Serde:: { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } impl PlayerCopy of Copy::; @@ -1032,7 +1011,7 @@ impl PlayerSerde of Serde:: { #[inline(always)] fn layout(self: @Player) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } @@ -1044,49 +1023,39 @@ impl PlayerSerde of Serde:: { } - impl PlayerSchemaIntrospection of dojo::database::schema::SchemaIntrospection { - - #[inline(always)] - fn size() -> usize { - 1 - } +impl PlayerIntrospect<> of dojo::database::introspect::Introspect> { + #[inline(always)] + fn size() -> usize { + 1 + } - #[inline(always)] - fn layout(ref layout: Array) { - layout.append(251); + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(251); - } + } - #[inline(always)] - fn ty() -> dojo::database::schema::Ty { - - dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + dojo::database::introspect::Ty::Struct(dojo::database::introspect::Struct { name: 'Player', attrs: array![].span(), - children: array![ - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'game', - ty: dojo::database::schema::Ty::Primitive('felt252'), - attrs: array!['key'].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'player', - ty: dojo::database::schema::Ty::Primitive('ContractAddress'), - attrs: array!['key'].span() - }) -, - - dojo::database::schema::serialize_member(@dojo::database::schema::Member { - name: 'name', - ty: dojo::database::schema::Ty::Primitive('felt252'), - attrs: array![].span() - }) -].span() + children: array![dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'game', + ty: dojo::database::introspect::Ty::Primitive('felt252'), + attrs: array!['key'].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'player', + ty: dojo::database::introspect::Ty::Primitive('ContractAddress'), + attrs: array!['key'].span() + }), dojo::database::introspect::serialize_member(@dojo::database::introspect::Member { + name: 'name', + ty: dojo::database::introspect::Ty::Primitive('felt252'), + attrs: array![].span() + })].span() }) - } - } + } +} #[starknet::interface] @@ -1108,13 +1077,13 @@ impl PlayerSerde of Serde:: { #[external(v0)] fn unpacked_size(self: @ContractState) -> usize { - dojo::database::schema::SchemaIntrospection::::size() + dojo::database::introspect::Introspect::::size() } #[external(v0)] fn packed_size(self: @ContractState) -> usize { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) } @@ -1122,12 +1091,12 @@ impl PlayerSerde of Serde:: { #[external(v0)] fn layout(self: @ContractState) -> Span { let mut layout = ArrayTrait::new(); - dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::introspect::Introspect::::layout(ref layout); array::ArrayTrait::span(@layout) } #[external(v0)] - fn schema(self: @ContractState) -> dojo::database::schema::Ty { - dojo::database::schema::SchemaIntrospection::::ty() + fn schema(self: @ContractState) -> dojo::database::introspect::Ty { + dojo::database::introspect::Introspect::::ty() } } diff --git a/crates/dojo-lang/src/semantics/test_data/get b/crates/dojo-lang/src/semantics/test_data/get index d46f8f8659..da5f04e60e 100644 --- a/crates/dojo-lang/src/semantics/test_data/get +++ b/crates/dojo-lang/src/semantics/test_data/get @@ -199,7 +199,7 @@ Block( StatementExpr { expr: FunctionCall( ExprFunctionCall { - function: test::HealthSchemaIntrospection::layout, + function: test::HealthIntrospect::layout, args: [ Reference( LocalVarId(test::__Health_layout__), diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs index 6b4b5e1981..887310faf0 100644 --- a/crates/dojo-world/src/contracts/model_test.rs +++ b/crates/dojo-world/src/contracts/model_test.rs @@ -63,7 +63,7 @@ async fn test_model() { assert_eq!( position.class_hash(), FieldElement::from_hex_be( - "0x06a11b5b3003a3aa0ae7f8f443e48314cc0bc51eaea7c3ed1c19beb909f5dda3" + "0x052a1da1853c194683ca5d6d154452d0654d23f2eacd4267c555ff2338e144d6" ) .unwrap() ); diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index 59ad92bcaa..a1c39e0280 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,14 +3,15 @@ version = 1 [[package]] name = "dojo" -version = "0.3.11" +version = "0.3.12" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.11" +version = "0.3.12" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" [[package]] name = "types_test" From e6a870f21e05d1946d07d13912158f3c43d59783 Mon Sep 17 00:00:00 2001 From: GianMarco Date: Sat, 25 Nov 2023 21:12:36 -0500 Subject: [PATCH 028/192] Update Graphql test (#1204) * Basic migration * add primative data types and enum * fmt * Clean fn model_fixtures(), clean/refactor susbcription_tests --- crates/torii/graphql/src/tests/mod.rs | 83 ++++---- .../graphql/src/tests/subscription_test.rs | 181 +++++++++++------- 2 files changed, 141 insertions(+), 123 deletions(-) diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 2576c5227c..3a07eff19a 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -189,75 +189,56 @@ pub async fn run_graphql_subscription( pub async fn model_fixtures(db: &mut Sql) { db.register_model( Ty::Struct(Struct { - name: "Moves".to_string(), + name: "Record".to_string(), children: vec![ Member { - name: "player".to_string(), - key: true, - ty: Ty::Primitive(Primitive::ContractAddress(None)), - }, - Member { - name: "remaining".to_string(), - key: false, - ty: Ty::Primitive(Primitive::U8(None)), - }, - Member { - name: "last_direction".to_string(), + name: "depth".to_string(), key: false, ty: Ty::Enum(Enum { - name: "Direction".to_string(), + name: "Depth".to_string(), option: None, options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Left".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Right".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Up".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Down".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) }, ], }), }, - ], - }), - vec![], - FieldElement::ONE, - 0, - 0, - ) - .await - .unwrap(); - - db.register_model( - Ty::Struct(Struct { - name: "Position".to_string(), - children: vec![ Member { - name: "player".to_string(), + name: "record_id".to_string(), key: true, - ty: Ty::Primitive(Primitive::ContractAddress(None)), + ty: Ty::Primitive(Primitive::U32(None)), }, Member { - name: "vec".to_string(), + name: "type_u16".to_string(), key: false, - ty: Ty::Struct(Struct { - name: "Vec2".to_string(), - children: vec![ - Member { - name: "x".to_string(), - key: false, - ty: Ty::Primitive(Primitive::U32(None)), - }, - Member { - name: "y".to_string(), - key: false, - ty: Ty::Primitive(Primitive::U32(None)), - }, - ], - }), + ty: Ty::Primitive(Primitive::U16(None)), + }, + Member { + name: "type_u64".to_string(), + key: false, + ty: Ty::Primitive(Primitive::U64(None)), + }, + Member { + name: "type_bool".to_string(), + key: false, + ty: Ty::Primitive(Primitive::Bool(None)), + }, + Member { + name: "type_felt".to_string(), + key: false, + ty: Ty::Primitive(Primitive::Felt252(None)), + }, + Member { + name: "type_contract_address".to_string(), + key: true, + ty: Ty::Primitive(Primitive::ContractAddress(None)), }, ], }), vec![], - FieldElement::TWO, + FieldElement::ONE, 0, 0, ) diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index e126404119..4c48dc93ba 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -22,6 +22,7 @@ mod tests { model_fixtures(&mut db).await; // 0. Preprocess expected entity value + let model_name = "Record".to_string(); let key = vec![FieldElement::ONE]; let entity_id = format!("{:#x}", poseidon_hash_many(&key)); let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); @@ -29,11 +30,16 @@ mod tests { "entityUpdated": { "id": entity_id, "keys":vec![keys_str], - "model_names": "Moves", + "model_names": model_name, "models" : [{ - "player": format!("{:#x}", FieldElement::ONE), - "remaining": 10, - "last_direction": "Left" + "__typename": model_name, + "depth": "Zero", + "record_id": 0, + "type_u16": 1, + "type_u64": 1, + "type_bool": true, + "type_felt": format!("{:#x}", FieldElement::from(1u128)), + "type_contract_address": format!("{:#x}", FieldElement::ONE) }] } }); @@ -43,35 +49,54 @@ mod tests { // 1. Open process and sleep.Go to execute subscription tokio::time::sleep(Duration::from_secs(1)).await; - // Set entity with one moves model + // Set entity with one Record model db.set_entity( Ty::Struct(Struct { - name: "Moves".to_string(), + name: model_name, children: vec![ Member { - name: "player".to_string(), - key: true, - ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), + name: "depth".to_string(), + key: false, + ty: Ty::Enum(Enum { + name: "Depth".to_string(), + option: Some(0), + options: vec![ + EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) }, + ], + }), }, Member { - name: "remaining".to_string(), + name: "record_id".to_string(), key: false, - ty: Ty::Primitive(Primitive::U8(Some(10))), + ty: Ty::Primitive(Primitive::U8(Some(0))), }, Member { - name: "last_direction".to_string(), + name: "type_u16".to_string(), key: false, - ty: Ty::Enum(Enum { - name: "Direction".to_string(), - option: Some(1), - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Left".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Right".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Up".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Down".to_string(), ty: Ty::Tuple(vec![]) }, - ], - }), + ty: Ty::Primitive(Primitive::U16(Some(1))), + }, + Member { + name: "type_u64".to_string(), + key: false, + ty: Ty::Primitive(Primitive::U64(Some(1))), + }, + Member { + name: "type_bool".to_string(), + key: false, + ty: Ty::Primitive(Primitive::Bool(Some(true))), + }, + Member { + name: "type_felt".to_string(), + key: false, + ty: Ty::Primitive(Primitive::Felt252(Some(FieldElement::from(1u128)))), + }, + Member { + name: "type_contract_address".to_string(), + key: true, + ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), }, ], }), @@ -79,12 +104,11 @@ mod tests { ) .await .unwrap(); - // 3. fn publish() is called from state.set_entity() tx.send(()).await.unwrap(); }); - // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + // 2. The subscription is executed and it is listening, waiting for publish() to be executed let response_value = run_graphql_subscription( &pool, r#"subscription { @@ -93,17 +117,22 @@ mod tests { keys model_names models { - ... on Moves { - player - remaining - last_direction + __typename + ... on Record { + depth + record_id + type_u16 + type_u64 + type_bool + type_felt + type_contract_address } } } }"#, ) .await; - // 4. The subcription has received the message from publish() + // 4. The subscription has received the message from publish() // 5. Compare values assert_eq!(expected_value, response_value); rx.recv().await.unwrap(); @@ -116,6 +145,7 @@ mod tests { model_fixtures(&mut db).await; // 0. Preprocess expected entity value + let model_name = "Record".to_string(); let key = vec![FieldElement::ONE]; let entity_id = format!("{:#x}", poseidon_hash_many(&key)); let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); @@ -123,11 +153,13 @@ mod tests { "entityUpdated": { "id": entity_id, "keys":vec![keys_str], - "model_names": "Moves", + "model_names": model_name, "models" : [{ - "player": format!("{:#x}", FieldElement::ONE), - "remaining": 10, - "last_direction": "Left" + "__typename": model_name, + "depth": "Zero", + "record_id": 0, + "type_felt": format!("{:#x}", FieldElement::from(1u128)), + "type_contract_address": format!("{:#x}", FieldElement::ONE) }] } }); @@ -137,35 +169,39 @@ mod tests { // 1. Open process and sleep.Go to execute subscription tokio::time::sleep(Duration::from_secs(1)).await; - // Set entity with one moves model + // Set entity with one Record model db.set_entity( Ty::Struct(Struct { - name: "Moves".to_string(), + name: model_name, children: vec![ Member { - name: "player".to_string(), - key: true, - ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), + name: "depth".to_string(), + key: false, + ty: Ty::Enum(Enum { + name: "Depth".to_string(), + option: Some(0), + options: vec![ + EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) }, + ], + }), }, Member { - name: "remaining".to_string(), + name: "record_id".to_string(), key: false, - ty: Ty::Primitive(Primitive::U8(Some(10))), + ty: Ty::Primitive(Primitive::U32(Some(0))), }, Member { - name: "last_direction".to_string(), + name: "type_felt".to_string(), key: false, - ty: Ty::Enum(Enum { - name: "Direction".to_string(), - option: Some(1), - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Left".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Right".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Up".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Down".to_string(), ty: Ty::Tuple(vec![]) }, - ], - }), + ty: Ty::Primitive(Primitive::Felt252(Some(FieldElement::from(1u128)))), + }, + Member { + name: "type_contract_address".to_string(), + key: true, + ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), }, ], }), @@ -173,24 +209,25 @@ mod tests { ) .await .unwrap(); - // 3. fn publish() is called from state.set_entity() tx.send(()).await.unwrap(); }); - // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + // 2. The subscription is executed and it is listening, waiting for publish() to be executed let response_value = run_graphql_subscription( &pool, r#"subscription { entityUpdated(id: "0x579e8877c7755365d5ec1ec7d3a94a457eff5d1f40482bbe9729c064cdead2") { - id + id keys model_names models { - ... on Moves { - player - remaining - last_direction + __typename + ... on Record { + depth + record_id + type_felt + type_contract_address } } } @@ -208,11 +245,11 @@ mod tests { async fn test_model_subscription(pool: SqlitePool) { let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); // 0. Preprocess model value - let name = "Moves".to_string(); - let model_id = name.clone(); + let model_name = "Subrecord".to_string(); + let model_id = model_name.clone(); let class_hash = FieldElement::TWO; let expected_value: async_graphql::Value = value!({ - "modelRegistered": { "id": model_id, "name":name } + "modelRegistered": { "id": model_id, "name":model_name } }); let (tx, mut rx) = mpsc::channel(7); @@ -221,11 +258,11 @@ mod tests { tokio::time::sleep(Duration::from_secs(1)).await; let model = Ty::Struct(Struct { - name: "Moves".to_string(), + name: model_name, children: vec![Member { - name: "player".to_string(), + name: "subrecord_id".to_string(), key: true, - ty: Ty::Primitive(Primitive::ContractAddress(None)), + ty: Ty::Primitive(Primitive::U32(None)), }], }); db.register_model(model, vec![], class_hash, 0, 0).await.unwrap(); @@ -257,11 +294,11 @@ mod tests { async fn test_model_subscription_with_id(pool: SqlitePool) { let mut db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); // 0. Preprocess model value - let name = "Test".to_string(); - let model_id = name.clone(); + let model_name = "Subrecord".to_string(); + let model_id = model_name.clone(); let class_hash = FieldElement::TWO; let expected_value: async_graphql::Value = value!({ - "modelRegistered": { "id": model_id, "name":name } + "modelRegistered": { "id": model_id, "name":model_name } }); let (tx, mut rx) = mpsc::channel(7); @@ -270,9 +307,9 @@ mod tests { tokio::time::sleep(Duration::from_secs(1)).await; let model = Ty::Struct(Struct { - name: "Test".to_string(), + name: model_name, children: vec![Member { - name: "test".into(), + name: "type_u8".into(), key: false, ty: Ty::Primitive(Primitive::U8(None)), }], @@ -288,7 +325,7 @@ mod tests { &pool, r#" subscription { - modelRegistered(id: "Test") { + modelRegistered(id: "Subrecord") { id, name } }"#, From ed216202c1649047d7b88e63205f86f77b04cc43 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Sun, 26 Nov 2023 13:45:24 -0500 Subject: [PATCH 029/192] Fix artifact path in release script --- .github/workflows/release.yml | 33 ++++++++++++++++++++------------- Dockerfile | 14 ++++++++------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df726fb4bd..dd107291a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,12 +93,12 @@ jobs: platform: linux target: x86_64-unknown-linux-gnu arch: amd64 - - os: ubuntu-latest-4-cores + - os: ubuntu-latest-8-cores platform: linux target: aarch64-unknown-linux-gnu arch: arm64 svm_target_platform: linux-aarch64 - - os: macos-latest + - os: macos-latest-xlarge platform: darwin target: x86_64-apple-darwin arch: amd64 @@ -124,9 +124,10 @@ jobs: with: cache-on-failure: true - - uses: arduino/setup-protoc@v1 + - uses: arduino/setup-protoc@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + version: "25.x" - name: Apple M1 setup if: ${{ matrix.job.target == 'aarch64-apple-darwin' }} @@ -191,10 +192,10 @@ jobs: body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} - + # Upload these for use with the Docker build later - name: Upload binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: binaries path: | @@ -241,10 +242,10 @@ jobs: uses: actions/checkout@v2 - name: Download binaries - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: binaries - path: target/ + path: artifacts - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -259,12 +260,15 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v4 with: + no-cache: true push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} platforms: linux/arm64 target: arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + build-contexts: | + artifacts=artifacts + # cache-from: type=gha + # cache-to: type=gha,mode=max docker-build-and-push-linux-amd64: name: Build and push linux-amd64 docker image @@ -276,10 +280,10 @@ jobs: uses: actions/checkout@v2 - name: Download binaries - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: binaries - path: target/ + path: artifacts - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -294,9 +298,12 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v4 with: + build-contexts: | + artifacts=artifacts + no-cache: true push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} platforms: linux/amd64 target: amd64 - cache-from: type=gha - cache-to: type=gha,mode=max + # cache-from: type=gha + # cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 685091562d..f936de7016 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,13 @@ LABEL description="Dojo is a provable game engine and toolchain for building onc documentation="https://book.dojoengine.org/" FROM base as amd64 -COPY target/x86_64-unknown-linux-gnu/release/katana /usr/local/bin/katana -COPY target/x86_64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo -COPY target/x86_64-unknown-linux-gnu/release/torii /usr/local/bin/torii + +COPY --from=artifacts x86_64-unknown-linux-gnu/release/katana /usr/local/bin/katana +COPY --from=artifacts x86_64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo +COPY --from=artifacts x86_64-unknown-linux-gnu/release/torii /usr/local/bin/torii FROM base as arm64 -COPY target/aarch64-unknown-linux-gnu/release/katana /usr/local/bin/katana -COPY target/aarch64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo -COPY target/aarch64-unknown-linux-gnu/release/torii /usr/local/bin/torii + +COPY --from=artifacts aarch64-unknown-linux-gnu/release/katana /usr/local/bin/katana +COPY --from=artifacts aarch64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo +COPY --from=artifacts aarch64-unknown-linux-gnu/release/torii /usr/local/bin/torii From c45c5a9fac6300a4166af92259c0471f0c615cca Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Sun, 26 Nov 2023 19:24:17 -0800 Subject: [PATCH 030/192] Hardcode plugin version (#1208) --- .github/workflows/release.yml | 4 ++++ crates/dojo-core/Scarb.lock | 4 ++-- crates/dojo-core/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- crates/dojo-lang/build.rs | 13 ------------- crates/dojo-lang/src/plugin.rs | 4 ++-- crates/dojo-test-utils/build.rs | 10 ---------- examples/spawn-and-move/Scarb.lock | 4 ++-- 8 files changed, 12 insertions(+), 31 deletions(-) delete mode 100644 crates/dojo-lang/build.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd107291a3..9fb0a9e7cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -285,6 +285,10 @@ jobs: name: binaries path: artifacts + - name: Display structure of downloaded files + run: ls -R + working-directory: artifacts + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 13286a04f2..72531bfc18 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -10,5 +10,5 @@ dependencies = [ [[package]] name = "dojo_plugin" -version = "0.3.12" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index d1f9ec3818..e80569a45b 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -5,5 +5,5 @@ name = "dojo" version = "0.3.12" [dependencies] -dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.12" } +dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } starknet = "2.3.1" diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 0f7e1b1532..d6108e0735 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.12" +version = "0.3.11" [cairo-plugin] diff --git a/crates/dojo-lang/build.rs b/crates/dojo-lang/build.rs deleted file mode 100644 index 109f547faf..0000000000 --- a/crates/dojo-lang/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::env; -use std::process::Command; - -fn main() { - let version = env!("CARGO_PKG_VERSION"); - let output = Command::new("git") - .args(["rev-list", "-n", "1", &format!("v{version}")]) - .output() - .expect("Failed to execute command"); - - let git_hash = String::from_utf8(output.stdout).unwrap().trim().to_string(); - println!("cargo:rustc-env=GIT_HASH={}", git_hash); -} diff --git a/crates/dojo-lang/src/plugin.rs b/crates/dojo-lang/src/plugin.rs index 8ade21a985..0c350b829d 100644 --- a/crates/dojo-lang/src/plugin.rs +++ b/crates/dojo-lang/src/plugin.rs @@ -188,8 +188,8 @@ impl BuiltinDojoPlugin { impl CairoPlugin for BuiltinDojoPlugin { fn id(&self) -> PackageId { let url = Url::parse("https://github.com/dojoengine/dojo").unwrap(); - let version = env!("CARGO_PKG_VERSION"); - let rev = env!("GIT_HASH"); + let version = "0.3.11"; + let rev = "1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659"; let source_id = SourceId::for_git(&url, &scarb::core::GitReference::Tag(format!("v{version}").into())) diff --git a/crates/dojo-test-utils/build.rs b/crates/dojo-test-utils/build.rs index 7160be1859..64b0e8db1f 100644 --- a/crates/dojo-test-utils/build.rs +++ b/crates/dojo-test-utils/build.rs @@ -1,7 +1,6 @@ #[cfg(feature = "build-examples")] fn main() { use std::env; - use std::process::Command; use camino::{Utf8Path, Utf8PathBuf}; use dojo_lang::compiler::DojoCompiler; @@ -11,15 +10,6 @@ fn main() { use scarb::ops::{self, CompileOpts}; use scarb_ui::Verbosity; - let version = env!("CARGO_PKG_VERSION"); - let output = Command::new("git") - .args(["rev-list", "-n", "1", &format!("v{version}")]) - .output() - .expect("Failed to execute command"); - - let git_hash = String::from_utf8(output.stdout).unwrap().trim().to_string(); - println!("cargo:rustc-env=GIT_HASH={}", git_hash); - let project_paths = ["../../examples/spawn-and-move", "../torii/graphql/src/tests/types-test"]; project_paths.iter().for_each(|path| compile(path)); diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 40830134bd..24a4548ad3 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -17,5 +17,5 @@ dependencies = [ [[package]] name = "dojo_plugin" -version = "0.3.12" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" From 7b72792ef7b3c46f573341cd79a46c460517a2e7 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 27 Nov 2023 05:32:01 -0800 Subject: [PATCH 031/192] Graphql `where` input type check (#1209) --- .../graphql/src/object/inputs/where_input.rs | 93 ++++++++++++------- crates/torii/graphql/src/object/model_data.rs | 2 +- crates/torii/graphql/src/query/filter.rs | 4 +- crates/torii/graphql/src/tests/models_test.rs | 5 +- 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/crates/torii/graphql/src/object/inputs/where_input.rs b/crates/torii/graphql/src/object/inputs/where_input.rs index ba0e5d7522..b7acfe7e6d 100644 --- a/crates/torii/graphql/src/object/inputs/where_input.rs +++ b/crates/torii/graphql/src/object/inputs/where_input.rs @@ -1,7 +1,9 @@ use std::str::FromStr; -use async_graphql::dynamic::{Field, InputObject, InputValue, ResolverContext, TypeRef}; -use async_graphql::Name; +use async_graphql::dynamic::{ + Field, InputObject, InputValue, ResolverContext, TypeRef, ValueAccessor, +}; +use async_graphql::{Error as GqlError, Name, Result}; use dojo_types::primitive::{Primitive, SqlType}; use strum::IntoEnumIterator; @@ -21,26 +23,25 @@ impl WhereInputObject { pub fn new(type_name: &str, object_types: &TypeMapping) -> Self { let where_mapping = object_types .iter() - .filter_map(|(type_name, type_data)| { + .filter(|(_, type_data)| !type_data.is_nested()) + .flat_map(|(type_name, type_data)| { // TODO: filter on nested and enum objects - if type_data.is_nested() { - return None; - } else if type_data.type_ref() == TypeRef::named("Enum") { - return Some(vec![(Name::new(type_name), type_data.clone())]); + if type_data.type_ref() == TypeRef::named("Enum") + || type_data.type_ref() == TypeRef::named("bool") + { + return vec![(Name::new(type_name), type_data.clone())]; } - let mut comparators = Comparator::iter() - .map(|comparator| { + Comparator::iter().fold( + vec![(Name::new(type_name), type_data.clone())], + |mut acc, comparator| { let name = format!("{}{}", type_name, comparator.as_ref()); - (Name::new(name), type_data.clone()) - }) - .collect::>(); - - comparators.push((Name::new(type_name), type_data.clone())); + acc.push((Name::new(name), type_data.clone())); - Some(comparators) + acc + }, + ) }) - .flatten() .collect(); Self { type_name: format!("{}WhereInput", type_name), type_mapping: where_mapping } @@ -70,26 +71,46 @@ pub fn where_argument(field: Field, type_name: &str) -> Field { pub fn parse_where_argument( ctx: &ResolverContext<'_>, where_mapping: &TypeMapping, -) -> Option> { - let where_input = ctx.args.get("where")?; - let input_object = where_input.object().ok()?; - - where_mapping - .iter() - .filter_map(|(type_name, type_data)| { - input_object.get(type_name).map(|input_filter| { - let filter_value = match Primitive::from_str(&type_data.type_ref().to_string()) { - Ok(primitive) => match primitive.to_sql_type() { - SqlType::Integer => FilterValue::Int(input_filter.i64().ok()?), - SqlType::Text => { - FilterValue::String(input_filter.string().ok()?.to_string()) - } - }, - _ => FilterValue::String(input_filter.string().ok()?.to_string()), - }; +) -> Result>> { + ctx.args.get("where").map_or(Ok(None), |where_input| { + let input_object = where_input.object()?; + where_mapping + .iter() + .filter_map(|(type_name, type_data)| { + input_object.get(type_name).map(|input| { + let primitive = Primitive::from_str(&type_data.type_ref().to_string())?; + let filter_value = match primitive.to_sql_type() { + SqlType::Integer => parse_integer(input, type_name, primitive)?, + SqlType::Text => parse_string(input, type_name)?, + }; - Some(parse_filter(type_name, filter_value)) + Ok(Some(parse_filter(type_name, filter_value))) + }) }) - }) - .collect::>>() + .collect::>>>() + }) +} + +fn parse_integer( + input: ValueAccessor<'_>, + type_name: &str, + primitive: Primitive, +) -> Result { + match primitive { + Primitive::Bool(_) => input + .boolean() + .map(|b| FilterValue::Int(b as i64)) // treat bool as int per sqlite + .map_err(|_| GqlError::new(format!("Expected boolean on field {}", type_name))), + _ => input + .i64() + .map(FilterValue::Int) + .map_err(|_| GqlError::new(format!("Expected integer on field {}", type_name))), + } +} + +fn parse_string(input: ValueAccessor<'_>, type_name: &str) -> Result { + input + .string() + .map(|i| FilterValue::String(i.to_string())) + .map_err(|_| GqlError::new(format!("Expected string on field {}", type_name))) } diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index aadd7ec784..642dfc7e2d 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -86,7 +86,7 @@ impl ObjectTrait for ModelDataObject { FieldFuture::new(async move { let mut conn = ctx.data::>()?.acquire().await?; let order = parse_order_argument(&ctx); - let filters = parse_where_argument(&ctx, &where_mapping); + let filters = parse_where_argument(&ctx, &where_mapping)?; let connection = parse_connection_arguments(&ctx)?; let id_column = "event_id"; diff --git a/crates/torii/graphql/src/query/filter.rs b/crates/torii/graphql/src/query/filter.rs index 2d3da160c6..dd6aef3f2e 100644 --- a/crates/torii/graphql/src/query/filter.rs +++ b/crates/torii/graphql/src/query/filter.rs @@ -2,7 +2,7 @@ use core::fmt; use async_graphql::Name; use strum::IntoEnumIterator; -use strum_macros::{AsRefStr, EnumIter}; +use strum_macros::{AsRefStr, Display, EnumIter}; #[derive(AsRefStr, Debug, Clone, PartialEq, EnumIter)] #[strum(serialize_all = "UPPERCASE")] @@ -28,7 +28,7 @@ impl fmt::Display for Comparator { } } -#[derive(Debug)] +#[derive(Debug, Display)] pub enum FilterValue { Int(i64), String(String), diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index c0942937bc..0995bc1d40 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -169,15 +169,14 @@ mod tests { assert_eq!(last_record.node.type_u256, "0x0"); // where filter on true bool - // TODO: use bool values on input instead of 0 or 1 - let records = records_model_query(&schema, "(where: { type_bool: 1 })").await; + let records = records_model_query(&schema, "(where: { type_bool: true })").await; let connection: Connection = serde_json::from_value(records).unwrap(); let first_record = connection.edges.first().unwrap(); assert_eq!(connection.total_count, 5); assert!(first_record.node.type_bool, "should be true"); // where filter on false bool - let records = records_model_query(&schema, "(where: { type_bool: 0 })").await; + let records = records_model_query(&schema, "(where: { type_bool: false })").await; let connection: Connection = serde_json::from_value(records).unwrap(); let first_record = connection.edges.first().unwrap(); assert_eq!(connection.total_count, 5); From cb91c21a44cab310f860beb5da142b6d8df865d7 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 27 Nov 2023 08:33:40 -0500 Subject: [PATCH 032/192] Cleanup release.yml --- .github/workflows/release.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9fb0a9e7cc..f79fe64254 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -267,8 +267,6 @@ jobs: target: arm64 build-contexts: | artifacts=artifacts - # cache-from: type=gha - # cache-to: type=gha,mode=max docker-build-and-push-linux-amd64: name: Build and push linux-amd64 docker image @@ -285,10 +283,6 @@ jobs: name: binaries path: artifacts - - name: Display structure of downloaded files - run: ls -R - working-directory: artifacts - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -309,5 +303,3 @@ jobs: tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} platforms: linux/amd64 target: amd64 - # cache-from: type=gha - # cache-to: type=gha,mode=max From a253b0c8119f5cc8b20f2f2893a20ae3ef79951a Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 27 Nov 2023 08:34:22 -0500 Subject: [PATCH 033/192] Prepare v0.3.13 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5157272aa..e83a521a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2265,7 +2265,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2312,7 +2312,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2334,7 +2334,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "starknet", @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "assert_fs", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.12" +version = "0.3.13" dependencies = [ "crypto-bigint", "hex", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "assert_fs", @@ -4816,7 +4816,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.12" +version = "0.3.13" dependencies = [ "assert_matches", "clap", @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "assert_matches", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "assert_matches", @@ -7236,7 +7236,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "assert_fs", @@ -8267,7 +8267,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.12" +version = "0.3.13" dependencies = [ "async-trait", "camino", @@ -8293,7 +8293,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "async-trait", @@ -8329,7 +8329,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "async-graphql", @@ -8367,7 +8367,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.12" +version = "0.3.13" dependencies = [ "bytes", "dojo-types", @@ -8404,7 +8404,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.12" +version = "0.3.13" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 957b0458b5..ecb68c435d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.12" +version = "0.3.13" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index e80569a45b..a53a2aec80 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.12" +version = "0.3.13" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 3acc1969d9..c5d4cd4175 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.12" +version = "0.3.13" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 01191e1edb..1e76728e61 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.12" +version = "0.3.13" [cairo] sierra-replace-ids = true From bbb932fa87aa09c28e4b6784ec7122c9ca105b85 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:56:46 +0100 Subject: [PATCH 034/192] make world upgradeable (#1210) * make world upgradeable * update records_contract address --- crates/dojo-core/src/world.cairo | 39 ++++++- crates/dojo-core/src/world_test.cairo | 109 +++++++++++++----- .../dojo-lang/src/manifest_test_data/manifest | 42 ++++++- crates/torii/graphql/src/tests/mod.rs | 2 +- 4 files changed, 156 insertions(+), 36 deletions(-) diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index 53094323c8..1271a3a621 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -45,6 +45,11 @@ trait IWorld { fn revoke_writer(ref self: T, model: felt252, system: ContractAddress); } +#[starknet::interface] +trait IUpgradeableWorld { + fn upgrade(ref self: T, new_class_hash : ClassHash); +} + #[starknet::interface] trait IWorldProvider { fn world(self: @T) -> IWorldDispatcher; @@ -65,14 +70,14 @@ 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}, SyscallResult, SyscallResultTrait, + 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}; + use dojo::world::{IWorldDispatcher, IWorld, IUpgradeableWorld}; use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; @@ -87,6 +92,7 @@ mod world { WorldSpawned: WorldSpawned, ContractDeployed: ContractDeployed, ContractUpgraded: ContractUpgraded, + WorldUpgraded: WorldUpgraded, MetadataUpdate: MetadataUpdate, ModelRegistered: ModelRegistered, StoreSetRecord: StoreSetRecord, @@ -102,6 +108,11 @@ mod world { creator: ContractAddress } + #[derive(Drop, starknet::Event)] + struct WorldUpgraded { + class_hash: ClassHash, + } + #[derive(Drop, starknet::Event)] struct ContractDeployed { salt: felt252, @@ -616,12 +627,34 @@ mod world { /// /// # Returns /// - /// * `ContractAddress` - The address of the contract_base contract. + /// * `ClassHash` - The class_hash of the contract_base contract. fn base(self: @ContractState) -> ClassHash { self.contract_base.read() } } + + #[external(v0)] + impl UpgradeableWorld of IUpgradeableWorld { + /// Upgrade world with new_class_hash + /// + /// # Arguments + /// + /// * `new_class_hash` - The new world class hash. + 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'); + + // 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 } + ); + } + } + /// Asserts that the current caller can write to the model. /// /// # Arguments diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index de6ced047f..22cb3c372d 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -9,7 +9,7 @@ use starknet::syscalls::deploy_syscall; use dojo::benchmarks; use dojo::executor::executor; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world}; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world, IUpgradeableWorld, IUpgradeableWorldDispatcher, IUpgradeableWorldDispatcherTrait }; use dojo::database::introspect::Introspect; use dojo::test_utils::{spawn_test_world, deploy_with_world_address}; use dojo::benchmarks::{Character, end}; @@ -405,36 +405,6 @@ fn test_set_writer_fails_for_non_owner() { } -#[starknet::interface] -trait IOrigin { - fn assert_origin(self: @TContractState); -} - -#[starknet::contract] -mod origin { - use super::{IWorldDispatcher, ContractAddress}; - - #[storage] - struct Storage { - world: IWorldDispatcher, - } - - #[constructor] - fn constructor(ref self: ContractState, world: ContractAddress) { - self.world.write(IWorldDispatcher { contract_address: world }) - } - - #[external(v0)] - impl IOriginImpl of super::IOrigin { - fn assert_origin(self: @ContractState) { - assert( - starknet::get_caller_address() == starknet::contract_address_const::<0x1337>(), - 'should be equal' - ); - } - } -} - #[test] #[available_gas(60000000)] fn test_execute_multiple_worlds() { @@ -511,3 +481,80 @@ fn bench_execute_complex() { assert(data.heigth == 1337, 'data not stored'); } + + +#[starknet::interface] +trait IWorldUpgrade { + fn hello(self: @TContractState) -> felt252; +} + +#[starknet::contract] +mod worldupgrade { + use super::{IWorldUpgrade, IWorldDispatcher, ContractAddress}; + + #[storage] + struct Storage { + world: IWorldDispatcher, + } + + #[external(v0)] + impl IWorldUpgradeImpl of super::IWorldUpgrade { + fn hello(self: @ContractState) -> felt252{ + 'dojo' + } + } +} + + +#[test] +#[available_gas(60000000)] +fn test_upgradeable_world() { + + // Deploy world contract + let world = deploy_world(); + + let mut upgradeable_world_dispatcher = IUpgradeableWorldDispatcher { + contract_address: world.contract_address + }; + upgradeable_world_dispatcher.upgrade(worldupgrade::TEST_CLASS_HASH.try_into().unwrap()); + + let res = (IWorldUpgradeDispatcher { + contract_address: world.contract_address + }).hello(); + + assert(res == 'dojo', 'should return dojo'); +} + +#[test] +#[available_gas(60000000)] +#[should_panic(expected:('invalid class_hash', 'ENTRYPOINT_FAILED'))] +fn test_upgradeable_world_with_class_hash_zero() { + + // Deploy world contract + let world = deploy_world(); + + starknet::testing::set_contract_address(starknet::contract_address_const::<0x1337>()); + + let mut upgradeable_world_dispatcher = IUpgradeableWorldDispatcher { + contract_address: world.contract_address + }; + upgradeable_world_dispatcher.upgrade(0.try_into().unwrap()); +} + +#[test] +#[available_gas(60000000)] +#[should_panic( expected: ('only owner can upgrade', 'ENTRYPOINT_FAILED'))] +fn test_upgradeable_world_from_non_owner() { + + // Deploy world contract + let world = deploy_world(); + + let not_owner = starknet::contract_address_const::<0x1337>(); + starknet::testing::set_contract_address(not_owner); + starknet::testing::set_account_contract_address(not_owner); + + let mut upgradeable_world_dispatcher = IUpgradeableWorldDispatcher { + contract_address: world.contract_address + }; + upgradeable_world_dispatcher.upgrade(worldupgrade::TEST_CLASS_HASH.try_into().unwrap()); +} \ No newline at end of file diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 393e808895..587db37a3a 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -8,7 +8,7 @@ test_manifest_file "world": { "name": "world", "address": null, - "class_hash": "0xb3e374b8087dca92601afbb9881fed855ac0d568e3bf878a876fca5ffcb479", + "class_hash": "0x99b08b2ff33750916e36b5e241b5d4a63e8d48862bf90a68fec2ff58a8de6", "abi": [ { "type": "impl", @@ -472,6 +472,29 @@ test_manifest_file } ] }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, { "type": "constructor", "name": "constructor", @@ -542,6 +565,18 @@ test_manifest_file } ] }, + { + "type": "event", + "name": "dojo::world::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::MetadataUpdate", @@ -706,6 +741,11 @@ test_manifest_file "type": "dojo::world::world::ContractUpgraded", "kind": "nested" }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world::WorldUpgraded", + "kind": "nested" + }, { "name": "MetadataUpdate", "type": "dojo::world::world::MetadataUpdate", diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 3a07eff19a..72c32b7902 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -270,7 +270,7 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); // Execute `create` and insert 10 records into storage - let records_contract = "0x27f701de7d71a2a6ee670bc1ff47a901fdc671cca26fe234ca1a42273aa7f7d"; + let records_contract = "0x2e6254aaf7e47502319f35de01376cece263f9b83afe6169a4b3a76ef47c8a3"; let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], From 84fbcb700b4e1fb7f4d39d53e31a33de8a70fef9 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 27 Nov 2023 08:46:49 -0800 Subject: [PATCH 035/192] Rename grpc attribute clause to member (#1211) --- crates/torii/grpc/proto/types.proto | 4 ++-- crates/torii/grpc/src/server/mod.rs | 4 ++-- crates/torii/grpc/src/types.rs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index 80d9ae8da6..873cabd4b7 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -94,7 +94,7 @@ message EntityQuery { message Clause { oneof clause_type { KeysClause keys = 1; - AttributeClause attribute = 2; + MemberClause member = 2; CompositeClause composite = 3; } } @@ -104,7 +104,7 @@ message KeysClause { repeated bytes keys = 2; } -message AttributeClause { +message MemberClause { string model = 1; string member = 2; ComparisonOperator operator = 3; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 8b7d484817..398343b9c7 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -165,7 +165,7 @@ impl DojoWorld { async fn entities_by_attribute( &self, - _attribute: proto::types::AttributeClause, + _attribute: proto::types::MemberClause, _limit: u32, _offset: u32, ) -> Result, Error> { @@ -249,7 +249,7 @@ impl DojoWorld { ClauseType::Keys(keys) => { self.entities_by_keys(keys, query.limit, query.offset).await? } - ClauseType::Attribute(attribute) => { + ClauseType::Member(attribute) => { self.entities_by_attribute(attribute, query.limit, query.offset).await? } ClauseType::Composite(composite) => { diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types.rs index 00efe50358..c2c834cf42 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types.rs @@ -20,7 +20,7 @@ pub struct Query { #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub enum Clause { Keys(KeysClause), - Attribute(AttributeClause), + Member(MemberClause), Composite(CompositeClause), } @@ -31,7 +31,7 @@ pub struct KeysClause { } #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct AttributeClause { +pub struct MemberClause { pub model: String, pub member: String, pub operator: ComparisonOperator, @@ -117,9 +117,9 @@ impl From for proto::types::Clause { Clause::Keys(clause) => { Self { clause_type: Some(proto::types::clause::ClauseType::Keys(clause.into())) } } - Clause::Attribute(clause) => Self { - clause_type: Some(proto::types::clause::ClauseType::Attribute(clause.into())), - }, + Clause::Member(clause) => { + Self { clause_type: Some(proto::types::clause::ClauseType::Member(clause.into())) } + } Clause::Composite(clause) => Self { clause_type: Some(proto::types::clause::ClauseType::Composite(clause.into())), }, @@ -150,8 +150,8 @@ impl TryFrom for KeysClause { } } -impl From for proto::types::AttributeClause { - fn from(value: AttributeClause) -> Self { +impl From for proto::types::MemberClause { + fn from(value: MemberClause) -> Self { Self { model: value.model, member: value.member, From 8c2f0d940460af7743cb4741306ceff559072729 Mon Sep 17 00:00:00 2001 From: Larko <59736843+Larkooo@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:31:59 -0500 Subject: [PATCH 036/192] refactor(spawn-and-move): up to date readme (#1212) --- examples/spawn-and-move/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/spawn-and-move/README.md b/examples/spawn-and-move/README.md index 2571aeb003..75e0ff346f 100644 --- a/examples/spawn-and-move/README.md +++ b/examples/spawn-and-move/README.md @@ -12,7 +12,7 @@ sozo build sozo migrate # Get the class hash of the Moves model by name -sozo model get --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves +sozo model class-hash --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves > 0x2b97f0b24be59ecf4504a27ac2301179be7df44c4c7d9482cd7b36137bc0fa4 # Get the schema of the Moves model @@ -24,14 +24,14 @@ sozo model schema --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219f # Get the value of the Moves model for an entity. (in this example, # 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 is # the calling account. -sozo model entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 +sozo model get --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 > 0x0 -# The returned value is 0 since we haven't spawned yet. Let's spawn -# a player for the caller -sozo execute --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 spawn +# The returned value is 0 since we haven't spawned yet. +# We can spawn a player using the actions contract address +sozo execute 0x31571485922572446df9e3198a891e10d3a48e544544317dbcbb667e15848cd spawn # Fetch the updated entity -sozo model entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 +sozo model get --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 > 0xa ``` From 09115ef0c6fe82ac34dedc1dfbefb6623df88f60 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 27 Nov 2023 20:06:39 -0800 Subject: [PATCH 037/192] Refactor torii core add entity_model table (#1213) --- crates/torii/core/src/sql.rs | 24 ++++------- crates/torii/core/src/types.rs | 1 - crates/torii/graphql/src/mapping.rs | 1 - crates/torii/graphql/src/object/entity.rs | 14 +++---- .../torii/graphql/src/tests/entities_test.rs | 6 --- crates/torii/graphql/src/tests/mod.rs | 1 - crates/torii/graphql/src/tests/models_test.rs | 2 - .../graphql/src/tests/subscription_test.rs | 4 -- .../graphql/src/tests/types-test/Scarb.lock | 6 +-- .../20231127235011_entity_model.sql | 40 +++++++++++++++++++ 10 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 crates/torii/migrations/20231127235011_entity_model.sql diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index f62c977d16..b56ae45e84 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -123,29 +123,19 @@ impl Sql { }; let entity_id = format!("{:#x}", poseidon_hash_many(&keys)); - let existing: Option<(String,)> = - sqlx::query_as("SELECT model_names FROM entities WHERE id = ?") - .bind(&entity_id) - .fetch_optional(&self.pool) - .await?; - - let model_names = match existing { - Some((existing_names,)) if existing_names.contains(&entity.name()) => { - existing_names.to_string() - } - Some((existing_names,)) => format!("{},{}", existing_names, entity.name()), - None => entity.name().to_string(), - }; + self.query_queue.enqueue( + "INSERT INTO entity_model (entity_id, model_id) VALUES (?, ?) ON CONFLICT(entity_id, \ + model_id) DO NOTHING", + vec![Argument::String(entity_id.clone()), Argument::String(entity.name())], + ); let keys_str = felts_sql_string(&keys); - let insert_entities = "INSERT INTO entities (id, keys, model_names, event_id) VALUES (?, \ - ?, ?, ?) ON CONFLICT(id) DO UPDATE SET \ - model_names=EXCLUDED.model_names, updated_at=CURRENT_TIMESTAMP, \ + let insert_entities = "INSERT INTO entities (id, keys, event_id) VALUES (?, ?, ?) ON \ + CONFLICT(id) DO UPDATE SET updated_at=CURRENT_TIMESTAMP, \ event_id=EXCLUDED.event_id RETURNING *"; let entity_updated: EntityUpdated = sqlx::query_as(insert_entities) .bind(&entity_id) .bind(&keys_str) - .bind(&model_names) .bind(event_id) .fetch_one(&self.pool) .await?; diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index 41383eef2c..cfca4769bb 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -34,7 +34,6 @@ pub struct Entity { pub id: String, pub keys: String, pub event_id: String, - pub model_names: String, pub created_at: DateTime, pub updated_at: DateTime, } diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index b378146787..c780369fd9 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -11,7 +11,6 @@ lazy_static! { pub static ref ENTITY_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), (Name::new("keys"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))), - (Name::new("model_names"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("event_id"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( Name::new("created_at"), diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 4d9751573e..898bb5fdbd 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -115,7 +115,6 @@ impl EntityObject { IndexMap::from([ (Name::new("id"), Value::from(entity.id)), (Name::new("keys"), Value::from(keys)), - (Name::new("model_names"), Value::from(entity.model_names)), (Name::new("event_id"), Value::from(entity.event_id)), ( Name::new("created_at"), @@ -135,14 +134,15 @@ fn model_union_field() -> Field { match ctx.parent_value.try_to_value()? { Value::Object(indexmap) => { let mut conn = ctx.data::>()?.acquire().await?; - let model_names: Vec = extract::(indexmap, "model_names")? - .split(',') - .map(|s| s.to_string()) - .collect(); - let entity_id = extract::(indexmap, "id")?; + let model_ids: Vec<(String,)> = + sqlx::query_as("SELECT model_id from entity_model WHERE entity_id = ?") + .bind(&entity_id) + .fetch_all(&mut conn) + .await?; + let mut results: Vec> = Vec::new(); - for name in model_names { + for (name,) in model_ids { let type_mapping = type_mapping_query(&mut conn, &name).await?; let mut path_array = vec![name.clone()]; diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index ac42333c8d..007096dfec 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -21,7 +21,6 @@ mod tests { cursor node {{ keys - model_names }} }} page_info {{ @@ -46,7 +45,6 @@ mod tests { {{ entity (id: "{:#x}") {{ keys - model_names models {{ ... on Record {{ __typename @@ -93,12 +91,8 @@ mod tests { // default without params let entities = entities_query(&schema, "").await; let connection: Connection = serde_json::from_value(entities).unwrap(); - let first_entity = connection.edges.first().unwrap(); - let last_entity = connection.edges.last().unwrap(); assert_eq!(connection.edges.len(), 10); assert_eq!(connection.total_count, 20); - assert_eq!(&first_entity.node.model_names, "Subrecord"); - assert_eq!(&last_entity.node.model_names, "Record,RecordSibling"); // first key param - returns all entities with `0x0` as first key let entities = entities_query(&schema, "(keys: [\"0x0\"])").await; diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 72c32b7902..4a55f66a3e 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -51,7 +51,6 @@ pub struct Edge { #[derive(Deserialize, Debug, PartialEq)] pub struct Entity { - pub model_names: String, pub keys: Option>, pub created_at: Option, } diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index 0995bc1d40..4678013f9f 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -54,7 +54,6 @@ mod tests { }} entity {{ keys - model_names }} }} }} @@ -92,7 +91,6 @@ mod tests { assert_eq!(connection.total_count, 10); assert_eq!(connection.edges.len(), 10); assert_eq!(&record.node.__typename, "Record"); - assert_eq!(&entity.model_names, "Record,RecordSibling"); assert_eq!(entity.keys.clone().unwrap(), vec!["0x0"]); assert_eq!(record.node.depth, "Zero"); assert_eq!(nested.depth, "One"); diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index 4c48dc93ba..89b851674e 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -30,7 +30,6 @@ mod tests { "entityUpdated": { "id": entity_id, "keys":vec![keys_str], - "model_names": model_name, "models" : [{ "__typename": model_name, "depth": "Zero", @@ -115,7 +114,6 @@ mod tests { entityUpdated { id keys - model_names models { __typename ... on Record { @@ -153,7 +151,6 @@ mod tests { "entityUpdated": { "id": entity_id, "keys":vec![keys_str], - "model_names": model_name, "models" : [{ "__typename": model_name, "depth": "Zero", @@ -220,7 +217,6 @@ mod tests { entityUpdated(id: "0x579e8877c7755365d5ec1ec7d3a94a457eff5d1f40482bbe9729c064cdead2") { id keys - model_names models { __typename ... on Record { diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index a1c39e0280..048d2746c4 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,15 +3,15 @@ version = 1 [[package]] name = "dojo" -version = "0.3.12" +version = "0.3.13" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.12" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.12#12d58f29ec53454317f1f6d265007a053d279288" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" [[package]] name = "types_test" diff --git a/crates/torii/migrations/20231127235011_entity_model.sql b/crates/torii/migrations/20231127235011_entity_model.sql new file mode 100644 index 0000000000..ed1838c9ac --- /dev/null +++ b/crates/torii/migrations/20231127235011_entity_model.sql @@ -0,0 +1,40 @@ +-- NOTE: sqlite does not support deleteing columns. Workaround is to create new table, copy, and delete old. + +-- Create new table without model_names column +CREATE TABLE entities_new ( + id TEXT NOT NULL PRIMARY KEY, + keys TEXT, + event_id TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Copy from old entities +INSERT INTO entities_new (id, keys, event_id, created_at, updated_at) +SELECT id, keys, event_id, created_at, updated_at +FROM entities; + +-- Disable foreign keys constraint so we can delete entities +PRAGMA foreign_keys = OFF; + +-- Drop old entities +DROP TABLE entities; + +-- Rename table and recreate indexes +ALTER TABLE entities_new RENAME TO entities; +CREATE INDEX idx_entities_keys ON entities (keys); +CREATE INDEX idx_entities_event_id ON entities (event_id); + +-- Renable foreign keys +PRAGMA foreign_keys = ON; + +-- New table to track entity to model relationships +CREATE TABLE entity_model ( + entity_id TEXT NOT NULL, + model_id TEXT NOT NULL, + UNIQUE (entity_id, model_id), + FOREIGN KEY (entity_id) REFERENCES entities (id), + FOREIGN KEY (model_id) REFERENCES models (id) +); +CREATE INDEX idx_entity_model_entity_id ON entity_model (entity_id); +CREATE INDEX idx_entity_model_model_id ON entity_model (model_id); \ No newline at end of file From f171f3404b7ce562c882dcb5f3a2c26ab8958e53 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 29 Nov 2023 22:04:55 +0200 Subject: [PATCH 038/192] Update docker base --- .github/workflows/release.yml | 2 -- Dockerfile | 10 +++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f79fe64254..fa58af603f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -260,7 +260,6 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v4 with: - no-cache: true push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} platforms: linux/arm64 @@ -298,7 +297,6 @@ jobs: with: build-contexts: | artifacts=artifacts - no-cache: true push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} platforms: linux/amd64 diff --git a/Dockerfile b/Dockerfile index f936de7016..5d959e7fa8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:buster-slim as base +FROM debian:bookworm-slim as base LABEL description="Dojo is a provable game engine and toolchain for building onchain games and autonomous worlds with Cairo" \ authors="tarrence " \ @@ -11,8 +11,16 @@ COPY --from=artifacts x86_64-unknown-linux-gnu/release/katana /usr/local/bin/kat COPY --from=artifacts x86_64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo COPY --from=artifacts x86_64-unknown-linux-gnu/release/torii /usr/local/bin/torii +RUN chmod +x /usr/local/bin/katana \ + && chmod +x /usr/local/bin/sozo \ + && chmod +x /usr/local/bin/torii + FROM base as arm64 COPY --from=artifacts aarch64-unknown-linux-gnu/release/katana /usr/local/bin/katana COPY --from=artifacts aarch64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo COPY --from=artifacts aarch64-unknown-linux-gnu/release/torii /usr/local/bin/torii + +RUN chmod +x /usr/local/bin/katana \ + && chmod +x /usr/local/bin/sozo \ + && chmod +x /usr/local/bin/torii \ No newline at end of file From 3176ff4558ebb4131ed546652ee1b1e09591dcbc Mon Sep 17 00:00:00 2001 From: Shramee Srivastav Date: Thu, 30 Nov 2023 08:56:25 +0800 Subject: [PATCH 039/192] dont die for ipfs (#1220) --- crates/sozo/src/ops/migration/mod.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs index 657d1d05f5..fe9d700efb 100644 --- a/crates/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/src/ops/migration/mod.rs @@ -342,16 +342,21 @@ where let metadata = dojo_metadata_from_workspace(ws); if let Some(meta) = metadata.as_ref().and_then(|inner| inner.world()) { - let hash = meta.upload().await?; - - let InvokeTransactionResult { transaction_hash } = - WorldContract::new(world.contract_address, migrator) - .set_metadata_uri(FieldElement::ZERO, format!("ipfs://{hash}")) - .await - .map_err(|e| anyhow!("Failed to set World metadata: {e}"))?; - - ui.print_sub(format!("Set Metadata transaction: {:#x}", transaction_hash)); - ui.print_sub(format!("Metadata uri: ipfs://{hash}")); + match meta.upload().await { + Ok(hash) => { + let InvokeTransactionResult { transaction_hash } = + WorldContract::new(world.contract_address, migrator) + .set_metadata_uri(FieldElement::ZERO, format!("ipfs://{hash}")) + .await + .map_err(|e| anyhow!("Failed to set World metadata: {e}"))?; + + ui.print_sub(format!("Set Metadata transaction: {:#x}", transaction_hash)); + ui.print_sub(format!("Metadata uri: ipfs://{hash}")); + } + Err(err) => { + ui.print_sub(format!("Failed to set World metadata:\n{err}")); + } + } } } None => {} From c96c7c17d396149a73a8061bc738d5d9ae897f43 Mon Sep 17 00:00:00 2001 From: Loaf <90423308+ponderingdemocritus@users.noreply.github.com> Date: Thu, 30 Nov 2023 08:58:43 +0100 Subject: [PATCH 040/192] readme update (#1224) --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 52f3d448c8..849598b475 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ [gha-badge]: https://img.shields.io/github/actions/workflow/status/dojoengine/dojo/ci.yml?branch=main [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fdojoengine [tg-url]: https://t.me/dojoengine + # Dojo: The Provable Game Engine @@ -31,7 +32,7 @@ Dojo offers a comprehensive suite of onchain game development tools, harnessing ### 🔑 Highlighted Features of Dojo: -1. **Entity Component System (ECS)**: Crafted in [Cairo](https://github.com/starkware-libs/cairo), it provides a solid foundation to structure your game. +1. **Default Entity Component System (ECS)**: Crafted in [Cairo](https://github.com/starkware-libs/cairo), it provides a solid foundation to structure your game. 2. **[Torii Indexer](/crates/torii/README.md)**: Say goodbye to manually creating indexers. Torii does it automatically for you! 3. **[Katana Network](/crates/katana/README.md)**: An RPC development network to streamline and expedite your game's iterative processes. 4. **[Sozo CLI](/crates/sozo/README.md)**: Your trusty CLI tool to oversee and upkeep your in-game universes. @@ -41,6 +42,11 @@ Dojo offers a comprehensive suite of onchain game development tools, harnessing See the [installation guide](https://book.dojoengine.org/getting-started/quick-start.html) in the Dojo book. +## ⛩️ Built with Dojo + +- [Awesome Dojo](https://github.com/dojoengine/awesome-dojo) +- [Origami](https://github.com/dojoengine/origami) + ## 📚 Examples in 30s - [Dojo starter react](https://github.com/dojoengine/dojo-starter-react-app) @@ -63,10 +69,6 @@ We welcome contributions of all kinds from anyone. See our [Contribution Guide]( See our [Enviroment setup](https://book.dojoengine.org/getting-started/setup.html) for more information. -## ⛩️ Built with Dojo - -- [Awesome Dojo](https://github.com/dojoengine/awesome-dojo) - ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): From d7e9788459f3b2e797b6565f1057c96a6f5f8cc2 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 29 Nov 2023 22:05:36 +0200 Subject: [PATCH 041/192] Prepare v0.3.14 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e83a521a57..a32c6a4809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2265,7 +2265,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2312,7 +2312,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2334,7 +2334,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "starknet", @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "assert_fs", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.13" +version = "0.3.14" dependencies = [ "crypto-bigint", "hex", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "assert_fs", @@ -4816,7 +4816,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.13" +version = "0.3.14" dependencies = [ "assert_matches", "clap", @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "assert_matches", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "assert_matches", @@ -7236,7 +7236,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "assert_fs", @@ -8267,7 +8267,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.13" +version = "0.3.14" dependencies = [ "async-trait", "camino", @@ -8293,7 +8293,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "async-trait", @@ -8329,7 +8329,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "async-graphql", @@ -8367,7 +8367,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.13" +version = "0.3.14" dependencies = [ "bytes", "dojo-types", @@ -8404,7 +8404,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ecb68c435d..10216198d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.13" +version = "0.3.14" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 72531bfc18..f862d12167 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.3.12" +version = "0.3.14" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index a53a2aec80..1cda391372 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.13" +version = "0.3.14" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index c5d4cd4175..348228fbc6 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.13" +version = "0.3.14" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 1e76728e61..2a79b43ed8 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.13" +version = "0.3.14" [cairo] sierra-replace-ids = true From f4c9a4c79eadb1abc923999cf90dfddd3c3badc2 Mon Sep 17 00:00:00 2001 From: Neo <128649481+neotheprogramist@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:44:32 +0100 Subject: [PATCH 042/192] Implement Comprehensive Dojo Storage Benchmarking with Katana (#1148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added rust crate and dojo contract for benches * fetching fee for tx * used cli methods in benches * a simple test * dependency cleanup * partial * emit macro bench * bench get and set macros * added a list of katana prefunded accounts * new components authorization * contract cleanup * prefunded keypair system * nicer transaction sequence api * first attempt on raw api usage * new contract version * working on starknet api * estimating gas usage instead of executiong * migration of everything to the new api * exposed async api as well * removed old attempt * fixes after merge * added character struct * character init test * update benchmark * using tracing to log gas usage * logging to file * script and program for running benches * sorting tests by name * double set test * a bit cleaner api * a few more benches * more character benches * last fixes * doubled performance * cargo fmt * cargo clippy --fix * add macro_use * fix missing trait * remove unused extern crate * clippy --------- Co-authored-by: Mateusz Zając --- .gitignore | 1 + Cargo.lock | 144 ++++++++ Cargo.toml | 1 + crates/benches/Cargo.toml | 19 + crates/benches/README.md | 51 +++ .../benches/contracts/.vscode/settings.json | 5 + crates/benches/contracts/Scarb.lock | 21 ++ crates/benches/contracts/Scarb.toml | 21 ++ .../benches/contracts/scripts/default_auth.sh | 24 ++ crates/benches/contracts/src/actions.cairo | 329 ++++++++++++++++++ crates/benches/contracts/src/character.cairo | 56 +++ crates/benches/contracts/src/lib.cairo | 5 + crates/benches/contracts/src/models.cairo | 86 +++++ crates/benches/contracts/src/utils.cairo | 23 ++ crates/benches/src/helpers.rs | 97 ++++++ crates/benches/src/lib.rs | 99 ++++++ crates/benches/src/main.rs | 69 ++++ crates/benches/tests/basic.rs | 54 +++ crates/benches/tests/character.rs | 100 ++++++ crates/benches/tests/primitive.rs | 49 +++ crates/dojo-lang/src/semantics/utils.rs | 2 +- crates/katana/core/src/utils/transaction.rs | 46 +-- crates/sozo/src/commands/dev.rs | 2 +- .../torii/graphql/src/tests/entities_test.rs | 2 +- crates/torii/graphql/src/tests/models_test.rs | 2 +- scripts/cargo_bench.sh | 14 + 26 files changed, 1295 insertions(+), 27 deletions(-) create mode 100644 crates/benches/Cargo.toml create mode 100644 crates/benches/README.md create mode 100644 crates/benches/contracts/.vscode/settings.json create mode 100644 crates/benches/contracts/Scarb.lock create mode 100644 crates/benches/contracts/Scarb.toml create mode 100644 crates/benches/contracts/scripts/default_auth.sh create mode 100644 crates/benches/contracts/src/actions.cairo create mode 100644 crates/benches/contracts/src/character.cairo create mode 100644 crates/benches/contracts/src/lib.cairo create mode 100644 crates/benches/contracts/src/models.cairo create mode 100644 crates/benches/contracts/src/utils.cairo create mode 100644 crates/benches/src/helpers.rs create mode 100644 crates/benches/src/lib.rs create mode 100644 crates/benches/src/main.rs create mode 100644 crates/benches/tests/basic.rs create mode 100644 crates/benches/tests/character.rs create mode 100644 crates/benches/tests/primitive.rs create mode 100644 scripts/cargo_bench.sh diff --git a/.gitignore b/.gitignore index 1e38a84066..2f03a36f66 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ dojo.iml .DS_Store .env data +crates/benches/gas_usage.txt diff --git a/Cargo.lock b/Cargo.lock index a32c6a4809..003a13e122 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,6 +633,22 @@ dependencies = [ "serde", ] +[[package]] +name = "benches" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap_builder", + "futures", + "hex", + "lazy_static", + "proptest", + "reqwest", + "sozo", + "starknet", + "tokio", +] + [[package]] name = "bigdecimal" version = "0.3.1" @@ -2990,6 +3006,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -4342,6 +4373,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -5244,6 +5288,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -5506,12 +5568,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -6035,6 +6135,8 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ + "bit-set", + "bit-vec", "bitflags 2.4.1", "lazy_static", "num-traits 0.2.17", @@ -6042,6 +6144,8 @@ dependencies = [ "rand_chacha", "rand_xorshift", "regex-syntax 0.7.5", + "rusty-fork", + "tempfile", "unarray", ] @@ -6153,6 +6257,12 @@ dependencies = [ "prost 0.12.1", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -6343,10 +6453,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls 0.24.2", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -6357,6 +6469,7 @@ dependencies = [ "serde_urlencoded", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", @@ -6538,6 +6651,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.15" @@ -8002,6 +8127,16 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -8857,6 +8992,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.4.0" diff --git a/Cargo.toml b/Cargo.toml index 10216198d3..25ae200b19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/sozo", "crates/torii/client", "crates/torii/server", + "crates/benches", ] [workspace.package] diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml new file mode 100644 index 0000000000..6e98071c43 --- /dev/null +++ b/crates/benches/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "benches" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proptest = "1.3.1" +reqwest = { version="0.11.18", features=["blocking", "json"] } +clap_builder = "4.4.6" + +sozo = { path = "../sozo" } +starknet.workspace = true +anyhow.workspace = true +hex.workspace = true +lazy_static.workspace = true +futures.workspace = true +tokio.workspace = true diff --git a/crates/benches/README.md b/crates/benches/README.md new file mode 100644 index 0000000000..ef39c7bd49 --- /dev/null +++ b/crates/benches/README.md @@ -0,0 +1,51 @@ +# This crate is dedicated for benchmarking purposes + +## Quick start + +```bash +katana +bash scripts/cargo_bench.sh +``` + +## Prerequisites + +- `cargo` - for test case generation and runtime +- `katana` - as a local RPC server +- `sozo` - for contract compilation and deployment + +## Requirements for running + +While benchmarks are running a Katana instance has to be online either remotely or locally... + +```bash +katana +``` + +...contracts have to be built and deployed... + +```bash +sozo --manifest-path crates/benches/contracts/Scarb.toml build +sozo --manifest-path crates/benches/contracts/Scarb.toml migrate +``` + +...and actions authorized. + +```bash +crates/benches/contracts/scripts/default_auth.sh +``` + +Then tests can be run with + +```bash +cargo test bench -- --ignored +``` + +Benchmarks are ignored by default because they need a while to complete and need a running Katana. Their names should start with bench. + +## Running with compiled `sozo` + +While during testing release version of the tool worked better, Sozo can be run from source with + +```bash +cargo run -r --bin sozo -- --manifest-path crates/benches/contracts/Scarb.toml build +``` diff --git a/crates/benches/contracts/.vscode/settings.json b/crates/benches/contracts/.vscode/settings.json new file mode 100644 index 0000000000..47c48a3459 --- /dev/null +++ b/crates/benches/contracts/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cairo1.languageServerPath": "$HOME/.dojo/bin/dojo-language-server", + "cairo1.enableLanguageServer": true, + "cairo1.enableScarb": false +} \ No newline at end of file diff --git a/crates/benches/contracts/Scarb.lock b/crates/benches/contracts/Scarb.lock new file mode 100644 index 0000000000..0158e346f6 --- /dev/null +++ b/crates/benches/contracts/Scarb.lock @@ -0,0 +1,21 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "benches" +version = "0.3.2" +dependencies = [ + "dojo", +] + +[[package]] +name = "dojo" +version = "0.3.2" +source = "git+https://github.com/dojoengine/dojo#0a772af3ec677705042630723c55b421f4fe084d" +dependencies = [ + "dojo_plugin", +] + +[[package]] +name = "dojo_plugin" +version = "0.3.2" diff --git a/crates/benches/contracts/Scarb.toml b/crates/benches/contracts/Scarb.toml new file mode 100644 index 0000000000..81f563c57f --- /dev/null +++ b/crates/benches/contracts/Scarb.toml @@ -0,0 +1,21 @@ +[package] +cairo-version = "2.2.0" +name = "benches" +version = "0.3.2" + +[cairo] +sierra-replace-ids = true + +[dependencies] +dojo = { git = "https://github.com/dojoengine/dojo", version = "0.3.2" } + +[[target.dojo]] + +[tool.dojo] +initializer_class_hash = "0xbeef" + +[tool.dojo.env] +rpc_url = "http://localhost:5050/" +# Default account for katana with seed = 0 +account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" +private_key = "0x1800000000300000180000000000030000000000003006001800006600" diff --git a/crates/benches/contracts/scripts/default_auth.sh b/crates/benches/contracts/scripts/default_auth.sh new file mode 100644 index 0000000000..7eec7a9346 --- /dev/null +++ b/crates/benches/contracts/scripts/default_auth.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -euo pipefail +pushd $(dirname "$0")/.. + +export RPC_URL="http://localhost:5050"; + +export WORLD_ADDRESS=$(cat ./target/dev/manifest.json | jq -r '.world.address') + +export ACTIONS_ADDRESS=$(cat ./target/dev/manifest.json | jq -r '.contracts[] | select(.name == "actions" ).address') + +echo "---------------------------------------------------------------------------" +echo world : $WORLD_ADDRESS +echo " " +echo actions : $ACTIONS_ADDRESS +echo "---------------------------------------------------------------------------" + +# enable system -> component authorizations +COMPONENTS=("Position" "Moves" "Alias" "Character" ) + +for component in ${COMPONENTS[@]}; do + sozo auth writer $component $ACTIONS_ADDRESS --world $WORLD_ADDRESS --rpc-url $RPC_URL +done + +echo "Default authorizations have been successfully set." \ No newline at end of file diff --git a/crates/benches/contracts/src/actions.cairo b/crates/benches/contracts/src/actions.cairo new file mode 100644 index 0000000000..991aa58998 --- /dev/null +++ b/crates/benches/contracts/src/actions.cairo @@ -0,0 +1,329 @@ +use benches::models::{Direction}; +use benches::character::Abilities; + +// define the interface +#[starknet::interface] +trait IActions { + fn spawn(self: @TContractState); + fn move(self: @TContractState, direction: Direction); + + fn bench_basic_emit(self: @TContractState, name: felt252); + fn bench_basic_set(self: @TContractState, name: felt252); + fn bench_basic_double_set(self: @TContractState, name: felt252); + fn bench_basic_get(self: @TContractState); + + fn bench_primitive_pass_many( + self: @TContractState, + first: felt252, + second: felt252, + third: felt252, + fourth: felt252, + fifth: felt252, + sixth: felt252, + seventh: felt252, + eighth: felt252, + ninth: felt252, + ); + fn bench_primitive_iter(self: @TContractState, n: u32); + fn bench_primitive_hash(self: @TContractState, a: felt252, b: felt252, c: felt252); + + fn bench_complex_set_default(self: @TContractState); + fn bench_complex_set_with_smaller(self: @TContractState, abilities: Abilities); + fn bench_complex_update_minimal(self: @TContractState, earned: u32); + fn bench_complex_update_minimal_nested(self: @TContractState, which: u8); + fn bench_complex_get(self: @TContractState); + fn bench_complex_get_minimal(self: @TContractState) -> u32; + fn bench_complex_check(self: @TContractState, ability: felt252, threshold: u8) -> bool; +} + +// dojo decorator +#[dojo::contract] +mod actions { + use starknet::{ContractAddress, get_caller_address}; + use benches::models::{Position, Moves, Direction, Vec2, Alias}; + use benches::utils::next_position; + use benches::character::{Character, Abilities, Stats, Weapon, Sword}; + use super::IActions; + use array::ArrayTrait; + use array::SpanTrait; + use poseidon::poseidon_hash_span; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Moved: Moved, + Aliased: Aliased, + } + + #[derive(Drop, starknet::Event)] + struct Moved { + player: ContractAddress, + direction: Direction + } + + #[derive(Drop, starknet::Event)] + struct Aliased { + player: ContractAddress, + name: felt252, + } + + + #[external(v0)] + impl ActionsImpl of IActions { + fn spawn(self: @ContractState) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + + let position = get!(world, player, (Position)); + let moves = get!(world, player, (Moves)); + + set!( + world, + ( + Moves { + player, remaining: moves.remaining + 10, last_direction: Direction::None(()) + }, + Position { + player, vec: Vec2 { x: position.vec.x + 10, y: position.vec.y + 10 } + }, + ) + ); + } + + fn move(self: @ContractState, direction: Direction) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + let (mut position, mut moves) = get!(world, player, (Position, Moves)); + + moves.remaining -= 1; + moves.last_direction = direction; + let next = next_position(position, direction); + + set!(world, (moves, next)); + emit!(world, Moved { player, direction }); + } + + fn bench_basic_emit(self: @ContractState, name: felt252) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + + emit!(world, Aliased { player, name: name }); + } + + fn bench_basic_set(self: @ContractState, name: felt252) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + + set!(world, Alias { player, name: name }); + } + + fn bench_basic_double_set(self: @ContractState, name: felt252) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + + set!(world, Alias { player, name: name }); + set!(world, Alias { player, name: name }); + } + + fn bench_basic_get(self: @ContractState) { + let world = self.world_dispatcher.read(); + let player = get_caller_address(); + + get!(world, player, Alias); + } + + fn bench_primitive_pass_many(self: @ContractState, + first: felt252, + second: felt252, + third: felt252, + fourth: felt252, + fifth: felt252, + sixth: felt252, + seventh: felt252, + eighth: felt252, + ninth: felt252, + ) { + let sum = first + second + third + fourth + fifth + sixth + seventh + eighth + ninth; + } + + fn bench_primitive_iter(self: @ContractState, n: u32) { + let mut i = 0; + loop { + if i == n { + break; + } + i += 1; + } + } + + fn bench_primitive_hash(self: @ContractState, a: felt252, b: felt252, c: felt252) { + let hash = poseidon_hash_span(array![a, b, c].span()); + } + + + fn bench_complex_set_default(self: @ContractState) { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + set!(world, Character { + caller: get_caller_address(), + heigth: 170, + abilities: Abilities { + strength: 8, + dexterity: 8, + constitution: 8, + intelligence: 8, + wisdom: 8, + charisma: 8, + }, + stats: Stats { + kills: 0, + deaths: 0, + rests: 0, + hits: 0, + blocks: 0, + walked: 0, + runned: 0, + finished: false, + romances: 0, + }, + weapon: Weapon::Fists(( + Sword { + swordsmith: get_caller_address(), + damage: 10, + }, + Sword { + swordsmith: get_caller_address(), + damage: 10, + }, + )), + gold: 0, + }); + } + + fn bench_complex_set_with_smaller(self: @ContractState, abilities: Abilities) { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + set!(world, Character { + caller: get_caller_address(), + heigth: 170, + abilities, + stats: Stats { + kills: 0, + deaths: 0, + rests: 0, + hits: 0, + blocks: 0, + walked: 0, + runned: 0, + finished: false, + romances: 0, + }, + weapon: Weapon::Fists(( + Sword { + swordsmith: get_caller_address(), + damage: 10, + }, + Sword { + swordsmith: get_caller_address(), + damage: 10, + }, + )), + gold: 0, + }); + } + + fn bench_complex_update_minimal(self: @ContractState, earned: u32) { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + let char = get!(world, caller, Character); + + set!(world, Character { + caller: get_caller_address(), + heigth: char.heigth, + abilities: char.abilities, + stats: char.stats, + weapon: char.weapon, + gold: char.gold + earned, + }); + } + + fn bench_complex_update_minimal_nested(self: @ContractState, which: u8) { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + let char = get!(world, caller, Character); + + let stats = Stats { + kills: char.stats.kills + if which == 0 { 0 } else { 1 }, + deaths: char.stats.deaths + if which == 1 { 0 } else { 1 }, + rests: char.stats.rests + if which == 2 { 0 } else { 1 }, + hits: char.stats.hits + if which == 3 { 0 } else { 1 }, + blocks: char.stats.blocks + if which == 4 { 0 } else { 1 }, + walked: char.stats.walked + if which == 5 { 0 } else { 1 }, + runned: char.stats.runned + if which == 6 { 0 } else { 1 }, + finished: char.stats.finished || if which == 7 { false } else { true }, + romances: char.stats.romances + if which == 8 { 0 } else { 1 }, + }; + + set!(world, Character { + caller: get_caller_address(), + heigth: char.heigth, + abilities: char.abilities, + stats: Stats { + kills: char.stats.kills + 1, + deaths: char.stats.deaths, + rests: char.stats.rests, + hits: char.stats.hits, + blocks: char.stats.blocks, + walked: char.stats.walked, + runned: char.stats.runned, + finished: char.stats.finished, + romances: char.stats.romances, + }, + weapon: char.weapon, + gold: char.gold, + }); + } + + fn bench_complex_get(self: @ContractState) { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + let char = get!(world, caller, Character); + } + + fn bench_complex_get_minimal(self: @ContractState) -> u32 { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + let char = get!(world, caller, Character); + char.gold + } + + fn bench_complex_check(self: @ContractState, ability: felt252, threshold: u8) -> bool { + let world = self.world_dispatcher.read(); + let caller = get_caller_address(); + + let char = get!(world, caller, Character); + let points = if ability == 0 { + char.abilities.strength + } else if ability == 1 { + char.abilities.dexterity + } else if ability == 2 { + char.abilities.constitution + } else if ability == 3 { + char.abilities.intelligence + } else if ability == 4 { + char.abilities.wisdom + } else if ability == 5 { + char.abilities.charisma + } else { + 0 + }; + + points >= threshold + } + } +} diff --git a/crates/benches/contracts/src/character.cairo b/crates/benches/contracts/src/character.cairo new file mode 100644 index 0000000000..c4980029d1 --- /dev/null +++ b/crates/benches/contracts/src/character.cairo @@ -0,0 +1,56 @@ +use starknet::{ContractAddress, get_caller_address}; + +// TODO import all this when complex benchmarks are merged +#[derive(Model, Copy, Drop, Serde)] +struct Character { + #[key] + caller: ContractAddress, + heigth: felt252, + abilities: Abilities, + stats: Stats, + weapon: Weapon, + gold: u32, +} + +#[derive(Introspect, Copy, Drop, Serde)] +struct Abilities { + strength: u8, + dexterity: u8, + constitution: u8, + intelligence: u8, + wisdom: u8, + charisma: u8, +} + +#[derive(Introspect, Copy, Drop, Serde)] +struct Stats { + kills: u128, + deaths: u16, + rests: u32, + hits: u64, + blocks: u32, + walked: felt252, + runned: felt252, + finished: bool, + romances: u16, +} + +#[derive(Introspect, Copy, Drop, Serde)] +enum Weapon { + DualWield: (Sword, Sword), + Fists: (Sword, Sword), // Introspect requires same arms +} + +#[derive(Introspect, Copy, Drop, Serde)] +struct Sword { + swordsmith: ContractAddress, + damage: u32, +} + +#[derive(Model, Copy, Drop, Serde)] +struct Case { + #[key] + owner: ContractAddress, + sword: Sword, + material: felt252, +} diff --git a/crates/benches/contracts/src/lib.cairo b/crates/benches/contracts/src/lib.cairo new file mode 100644 index 0000000000..d18dc42392 --- /dev/null +++ b/crates/benches/contracts/src/lib.cairo @@ -0,0 +1,5 @@ +mod actions; +mod models; +mod utils; +mod character; + diff --git a/crates/benches/contracts/src/models.cairo b/crates/benches/contracts/src/models.cairo new file mode 100644 index 0000000000..a78f399c17 --- /dev/null +++ b/crates/benches/contracts/src/models.cairo @@ -0,0 +1,86 @@ +use starknet::ContractAddress; + +#[derive(Serde, Copy, Drop, Introspect)] +enum Direction { + None: (), + Left: (), + Right: (), + Up: (), + Down: (), +} + +impl DirectionIntoFelt252 of Into { + fn into(self: Direction) -> felt252 { + match self { + Direction::None(()) => 0, + Direction::Left(()) => 1, + Direction::Right(()) => 2, + Direction::Up(()) => 3, + Direction::Down(()) => 4, + } + } +} + +#[derive(Model, Drop, Serde)] +struct Moves { + #[key] + player: ContractAddress, + remaining: u8, + last_direction: Direction +} + +#[derive(Copy, Drop, Serde, Introspect)] +struct Vec2 { + x: u32, + y: u32 +} + +#[derive(Model, Copy, Drop, Serde)] +struct Position { + #[key] + player: ContractAddress, + vec: Vec2, +} + +#[derive(Model, Copy, Drop, Serde)] +struct Alias { + #[key] + player: ContractAddress, + name: felt252, +} + +trait Vec2Trait { + fn is_zero(self: Vec2) -> bool; + fn is_equal(self: Vec2, b: Vec2) -> bool; +} + +impl Vec2Impl of Vec2Trait { + fn is_zero(self: Vec2) -> bool { + if self.x - self.y == 0 { + return true; + } + false + } + + fn is_equal(self: Vec2, b: Vec2) -> bool { + self.x == b.x && self.y == b.y + } +} + +#[cfg(test)] +mod tests { + use super::{Position, Vec2, Vec2Trait}; + + #[test] + #[available_gas(100000)] + fn test_vec_is_zero() { + assert(Vec2Trait::is_zero(Vec2 { x: 0, y: 0 }), 'not zero'); + } + + #[test] + #[available_gas(100000)] + fn test_vec_is_equal() { + let position = Vec2 { x: 420, y: 0 }; + assert(position.is_equal(Vec2 { x: 420, y: 0 }), 'not equal'); + } +} diff --git a/crates/benches/contracts/src/utils.cairo b/crates/benches/contracts/src/utils.cairo new file mode 100644 index 0000000000..82164df8ef --- /dev/null +++ b/crates/benches/contracts/src/utils.cairo @@ -0,0 +1,23 @@ +use benches::models::{Position, Direction}; + +fn next_position(mut position: Position, direction: Direction) -> Position { + match direction { + Direction::None(()) => { + return position; + }, + Direction::Left(()) => { + position.vec.x -= 1; + }, + Direction::Right(()) => { + position.vec.x += 1; + }, + Direction::Up(()) => { + position.vec.y -= 1; + }, + Direction::Down(()) => { + position.vec.y += 1; + }, + }; + + position +} diff --git a/crates/benches/src/helpers.rs b/crates/benches/src/helpers.rs new file mode 100644 index 0000000000..81fb8f203e --- /dev/null +++ b/crates/benches/src/helpers.rs @@ -0,0 +1,97 @@ +use std::fs::OpenOptions; +use std::io::Write; + +use anyhow::{Context, Result}; +use reqwest::Url; +use starknet::accounts::{Account, Call, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; +use starknet::core::utils::get_selector_from_name; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; +use starknet::signers::{LocalWallet, SigningKey}; +use tokio::sync::OnceCell; + +use crate::{BenchCall, OwnerAccount, ACCOUNT_ADDRESS, CONTRACT, KATANA_ENDPOINT, PRIVATE_KEY}; + +pub async fn chain_id() -> FieldElement { + static CHAIN_ID: OnceCell = OnceCell::const_new(); + + *CHAIN_ID + .get_or_init(|| async { + let provider = provider(); + provider.chain_id().await.unwrap() + }) + .await +} + +// Because no calls are actually executed in the benchmark, we can use the same nonce for all of +// them +pub async fn nonce() -> FieldElement { + static NONCE: OnceCell = OnceCell::const_new(); + + *NONCE + .get_or_init(|| async { + let account = account().await; + account.get_nonce().await.unwrap() + }) + .await +} + +pub async fn account() -> OwnerAccount { + let signer = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_hex_be(PRIVATE_KEY).unwrap(), + )); + let address = FieldElement::from_hex_be(ACCOUNT_ADDRESS).unwrap(); + let mut account = SingleOwnerAccount::new( + provider(), + signer, + address, + chain_id().await, + ExecutionEncoding::Legacy, + ); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + account +} + +pub fn provider() -> JsonRpcClient { + let url = Url::parse(KATANA_ENDPOINT).expect("Invalid Katana endpoint"); + JsonRpcClient::new(HttpTransport::new(url)) +} + +pub fn log(name: &str, gas: u64, calldata: &str) { + let mut file = + OpenOptions::new().create(true).write(true).append(true).open("gas_usage.txt").unwrap(); + + let mut calldata = String::from(calldata); + if !calldata.is_empty() { + calldata = String::from("\tcalldata: ") + &calldata + } + + writeln!(file, "{}\tfee: {}{calldata}", name, gas).unwrap(); + file.flush().unwrap(); +} + +pub fn parse_calls(entrypoints_and_calldata: Vec) -> Vec { + entrypoints_and_calldata + .into_iter() + .map(|BenchCall(name, calldata)| Call { + to: *CONTRACT, + selector: get_selector_from_name(name).context("Failed to get selector").unwrap(), + calldata, + }) + .collect() +} + +pub async fn execute_calls(calls: Vec) -> Result { + let fee = account() + .await + .execute(calls) + .nonce(nonce().await) + .estimate_fee() + .await + .context("Failed to estimate fee") + .unwrap(); + + Ok(fee.gas_consumed) +} diff --git a/crates/benches/src/lib.rs b/crates/benches/src/lib.rs new file mode 100644 index 0000000000..e5ad3b26c3 --- /dev/null +++ b/crates/benches/src/lib.rs @@ -0,0 +1,99 @@ +mod helpers; + +use anyhow::Result; +use futures::executor::block_on; +use futures::future; +pub use helpers::log; +use helpers::*; +use lazy_static::lazy_static; +use starknet::accounts::SingleOwnerAccount; +use starknet::core::types::FieldElement; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet::signers::LocalWallet; +use tokio::runtime::Runtime; + +type OwnerAccount = SingleOwnerAccount, LocalWallet>; + +const KATANA_ENDPOINT: &str = "http://localhost:5050"; +const CONTRACT_ADDRESS: &str = "0x6c27e3b47f88abca376261ad4f0ffbe3461b9d08477f9e10953829603184e13"; + +const ACCOUNT_ADDRESS: &str = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973"; +const PRIVATE_KEY: &str = "0x1800000000300000180000000000030000000000003006001800006600"; + +pub struct BenchCall(pub &'static str, pub Vec); + +lazy_static! { + static ref CONTRACT: FieldElement = FieldElement::from_hex_be(CONTRACT_ADDRESS).unwrap(); + pub static ref RUNTIME: Runtime = Runtime::new().unwrap(); +} + +pub fn estimate_gas_last(calls: Vec) -> Result { + let mut calls = parse_calls(calls); + let all = calls.clone(); + calls.pop().expect("Empty calls vector"); // remove last call + + let _rt = RUNTIME.enter(); + block_on(async move { + let (whole_gas, before_gas) = + future::try_join(execute_calls(all), execute_calls(calls)).await?; + Ok(whole_gas - before_gas) + }) +} + +pub fn estimate_gas(call: BenchCall) -> Result { + let calls = parse_calls(vec![call]); + let _rt = RUNTIME.enter(); + block_on(async move { execute_calls(calls).await }) +} + +pub fn estimate_gas_multiple(calls: Vec) -> Result { + let calls = parse_calls(calls); + let _rt = RUNTIME.enter(); + block_on(async move { execute_calls(calls).await }) +} + +pub async fn estimate_gas_async(calls: Vec) -> Result { + let calls = parse_calls(calls); + execute_calls(calls).await +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + + use super::*; + + // does not need proptest, as it doesn't use any input + #[test] + #[ignore] // needs a running katana + fn bench_default_spawn() { + let fee = estimate_gas(BenchCall("spawn", vec![])).unwrap(); + + log("bench_spawn", fee, ""); + } + + proptest! { + #[test] + #[ignore] // needs a running katana + fn bench_default_move(c in "0x[0-4]") { + let fee = estimate_gas_last(vec![ + BenchCall("spawn", vec![]), + BenchCall("move", vec![FieldElement::from_hex_be(&c).unwrap()]) + ]).unwrap(); + + log("bench_move", fee, &c); + } + + #[test] + #[ignore] // needs a running katana + fn bench_default_spawn_and_move(c in "0x[0-4]") { + let fee = estimate_gas_multiple(vec![ + BenchCall("spawn", vec![]), + BenchCall("move", vec![FieldElement::from_hex_be(&c).unwrap()]) + ]).unwrap(); + + log("bench_move", fee, &c); + } + } +} diff --git a/crates/benches/src/main.rs b/crates/benches/src/main.rs new file mode 100644 index 0000000000..62330c14e4 --- /dev/null +++ b/crates/benches/src/main.rs @@ -0,0 +1,69 @@ +use std::collections::HashMap; +use std::env; +use std::fs::OpenOptions; +use std::io::{BufRead, BufReader}; + +const DEFAULT_FILENAME: &str = "crates/benches/gas_usage.txt"; +fn main() { + let filename = env::args().nth(1).unwrap_or(DEFAULT_FILENAME.into()); + + let file = OpenOptions::new().create(false).read(true).open(filename).expect( + "Failed to open gas_usage.txt: run tests first with `cargo test bench -- --ignored` and \ + pass correct filename", + ); + let reader = BufReader::new(file); + + let mut map: HashMap)>> = HashMap::new(); + + // Collect info from all runs + for line in reader.lines() { + let line = line.unwrap(); + let segments = line.split('\t').take(3).collect::>(); + + let (name, gas, calldata) = match segments.len() { + 3 => (segments[0], segments[1], Some(String::from(segments[2]))), + 2 => (segments[0], segments[1], None), + _ => panic!("Invalid line: {}", line), + }; + + let gas = gas.split(' ').nth(1).expect("Invalid gas format"); + let gas = gas.parse::().unwrap(); + + if let Some(el) = map.get_mut(name) { + el.push((gas, calldata)); + } else { + map.insert(String::from(name), vec![(gas, calldata)]); + } + } + + let mut pairs = map.into_iter().map(|(name, runs)| (name, runs)).collect::>(); + pairs.sort_by_key(|(key, _)| key.clone()); + + for (name, mut runs) in pairs { + runs.sort_by_key(|(gas, _)| *gas); + let (gas, calldata): (Vec<_>, Vec<_>) = runs.into_iter().unzip(); + + println!("{}:", name); + + if gas[0] == *gas.last().unwrap() { + println!("\tconstant: {}", gas[0]); + continue; + } + + let min_calldata = if let Some(calldata) = calldata[0].clone() { + format!(" for {}", calldata) + } else { + String::new() + }; + let max_calldata = if let Some(calldata) = calldata[calldata.len() - 1].clone() { + format!(" for {}", calldata) + } else { + String::new() + }; + + println!("\tmin: {}{}", gas[0], min_calldata); + println!("\tmax: {}{}", gas[gas.len() - 1], max_calldata); + println!("\taverage: {}", gas.iter().sum::() / gas.len() as u64); + println!("\tmedian: {}", gas[gas.len() / 2]); + } +} diff --git a/crates/benches/tests/basic.rs b/crates/benches/tests/basic.rs new file mode 100644 index 0000000000..f5059d2008 --- /dev/null +++ b/crates/benches/tests/basic.rs @@ -0,0 +1,54 @@ +use benches::{estimate_gas, estimate_gas_last, log, BenchCall}; +use hex::ToHex; +use proptest::prelude::*; +use starknet::core::types::FieldElement; + +proptest! { + #[test] + #[ignore] // needs a running katana + fn bench_basic_emit(s in "[A-Za-z0-9]{1,31}") { + let s_hex = FieldElement::from_hex_be(&format!("0x{}", s.as_bytes().encode_hex::())).unwrap(); + + let fee = estimate_gas( + BenchCall("bench_basic_emit", vec![s_hex]) + ).unwrap(); + + log("bench_basic_emit", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_basic_set(s in "[A-Za-z0-9]{1,31}") { + let s_hex = FieldElement::from_hex_be(&format!("0x{}", s.as_bytes().encode_hex::())).unwrap(); + + let fee = estimate_gas( + BenchCall("bench_basic_set", vec![s_hex]) + ).unwrap(); + + log("bench_basic_set", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_basic_double_set(s in "[A-Za-z0-9]{1,31}") { + let s_hex = FieldElement::from_hex_be(&format!("0x{}", s.as_bytes().encode_hex::())).unwrap(); + + let fee = estimate_gas( + BenchCall("bench_basic_double_set", vec![s_hex]) + ).unwrap(); + + log("bench_basic_double_set", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_basic_get(s in "[A-Za-z0-9]{1,31}") { + let s_hex = FieldElement::from_hex_be(&format!("0x{}", s.as_bytes().encode_hex::())).unwrap(); + let fee = estimate_gas_last(vec![ + BenchCall("bench_basic_set", vec![s_hex]), + BenchCall("bench_basic_get", vec![]) + ]).unwrap(); + + log("bench_basic_get", fee, &s); + } +} diff --git a/crates/benches/tests/character.rs b/crates/benches/tests/character.rs new file mode 100644 index 0000000000..a8fa4a8468 --- /dev/null +++ b/crates/benches/tests/character.rs @@ -0,0 +1,100 @@ +use benches::{estimate_gas, estimate_gas_last, log, BenchCall}; +use proptest::prelude::*; +use starknet::core::types::FieldElement; + +#[test] +#[ignore] // needs a running katana +fn bench_complex_set_default() { + let fee = estimate_gas(BenchCall("bench_complex_set_default", vec![])).unwrap(); + + log("bench_complex_set_default", fee, ""); +} + +proptest! { + #[test] + #[ignore] // needs a running katana + fn bench_complex_set_with_smaller(s in "[0-7]{6}") { + let points = s.chars() + .map(|c| c.to_digit(10).unwrap()) + .map(FieldElement::from) + .collect(); + + let fee = estimate_gas( + BenchCall("bench_complex_set_with_smaller", points) + ).unwrap(); + + log("bench_complex_set_with_smaller", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_complex_update_minimal(s in "[0-9]{9}") { + let calldata = FieldElement::from(s.parse::().unwrap()); + let fee = estimate_gas_last(vec![ + BenchCall("bench_complex_set_default", vec![]), + BenchCall("bench_complex_update_minimal", vec![calldata]) + ]).unwrap(); + + log("bench_complex_update_minimal", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_complex_update_minimal_nested(w in 0..=8) { + let calldata = FieldElement::from(w as u32); + let fee = estimate_gas_last(vec![ + BenchCall("bench_complex_set_default", vec![]), + BenchCall("bench_complex_update_minimal_nested", vec![calldata]) + ]).unwrap(); + + log("bench_complex_update_minimal_nested", fee, &(w as u32).to_string()); + } + + #[test] + #[ignore] // needs a running katana + fn bench_complex_get(s in "[0-7]{6}") { + let calldata = s.chars() + .map(|c| c.to_digit(10).unwrap()) + .map(FieldElement::from) + .collect(); + let fee = estimate_gas_last(vec![ + BenchCall("bench_complex_set_with_smaller", calldata), + BenchCall("bench_complex_get", vec![]) + ]).unwrap(); + + log("bench_complex_get", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_complex_get_minimal(s in "[0-9]{9}") { + let calldata = FieldElement::from(s.parse::().unwrap()); + let fee = estimate_gas_last(vec![ + BenchCall("bench_complex_set_default", vec![]), + BenchCall("bench_complex_update_minimal", vec![calldata]), + BenchCall("bench_complex_get_minimal", vec![]) + ]).unwrap(); + + log("bench_complex_get_minimal", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_complex_check(s in "[0-7]{6}", a in 0..6, t in 0..20) { + let abilities = s.chars() + .map(|c| c.to_digit(10).unwrap()) + .map(FieldElement::from) + .collect(); + + let ability = FieldElement::from(a as u32); + let threshold = FieldElement::from(t as u32); + + let fee = estimate_gas_last(vec![ + BenchCall("bench_complex_set_with_smaller", abilities), + BenchCall("bench_complex_check", vec![ability, threshold]) + ]).unwrap(); + + log("bench_complex_check", fee, &format!("{}, {}, {}", s, a, t)); + + } +} diff --git a/crates/benches/tests/primitive.rs b/crates/benches/tests/primitive.rs new file mode 100644 index 0000000000..239decdb8a --- /dev/null +++ b/crates/benches/tests/primitive.rs @@ -0,0 +1,49 @@ +use benches::{estimate_gas, log, BenchCall}; +use proptest::prelude::*; +use starknet::core::types::FieldElement; + +proptest! { + #[test] + #[ignore] // needs a running katana + fn bench_primitive_pass_many(s in "[0-9a-f]{9}") { + let args = s.chars().map(|c| { + let c = String::from(c); + let hex = format!("0x{}", c); + FieldElement::from_hex_be(&hex).unwrap() + }).collect::>(); + + let fee = estimate_gas( + BenchCall("bench_primitive_pass_many", args) + ).unwrap(); + + log("bench_primitive_pass_many", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_primitive_iter(s in 990..1010) { + let s = format!("{}", s); + let s_hex = FieldElement::from_dec_str(&s).unwrap(); + + let fee = estimate_gas( + BenchCall("bench_primitive_iter", vec![s_hex]) + ).unwrap(); + + log("bench_primitive_iter", fee, &s); + } + + #[test] + #[ignore] // needs a running katana + fn bench_primitive_hash(a in 0..u64::MAX, b in 0..u64::MAX, c in 0..u64::MAX) { + let a = format!("{}", a); + let b = format!("{}", b); + let c = format!("{}", c); + let args = vec![a.clone(), b.clone(), c.clone()].into_iter().map(|d| FieldElement::from_dec_str(&d).unwrap()).collect::>(); + + let fee = estimate_gas( + BenchCall("bench_primitive_hash", args) + ).unwrap(); + + log("bench_primitive_hash", fee, &format!("{},{},{}", a, b, c)); + } +} diff --git a/crates/dojo-lang/src/semantics/utils.rs b/crates/dojo-lang/src/semantics/utils.rs index a1bf09bee6..85536e6f2a 100644 --- a/crates/dojo-lang/src/semantics/utils.rs +++ b/crates/dojo-lang/src/semantics/utils.rs @@ -91,7 +91,7 @@ pub fn find_function_rw( match db.concrete_function_signature(layout_fn) { Ok(signature) => { if let Some(params) = - signature.params.get(0) + signature.params.first() { // looks like // "@dojo_examples::models::Position" diff --git a/crates/katana/core/src/utils/transaction.rs b/crates/katana/core/src/utils/transaction.rs index e66af2f7d8..5fecbe2205 100644 --- a/crates/katana/core/src/utils/transaction.rs +++ b/crates/katana/core/src/utils/transaction.rs @@ -509,6 +509,29 @@ pub fn broadcasted_deploy_account_rpc_to_api_transaction( (api_transaction, contract_address) } +pub fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { + match err { + TransactionExecutionError::EntryPointExecutionError(ref eperr) + | TransactionExecutionError::ExecutionError(ref eperr) => match eperr { + EntryPointExecutionError::ExecutionFailed { error_data } => { + let mut reasons: Vec = vec![]; + error_data.iter().for_each(|felt| { + if let Ok(s) = parse_cairo_short_string(&FieldElement::from(*felt)) { + reasons.push(s); + } + }); + + tracing::warn!(target: "executor", + "Transaction validation error: {}", reasons.join(" ")); + } + _ => tracing::warn!(target: "executor", + "Transaction validation error: {:?}", err), + }, + _ => tracing::warn!(target: "executor", + "Transaction validation error: {:?}", err), + } +} + #[cfg(test)] mod tests { use starknet::core::chain_id; @@ -569,26 +592,3 @@ mod tests { ); } } - -pub fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { - match err { - TransactionExecutionError::EntryPointExecutionError(ref eperr) - | TransactionExecutionError::ExecutionError(ref eperr) => match eperr { - EntryPointExecutionError::ExecutionFailed { error_data } => { - let mut reasons: Vec = vec![]; - error_data.iter().for_each(|felt| { - if let Ok(s) = parse_cairo_short_string(&FieldElement::from(*felt)) { - reasons.push(s); - } - }); - - tracing::warn!(target: "executor", - "Transaction validation error: {}", reasons.join(" ")); - } - _ => tracing::warn!(target: "executor", - "Transaction validation error: {:?}", err), - }, - _ => tracing::warn!(target: "executor", - "Transaction validation error: {:?}", err), - } -} diff --git a/crates/sozo/src/commands/dev.rs b/crates/sozo/src/commands/dev.rs index 5db6cf1421..744bfffb99 100644 --- a/crates/sozo/src/commands/dev.rs +++ b/crates/sozo/src/commands/dev.rs @@ -86,7 +86,7 @@ fn load_context(config: &Config) -> Result> { .filter(|cu| packages.contains(&cu.main_package_id)) .collect::>(); // we have only 1 unit in projects - let unit = compilation_units.get(0).unwrap(); + let unit = compilation_units.first().unwrap(); let db = build_scarb_root_database(unit, &ws).unwrap(); Ok(DevContext { db, unit: unit.clone(), ws }) } diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index 007096dfec..e41da42f32 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -115,7 +115,7 @@ mod tests { // pagination testing let entities = entities_query(&schema, "(first: 20)").await; let all_entities_connection: Connection = serde_json::from_value(entities).unwrap(); - let one = all_entities_connection.edges.get(0).unwrap(); + let one = all_entities_connection.edges.first().unwrap(); let two = all_entities_connection.edges.get(1).unwrap(); let three = all_entities_connection.edges.get(2).unwrap(); let four = all_entities_connection.edges.get(3).unwrap(); diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index 4678013f9f..b033512071 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -224,7 +224,7 @@ mod tests { let records = records_model_query(&schema, "(where: { type_u8GTE: 5 })").await; let connection: Connection = serde_json::from_value(records).unwrap(); - let one = connection.edges.get(0).unwrap(); + let one = connection.edges.first().unwrap(); let two = connection.edges.get(1).unwrap(); let three = connection.edges.get(2).unwrap(); let four = connection.edges.get(3).unwrap(); diff --git a/scripts/cargo_bench.sh b/scripts/cargo_bench.sh new file mode 100644 index 0000000000..f4652a37d5 --- /dev/null +++ b/scripts/cargo_bench.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euxo pipefail + +# Can be run for one intergration test with: `--test TEST_NAME` + +# prepare contract +sozo --manifest-path crates/benches/contracts/Scarb.toml build +sozo --manifest-path crates/benches/contracts/Scarb.toml migrate --rpc-url http://localhost:5050 +/bin/bash crates/benches/contracts/scripts/default_auth.sh + +#run bench and show results +rm -f crates/benches/gas_usage.txt +cargo test bench $@ -- --ignored +cargo run --bin benches crates/benches/gas_usage.txt From 4ffeff5a335f742957d8d167473f156b290629bd Mon Sep 17 00:00:00 2001 From: Bal7hazar Date: Thu, 30 Nov 2023 10:44:53 +0100 Subject: [PATCH 043/192] =?UTF-8?q?=F0=9F=90=9B=20Fix=20duplicates=20while?= =?UTF-8?q?=20sozo=20tests=20(#1157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Fix duplicates while sozo tests * 💚 Clippy * Update crates/sozo/src/commands/test.rs Co-authored-by: Ammar Arif --------- Co-authored-by: Ammar Arif --- crates/sozo/src/commands/test.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/sozo/src/commands/test.rs b/crates/sozo/src/commands/test.rs index 391a484380..ff73c9ab3a 100644 --- a/crates/sozo/src/commands/test.rs +++ b/crates/sozo/src/commands/test.rs @@ -46,7 +46,11 @@ impl TestArgs { }); let resolve = ops::resolve_workspace(&ws)?; - let compilation_units = ops::generate_compilation_units(&resolve, &ws)?; + // TODO: Compute all compilation units and remove duplicates, could be unnecessary in future + // version of Scarb. + let mut compilation_units = ops::generate_compilation_units(&resolve, &ws)?; + compilation_units.sort_by_key(|unit| unit.main_package_id); + compilation_units.dedup_by_key(|unit| unit.main_package_id); for unit in compilation_units { let props: Props = unit.target().props()?; From a279253266c68d42c6f18597816155485242d98d Mon Sep 17 00:00:00 2001 From: Neo <128649481+neotheprogramist@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:15:21 +0100 Subject: [PATCH 044/192] Enhance Katana Logging to Mirror Starknet Devnet Features (#1206) --- crates/katana/src/main.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index 72e76e1e92..67fcbfa61c 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -4,6 +4,9 @@ use std::{fs, io}; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Shell}; use console::Style; +use katana_core::constants::{ + ERC20_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, UDC_ADDRESS, UDC_CLASS_HASH, +}; use katana_core::sequencer::KatanaSequencer; use katana_rpc::{spawn, NodeHandle}; use tokio::signal::ctrl_c; @@ -36,7 +39,8 @@ async fn main() -> Result<(), Box> { let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; if !config.silent { - let accounts = sequencer.backend.accounts.iter(); + let mut accounts = sequencer.backend.accounts.iter().peekable(); + let account_class_hash = accounts.peek().unwrap().class_hash; if config.json_log { info!( @@ -56,6 +60,7 @@ async fn main() -> Result<(), Box> { "🚀 JSON-RPC server started: {}", Style::new().red().apply_to(format!("http://{addr}")) ), + format!("{}", account_class_hash), ); } } @@ -74,7 +79,7 @@ fn print_completion(shell: Shell) { generate(shell, &mut command, name, &mut io::stdout()); } -fn print_intro(accounts: String, seed: String, address: String) { +fn print_intro(accounts: String, seed: String, address: String, account_class_hash: String) { println!( "{}", Style::new().red().apply_to( @@ -87,11 +92,33 @@ fn print_intro(accounts: String, seed: String, address: String) { ██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║ ██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ - " ) ); + println!( + r" +PREDEPLOYED CONTRACTS +================== + +| Contract | Fee Token +| Address | {} +| Class Hash | {} + +| Contract | Universal Deployer +| Address | {} +| Class Hash | {} + +| Contract | Account Contract +| Class Hash | {} + ", + *FEE_TOKEN_ADDRESS, + *ERC20_CONTRACT_CLASS_HASH, + *UDC_ADDRESS, + *UDC_CLASS_HASH, + account_class_hash + ); + println!( r" PREFUNDED ACCOUNTS From 8c33ed6cc93d8eea65acf5ce0584d80605475960 Mon Sep 17 00:00:00 2001 From: Loaf <90423308+ponderingdemocritus@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:09:27 +0100 Subject: [PATCH 045/192] updates execute to accept name of contract not address (#1225) * updates execute to accept name of contract not address * remove print * fixes and accept addr and name * changed to contract from contract_name * clippy shutup * clippy * move network req --- Cargo.lock | 1 + crates/sozo/Cargo.toml | 1 + crates/sozo/src/commands/execute.rs | 2 +- crates/sozo/src/ops/execute.rs | 36 +++++++++++++++++++++++++++-- examples/spawn-and-move/Scarb.lock | 4 ++-- examples/spawn-and-move/Scarb.toml | 5 ++-- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 003a13e122..34bb135760 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7395,6 +7395,7 @@ dependencies = [ "smol_str", "snapbox", "starknet", + "starknet-crypto 0.6.1", "thiserror", "tokio", "tracing", diff --git a/crates/sozo/Cargo.toml b/crates/sozo/Cargo.toml index 7adec630d8..24418dabef 100644 --- a/crates/sozo/Cargo.toml +++ b/crates/sozo/Cargo.toml @@ -34,6 +34,7 @@ semver.workspace = true serde.workspace = true serde_json.workspace = true smol_str.workspace = true +starknet-crypto.workspace = true starknet.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/crates/sozo/src/commands/execute.rs b/crates/sozo/src/commands/execute.rs index 0e11a2077c..666c0b8357 100644 --- a/crates/sozo/src/commands/execute.rs +++ b/crates/sozo/src/commands/execute.rs @@ -12,7 +12,7 @@ use crate::ops::execute; #[command(about = "Execute a system with the given calldata.")] pub struct ExecuteArgs { #[arg(help = "The address of the contract to be executed.")] - pub contract: FieldElement, + pub contract: String, #[arg(help = "The name of the entrypoint to be executed.")] pub entrypoint: String, diff --git a/crates/sozo/src/ops/execute.rs b/crates/sozo/src/ops/execute.rs index 3d8fe31a64..9cc727badc 100644 --- a/crates/sozo/src/ops/execute.rs +++ b/crates/sozo/src/ops/execute.rs @@ -1,7 +1,11 @@ use anyhow::{Context, Result}; use dojo_world::metadata::Environment; +use dojo_world::migration::strategy::generate_salt; use starknet::accounts::{Account, Call}; -use starknet::core::utils::get_selector_from_name; +use starknet::core::types::{BlockId, BlockTag, FieldElement, FunctionCall}; +use starknet::core::utils::{get_contract_address, get_selector_from_name}; +use starknet::macros::selector; +use starknet::providers::Provider; use crate::commands::execute::ExecuteArgs; @@ -10,12 +14,40 @@ pub async fn execute(args: ExecuteArgs, env_metadata: Option) -> Re let provider = starknet.provider(env_metadata.as_ref())?; + let contract_address = if contract.starts_with("0x") { + FieldElement::from_hex_be(&contract)? + } else { + let world_address = env_metadata + .as_ref() + .and_then(|env| env.world_address.as_ref()) + .cloned() + .ok_or_else(|| anyhow::anyhow!("No World Address found"))?; + + let contract_class_hash = provider + .call( + FunctionCall { + contract_address: FieldElement::from_hex_be(&world_address).unwrap(), + entry_point_selector: selector!("base"), + calldata: [].to_vec(), + }, + BlockId::Tag(BlockTag::Latest), + ) + .await?; + + get_contract_address( + generate_salt(&contract), + contract_class_hash[0], + &[], + FieldElement::from_hex_be(&world_address).unwrap(), + ) + }; + let account = account.account(provider, env_metadata.as_ref()).await?; let res = account .execute(vec![Call { calldata, - to: contract, + to: contract_address, selector: get_selector_from_name(&entrypoint).unwrap(), }]) .send() diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 24a4548ad3..87ccac7512 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,14 +3,14 @@ version = 1 [[package]] name = "dojo" -version = "0.3.12" +version = "0.3.13" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.12" +version = "0.3.13" dependencies = [ "dojo", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 2a79b43ed8..5c80c1f677 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -10,11 +10,11 @@ sierra-replace-ids = true dojo = { path = "../../crates/dojo-core" } [[target.dojo]] -build-external-contracts = [] +build-external-contracts = [ ] [tool.dojo.world] -name = "example" description = "example world" +name = "example" [tool.dojo.env] rpc_url = "http://localhost:5050/" @@ -22,3 +22,4 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" private_key = "0x1800000000300000180000000000030000000000003006001800006600" +world_address = "0x5010c31f127114c6198df8a5239e2b7a5151e1156fb43791e37e7385faa8138" From 4a9305f4045a4da607f5d744fc228e3c07df1f08 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:12:30 +0100 Subject: [PATCH 046/192] Feat/storage custom (#1230) --- crates/dojo-lang/src/contract.rs | 59 ++++++- crates/dojo-lang/src/plugin_test_data/system | 176 +++++++++---------- 2 files changed, 137 insertions(+), 98 deletions(-) diff --git a/crates/dojo-lang/src/contract.rs b/crates/dojo-lang/src/contract.rs index 9880b4efc6..bdee671e77 100644 --- a/crates/dojo-lang/src/contract.rs +++ b/crates/dojo-lang/src/contract.rs @@ -24,6 +24,7 @@ impl DojoContract { let name = module_ast.name(db).text(db); let mut system = DojoContract { diagnostics: vec![], dependencies: HashMap::new() }; let mut has_event = false; + let mut has_storage = false; if let MaybeModuleBody::Some(body) = module_ast.body(db) { let mut body_nodes: Vec<_> = body @@ -36,6 +37,11 @@ impl DojoContract { has_event = true; return system.merge_event(db, enum_ast.clone()); } + } else if let ast::Item::Struct(struct_ast) = el { + if struct_ast.name(db).text(db).to_string() == "Storage" { + has_storage = true; + return system.merge_storage(db, struct_ast.clone()); + } } vec![RewriteNode::Copied(el.as_syntax_node())] @@ -46,6 +52,10 @@ impl DojoContract { body_nodes.append(&mut system.create_event()) } + if !has_storage { + body_nodes.append(&mut system.create_storage()) + } + let mut builder = PatchBuilder::new(db); builder.add_modified(RewriteNode::interpolate_patched( " @@ -59,14 +69,6 @@ impl DojoContract { component!(path: dojo::components::upgradeable::upgradeable, storage: \ upgradeable, event: UpgradeableEvent); - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } - #[external(v0)] fn dojo_resource(self: @ContractState) -> felt252 { '$name$' @@ -151,6 +153,47 @@ impl DojoContract { .to_string(), )] } + + pub fn merge_storage( + &mut self, + db: &dyn SyntaxGroup, + struct_ast: ast::ItemStruct, + ) -> Vec { + let mut rewrite_nodes = vec![]; + + let elements = struct_ast.members(db).elements(db); + + let members = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); + let members = members.join(", "); + + rewrite_nodes.push(RewriteNode::interpolate_patched( + " + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + $members$ + } + ", + &UnorderedHashMap::from([("members".to_string(), RewriteNode::Text(members))]), + )); + rewrite_nodes + } + + pub fn create_storage(&mut self) -> Vec { + vec![RewriteNode::Text( + " + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } + " + .to_string(), + )] + } } // fn is_context(db: &dyn SyntaxGroup, param: &Param) -> bool { diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index bb252973ac..62d81aabdf 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -187,145 +187,145 @@ error: Unknown inline item macro: 'component'. ^**********************************************************************************************************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:12:21 - #[storage] - ^********^ - -error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:15:25 - #[substorage(v0)] - ^***************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:19:21 + --> test_src/lib.cairo[spawn]:11:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:24:21 + --> test_src/lib.cairo[spawn]:16:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:31:21 + --> test_src/lib.cairo[spawn]:23:21 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[spawn]:41:13 + --> test_src/lib.cairo[spawn]:33:13 #[event] ^******^ +error: Unsupported attribute. + --> test_src/lib.cairo[spawn]:39:13 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[spawn]:42:17 + #[substorage(v0)] + ^***************^ + error: Unknown inline item macro: 'component'. --> test_src/lib.cairo[proxy]:9:21 component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); ^**********************************************************************************************************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:12:21 - #[storage] - ^********^ - -error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:15:25 - #[substorage(v0)] - ^***************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:19:21 + --> test_src/lib.cairo[proxy]:11:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:24:21 + --> test_src/lib.cairo[proxy]:16:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:31:21 + --> test_src/lib.cairo[proxy]:23:21 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[proxy]:38:13 + --> test_src/lib.cairo[proxy]:30:13 #[event] ^******^ +error: Unsupported attribute. + --> test_src/lib.cairo[proxy]:36:13 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[proxy]:39:17 + #[substorage(v0)] + ^***************^ + error: Unknown inline item macro: 'component'. --> test_src/lib.cairo[ctxnamed]:9:21 component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); ^**********************************************************************************************************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:12:21 - #[storage] - ^********^ - -error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:15:25 - #[substorage(v0)] - ^***************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:19:21 + --> test_src/lib.cairo[ctxnamed]:11:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:24:21 + --> test_src/lib.cairo[ctxnamed]:16:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:31:21 + --> test_src/lib.cairo[ctxnamed]:23:21 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[ctxnamed]:41:13 + --> test_src/lib.cairo[ctxnamed]:33:13 #[event] ^******^ +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:39:13 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[ctxnamed]:42:17 + #[substorage(v0)] + ^***************^ + error: Unknown inline item macro: 'component'. --> test_src/lib.cairo[withevent]:9:21 component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); ^**********************************************************************************************************^ error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:12:21 - #[storage] - ^********^ - -error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:15:25 - #[substorage(v0)] - ^***************^ - -error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:19:21 + --> test_src/lib.cairo[withevent]:11:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:24:21 + --> test_src/lib.cairo[withevent]:16:21 #[external(v0)] ^*************^ error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:31:21 + --> test_src/lib.cairo[withevent]:23:21 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:35:13 + --> test_src/lib.cairo[withevent]:27:13 #[event] ^******^ error: Unsupported attribute. - --> test_src/lib.cairo[withevent]:47:5 + --> test_src/lib.cairo[withevent]:39:5 #[external(v0)] ^*************^ +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:44:13 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withevent]:47:17 + #[substorage(v0)] + ^***************^ + //! > expanded_cairo_code #[starknet::contract] mod spawn { @@ -334,14 +334,6 @@ error: Unsupported attribute. use dojo::world::IWorldDispatcherTrait; use dojo::world::IWorldProvider; - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } - #[external(v0)] fn dojo_resource(self: @ContractState) -> felt252 { 'spawn' @@ -369,6 +361,13 @@ error: Unsupported attribute. enum Event { UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, } + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } impl EventDrop of Drop::; } @@ -380,14 +379,6 @@ impl EventDrop of Drop::; use dojo::world::IWorldDispatcherTrait; use dojo::world::IWorldProvider; - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } - #[external(v0)] fn dojo_resource(self: @ContractState) -> felt252 { 'proxy' @@ -412,6 +403,13 @@ impl EventDrop of Drop::; enum Event { UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, } + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } impl EventDrop of Drop::; } @@ -423,14 +421,6 @@ impl EventDrop of Drop::; use dojo::world::IWorldDispatcherTrait; use dojo::world::IWorldProvider; - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } - #[external(v0)] fn dojo_resource(self: @ContractState) -> felt252 { 'ctxnamed' @@ -458,6 +448,13 @@ impl EventDrop of Drop::; enum Event { UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, } + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } impl EventDrop of Drop::; } @@ -469,14 +466,6 @@ impl EventDrop of Drop::; use dojo::world::IWorldDispatcherTrait; use dojo::world::IWorldProvider; - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } - #[external(v0)] fn dojo_resource(self: @ContractState) -> felt252 { 'withevent' @@ -509,7 +498,14 @@ impl EventDrop of Drop::; fn test(value: felt252) -> value { value } + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + } impl EventDrop of Drop::; impl TestEventDrop of Drop::; - + } From 4979471af5d365c2e6aeb9ef1a2cc87b49b04a67 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 10:37:14 +0200 Subject: [PATCH 047/192] Update devcontainer docker --- .devcontainer/Dockerfile | 7 +++---- .github/workflows/devcontainer.yml | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a8c93b5a49..b82f63fde9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,7 +2,7 @@ # [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye ARG VARIANT -FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT} +FROM mcr.microsoft.com/vscode/devcontainers/rust:${VARIANT} # Install additional packages RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ @@ -28,10 +28,9 @@ ENV NVM_SYMLINK_CURRENT=true \ COPY .devcontainer/library-scripts/node-debian.sh /tmp/library-scripts/ RUN apt-get update && bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" -RUN pip install starknet-devnet==v0.5.0a2 - -# Install dojoup for vscode user +# Install dojoup and scarb for vscode user USER vscode RUN curl -L https://install.dojoengine.org | bash +RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash ENV PATH=${PATH}:/workspaces/dojo/target/release diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index d28914cc25..505d1acfc5 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -31,7 +31,9 @@ jobs: - name: Set Docker tag for push event if: github.event_name == 'push' - run: echo "DOCKER_TAG=${{ github.sha }}" >> $GITHUB_ENV + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c 1-7) + echo "DOCKER_TAG=$SHORT_SHA" >> $GITHUB_ENV - name: Build and push Docker image uses: docker/build-push-action@v2 From a8ddb88308180c51cf23f0d8f2c97a3d5143b36d Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 12:11:48 +0200 Subject: [PATCH 048/192] Update devcontainer pr ci --- .github/workflows/devcontainer.yml | 110 ++++++++++++++--------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 505d1acfc5..810e0356cf 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -3,49 +3,49 @@ name: devcontainer on: push: paths: - - '.devcontainer/**' - - '.github/workflows/devcontainer.yml' - - '!.devcontainer/devcontainer.json' + - ".devcontainer/**" + - ".github/workflows/devcontainer.yml" + - "!.devcontainer/devcontainer.json" jobs: build-and-push: runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Set Docker tag for release event - if: github.event_name == 'release' - run: echo "DOCKER_TAG=${{ github.event.release.tag_name }}" >> $GITHUB_ENV + - name: Set Docker tag for release event + if: github.event_name == 'release' + run: echo "DOCKER_TAG=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - - name: Set Docker tag for push event - if: github.event_name == 'push' - run: | - SHORT_SHA=$(echo "${{ github.sha }}" | cut -c 1-7) - echo "DOCKER_TAG=$SHORT_SHA" >> $GITHUB_ENV + - name: Set Docker tag for push event + if: github.event_name == 'push' + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c 1-7) + echo "DOCKER_TAG=$SHORT_SHA" >> $GITHUB_ENV - - name: Build and push Docker image - uses: docker/build-push-action@v2 - with: - push: true - file: .devcontainer/Dockerfile - tags: ghcr.io/${{ github.repository }}-dev:latest,ghcr.io/${{ github.repository }}-dev:${{ env.DOCKER_TAG }} - build-args: | - VARIANT=bullseye - platforms: linux/amd64,linux/arm64 - cache-from: type=registry,ref=ghcr.io/${{ github.repository }}-dev:latest - cache-to: type=registry,ref=ghcr.io/${{ github.repository }}-dev:cache + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + push: true + file: .devcontainer/Dockerfile + tags: ghcr.io/${{ github.repository }}-dev:latest,ghcr.io/${{ github.repository }}-dev:${{ env.DOCKER_TAG }} + build-args: | + VARIANT=bullseye + platforms: linux/amd64,linux/arm64 + cache-from: type=registry,ref=ghcr.io/${{ github.repository }}-dev:latest + cache-to: type=registry,ref=ghcr.io/${{ github.repository }}-dev:cache propose-update-pr: needs: build-and-push @@ -53,30 +53,30 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - - name: Update devcontainer.json - run: | - sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.DOCKER_TAG }}|" .devcontainer/devcontainer.json + - name: Update devcontainer.json + run: | + sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.DOCKER_TAG }}|" .devcontainer/devcontainer.json - - name: Setup Git credentials - run: | - git config user.name "GitHub Action" - git config user.email "action@github.com" + - name: Setup Git credentials + run: | + git config user.name "GitHub Action" + git config user.email "action@github.com" - - name: Commit and push changes - run: | - git add .devcontainer/devcontainer.json - git commit -m "Update devcontainer image hash" - git push + - name: Commit and push changes + run: | + git add .devcontainer/devcontainer.json + git commit -m "Update devcontainer image hash: ${{needs.build-and-push.outputs.DOCKER_TAG}}" + git push -b devcontainer-${{needs.build-and-push.outputs.DOCKER_TAG}} - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - title: "Update devcontainer image hash" - commit-message: "Update devcontainer image hash" - branch: update-devcontainer-image-hash - base: main - delete-branch: true + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: "Update devcontainer image hash: ${{needs.build-and-push.outputs.DOCKER_TAG}}" + commit-message: "Update devcontainer image hash" + branch: update-devcontainer-image-hash + base: main + delete-branch: true From 9d207ba5b46227e6e2544e0b8a4988abbb263345 Mon Sep 17 00:00:00 2001 From: Yun Date: Fri, 1 Dec 2023 02:15:21 -0800 Subject: [PATCH 049/192] Torii client hook up retrieve entities (#1223) * Torii client hook up retrieve entities * mirror dojo_types schema in protobuf * add more protobuf messages * fix wasm compile error * fix test --- Cargo.lock | 1 + Cargo.toml | 1 + crates/dojo-types/src/primitive.rs | 22 ++- crates/dojo-types/src/schema.rs | 10 +- crates/torii/client/Cargo.toml | 2 +- crates/torii/client/src/client/mod.rs | 15 +- crates/torii/core/src/model.rs | 23 ++-- crates/torii/grpc/Cargo.toml | 1 + crates/torii/grpc/proto/schema.proto | 62 +++++++++ crates/torii/grpc/proto/types.proto | 37 +---- crates/torii/grpc/proto/world.proto | 2 +- crates/torii/grpc/src/client.rs | 28 +++- crates/torii/grpc/src/server/mod.rs | 103 ++++++++------ crates/torii/grpc/src/types.rs | 189 ++++++++++++++++++++++++-- 14 files changed, 378 insertions(+), 118 deletions(-) create mode 100644 crates/torii/grpc/proto/schema.proto diff --git a/Cargo.lock b/Cargo.lock index 34bb135760..57c8f1433e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8506,6 +8506,7 @@ name = "torii-grpc" version = "0.3.14" dependencies = [ "bytes", + "crypto-bigint", "dojo-types", "futures", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index 25ae200b19..a4085f9557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ clap_complete = "4.3" colored = "2" console = "0.15.7" convert_case = "0.6.0" +crypto-bigint = { version = "0.5.3", features = [ "serde" ] } env_logger = "0.10.0" flate2 = "1.0.24" futures = "0.3.28" diff --git a/crates/dojo-types/src/primitive.rs b/crates/dojo-types/src/primitive.rs index 2e91d0fdbb..49d67fc9a5 100644 --- a/crates/dojo-types/src/primitive.rs +++ b/crates/dojo-types/src/primitive.rs @@ -1,10 +1,22 @@ use crypto_bigint::{Encoding, U256}; use serde::{Deserialize, Serialize}; use starknet::core::types::{FieldElement, ValueOutOfRangeError}; +use strum::IntoEnumIterator; use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; #[derive( - AsRefStr, Display, EnumIter, EnumString, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, + AsRefStr, + Display, + EnumIter, + EnumString, + Copy, + Clone, + Debug, + Serialize, + Deserialize, + PartialEq, + Hash, + Eq, )] #[serde(tag = "scalar_type", content = "value")] #[strum(serialize_all = "lowercase")] @@ -101,6 +113,14 @@ impl Primitive { set_primitive!(set_class_hash, ClassHash, FieldElement); set_primitive!(set_contract_address, ContractAddress, FieldElement); + pub fn to_numeric(&self) -> usize { + Self::iter().position(|p| p == *self).unwrap() + } + + pub fn from_numeric(value: usize) -> Option { + Self::iter().nth(value) + } + pub fn to_sql_type(&self) -> SqlType { match self { Primitive::U8(_) diff --git a/crates/dojo-types/src/schema.rs b/crates/dojo-types/src/schema.rs index f791c7bb96..a1e42206b7 100644 --- a/crates/dojo-types/src/schema.rs +++ b/crates/dojo-types/src/schema.rs @@ -6,7 +6,7 @@ use strum_macros::AsRefStr; use crate::primitive::{Primitive, PrimitiveError}; /// Represents a model member. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Member { pub name: String, #[serde(rename = "member_type")] @@ -31,7 +31,7 @@ pub struct ModelMetadata { } /// Represents all possible types in Cairo -#[derive(AsRefStr, Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(AsRefStr, Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] #[serde(tag = "type", content = "content")] #[serde(rename_all = "lowercase")] pub enum Ty { @@ -216,7 +216,7 @@ impl std::fmt::Display for Ty { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Struct { pub name: String, pub children: Vec, @@ -241,14 +241,14 @@ pub enum EnumError { OptionInvalid, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct Enum { pub name: String, pub option: Option, pub options: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash, Eq)] pub struct EnumOption { pub name: String, pub ty: Ty, diff --git a/crates/torii/client/Cargo.toml b/crates/torii/client/Cargo.toml index 4e4ab8e240..c777de0701 100644 --- a/crates/torii/client/Cargo.toml +++ b/crates/torii/client/Cargo.toml @@ -7,7 +7,7 @@ version.workspace = true [dependencies] async-trait.workspace = true -crypto-bigint = "0.5.3" +crypto-bigint.workspace = true dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world", features = [ "contracts" ] } futures-util = "0.3.28" diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index ba713b5c5a..7bed643a32 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -17,7 +17,8 @@ use starknet::providers::JsonRpcClient; use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; use torii_grpc::client::EntityUpdateStreaming; -use torii_grpc::types::KeysClause; +use torii_grpc::proto::world::RetrieveEntitiesResponse; +use torii_grpc::types::{Entity, KeysClause, Query}; use self::error::{Error, ParseError}; use self::storage::ModelStorage; @@ -98,6 +99,18 @@ impl Client { self.subscribed_entities.entities_keys.read() } + /// Retrieves entities matching specified keys and/or model name in query parameter. + /// + /// The query can include keys and a model name, both optional. Without parameters, it fetches + /// all entities, which is less efficient as it requires additional queries for each + /// entity's model data. Specifying a model name optimizes the process by limiting the + /// retrieval to entities with that model, requiring just one query. + pub async fn entities(&self, query: Query) -> Result, Error> { + let mut grpc_client = self.inner.write().await; + let RetrieveEntitiesResponse { entities } = grpc_client.retrieve_entities(query).await?; + Ok(entities.into_iter().map(TryInto::try_into).collect::, _>>()?) + } + /// Returns the model value of an entity. /// /// This function will only return `None`, if `model` doesn't exist. If there is no entity with diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index c05831c664..883470e1b1 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -177,14 +177,9 @@ pub fn build_sql_query(model_schemas: &Vec) -> Result { } } - let primary_table = model_schemas[0].name(); let mut global_selections = Vec::new(); - let mut global_tables = model_schemas - .iter() - .enumerate() - .filter(|(index, _)| *index != 0) // primary_table don't `JOIN` itself - .map(|(_, schema)| schema.name()) - .collect::>(); + let mut global_tables = + model_schemas.iter().enumerate().map(|(_, schema)| schema.name()).collect::>(); for ty in model_schemas { let schema = ty.as_struct().expect("schema should be struct"); @@ -206,11 +201,11 @@ pub fn build_sql_query(model_schemas: &Vec) -> Result { let selections_clause = global_selections.join(", "); let join_clause = global_tables .into_iter() - .map(|table| format!(" LEFT JOIN {table} ON {primary_table}.entity_id = {table}.entity_id")) + .map(|table| format!(" LEFT JOIN {table} ON entities.id = {table}.entity_id")) .collect::>() .join(" "); - Ok(format!("SELECT {selections_clause} FROM {primary_table}{join_clause}")) + Ok(format!("SELECT entities.keys, {selections_clause} FROM entities{join_clause}")) } /// Populate the values of a Ty (schema) from SQLite row. @@ -509,12 +504,14 @@ mod tests { }); let query = build_sql_query(&vec![ty]).unwrap(); + println!("{query}"); assert_eq!( query, - "SELECT Position.external_name AS \"Position.name\", Position.external_age AS \ - \"Position.age\", Position$Vec2.external_x AS \"Position$Vec2.x\", \ - Position$Vec2.external_y AS \"Position$Vec2.y\" FROM Position LEFT JOIN \ - Position$Vec2 ON Position.entity_id = Position$Vec2.entity_id" + "SELECT entities.keys, Position.external_name AS \"Position.name\", \ + Position.external_age AS \"Position.age\", Position$Vec2.external_x AS \ + \"Position$Vec2.x\", Position$Vec2.external_y AS \"Position$Vec2.y\" FROM entities \ + LEFT JOIN Position ON entities.id = Position.entity_id LEFT JOIN Position$Vec2 ON \ + entities.id = Position$Vec2.entity_id" ); } } diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml index 451f62d02a..b1101c82fc 100644 --- a/crates/torii/grpc/Cargo.toml +++ b/crates/torii/grpc/Cargo.toml @@ -19,6 +19,7 @@ torii-core = { path = "../core", optional = true } serde.workspace = true strum_macros.workspace = true +crypto-bigint.workspace = true # server hex.workspace = true diff --git a/crates/torii/grpc/proto/schema.proto b/crates/torii/grpc/proto/schema.proto new file mode 100644 index 0000000000..7c6e5a9cd9 --- /dev/null +++ b/crates/torii/grpc/proto/schema.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; +package types; + +enum PrimitiveType { + U8 = 0; + U16 = 1; + U32 = 2; + U64 = 3; + U128 = 4; + U256 = 5; + USIZE = 6; + BOOL = 7; + FELT252 = 8; + CLASS_HASH = 9; + CONTRACT_ADDRESS = 10; +} + +message EnumOption { + string name = 1; + Ty ty = 2; +} + +message Enum { + string name = 1; + uint32 option = 2; + repeated EnumOption options = 3; +} + +message Primitive { + PrimitiveType type = 1; + Value value = 2; +} + +message Struct { + string name = 1; + repeated Member children = 2; +} + +message Ty { + oneof ty_type { + Primitive primitive = 2; + Enum enum = 3; + Struct struct = 4; + // TODO: Tuple + } +} + +message Member { + string name = 1; + Ty ty = 2; + bool key = 3; +} + +message Value { + oneof value_type { + string string_value = 2; + int64 int_value = 3; + uint64 uint_value = 4; + bool bool_value = 5; + bytes byte_value = 6; + } +} diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index 873cabd4b7..e413817e22 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package types; +import "schema.proto"; + message WorldMetadata { // The hex-encoded address of the world. string world_address = 1; @@ -29,31 +31,10 @@ message ModelMetadata { bytes schema = 6; } -message Enum { - // Variant - uint32 option = 1; - // Variants of the enum - repeated string options = 2; -} - -message Member { - // Name of the member - string name = 1; - // Type of member - oneof member_type { - Value value = 2; - Enum enum = 3; - Model struct = 4; - } -} - message Model { - // Name of the model string name = 1; - // Members of the model repeated Member members = 2; } - message Entity { // The entity key bytes key = 1; @@ -85,7 +66,7 @@ message EntityUpdate { EntityDiff entity_diff = 2; } -message EntityQuery { +message Query { Clause clause = 1; uint32 limit = 2; uint32 offset = 3; @@ -129,14 +110,4 @@ enum ComparisonOperator { GTE = 3; LT = 4; LTE = 5; -} - -message Value { - oneof value_type { - string string_value = 1; - int64 int_value = 2; - uint64 uint_value = 3; - bool bool_value = 4; - bytes byte_value = 5; - } -} +} \ No newline at end of file diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index ec6dccc469..2f55a2cc72 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -39,7 +39,7 @@ message SubscribeEntitiesResponse { message RetrieveEntitiesRequest { // The entities to retrieve - types.EntityQuery query = 1; + types.Query query = 1; } message RetrieveEntitiesResponse { diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index e4097849c3..0e2ef98e1b 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -1,13 +1,17 @@ //! Client implementation for the gRPC service. +use std::num::ParseIntError; + use futures_util::stream::MapOk; use futures_util::{Stream, StreamExt, TryStreamExt}; use proto::world::{world_client, SubscribeEntitiesRequest}; -use starknet::core::types::{FromStrError, StateUpdate}; +use starknet::core::types::{FromByteSliceError, FromStrError, StateUpdate}; use starknet_crypto::FieldElement; -use crate::proto::world::{MetadataRequest, SubscribeEntitiesResponse}; +use crate::proto::world::{ + MetadataRequest, RetrieveEntitiesRequest, RetrieveEntitiesResponse, SubscribeEntitiesResponse, +}; use crate::proto::{self}; -use crate::types::KeysClause; +use crate::types::{KeysClause, Query}; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -15,8 +19,14 @@ pub enum Error { Grpc(tonic::Status), #[error("Missing expected data")] MissingExpectedData, + #[error("Unsupported type")] + UnsupportedType, + #[error(transparent)] + ParseStr(FromStrError), + #[error(transparent)] + SliceError(FromByteSliceError), #[error(transparent)] - Parsing(FromStrError), + ParseInt(ParseIntError), #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] @@ -61,7 +71,15 @@ impl WorldClient { .await .map_err(Error::Grpc) .and_then(|res| res.into_inner().metadata.ok_or(Error::MissingExpectedData)) - .and_then(|metadata| metadata.try_into().map_err(Error::Parsing)) + .and_then(|metadata| metadata.try_into().map_err(Error::ParseStr)) + } + + pub async fn retrieve_entities( + &mut self, + query: Query, + ) -> Result { + let request = RetrieveEntitiesRequest { query: Some(query.into()) }; + self.inner.retrieve_entities(request).await.map_err(Error::Grpc).map(|res| res.into_inner()) } /// Subscribe to the state diff for a set of entities of a World. diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 398343b9c7..dccb730239 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -128,15 +128,21 @@ impl DojoWorld { .collect::, Error>>()?; let keys_pattern = keys.join("/") + "/%"; - let db_entities: Vec<(String, String)> = sqlx::query_as( - "SELECT id, model_names FROM entities WHERE keys LIKE ? ORDER BY event_id ASC LIMIT ? \ - OFFSET ?", - ) - .bind(&keys_pattern) - .bind(limit) - .bind(offset) - .fetch_all(&self.pool) - .await?; + let query = r#" + SELECT entities.id, group_concat(entity_model.model_id) as model_names + FROM entities + JOIN entity_model ON entities.id = entity_model.entity_id + WHERE entities.keys LIKE ? + GROUP BY entities.id + ORDER BY entities.event_id DESC + LIMIT ? OFFSET ? + "#; + let db_entities: Vec<(String, String)> = sqlx::query_as(query) + .bind(&keys_pattern) + .bind(limit) + .bind(offset) + .fetch_all(&self.pool) + .await?; let mut entities = Vec::new(); for (entity_id, models_str) in db_entities { @@ -153,7 +159,7 @@ impl DojoWorld { let mut models = Vec::new(); for schema in schemas { let struct_ty = schema.as_struct().expect("schema should be struct"); - models.push(Self::map_row_to_model(&schema.name(), struct_ty, &row)?); + models.push(Self::map_row_to_proto(&schema.name(), struct_ty, &row)?.into()); } let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; @@ -237,7 +243,7 @@ impl DojoWorld { async fn retrieve_entities( &self, - query: proto::types::EntityQuery, + query: proto::types::Query, ) -> Result { let clause_type = query .clause @@ -260,84 +266,95 @@ impl DojoWorld { Ok(RetrieveEntitiesResponse { entities }) } - fn map_row_to_model( + /// Helper function to map Sqlite row to proto::types::Struct + // TODO: refactor this to use `map_row_to_ty` from core and implement Ty to protobuf conversion + fn map_row_to_proto( path: &str, struct_ty: &Struct, row: &SqliteRow, - ) -> Result { - let members = struct_ty + ) -> Result { + let children = struct_ty .children .iter() .map(|member| { let column_name = format!("{}.{}", path, member.name); let name = member.name.clone(); - let member = match &member.ty { + let ty_type = match &member.ty { Ty::Primitive(primitive) => { let value_type = match primitive { - Primitive::Bool(_) => proto::types::value::ValueType::BoolValue( + Primitive::Bool(_) => Some(proto::types::value::ValueType::BoolValue( row.try_get::(&column_name)?, - ), + )), Primitive::U8(_) | Primitive::U16(_) | Primitive::U32(_) | Primitive::U64(_) | Primitive::USize(_) => { let value = row.try_get::(&column_name)?; - proto::types::value::ValueType::UintValue(value as u64) + Some(proto::types::value::ValueType::UintValue(value as u64)) } Primitive::U128(_) - | Primitive::U256(_) | Primitive::Felt252(_) | Primitive::ClassHash(_) | Primitive::ContractAddress(_) => { let value = row.try_get::(&column_name)?; - proto::types::value::ValueType::StringValue(value) + let felt = + FieldElement::from_str(&value).map_err(ParseError::FromStr)?; + Some(proto::types::value::ValueType::ByteValue( + felt.to_bytes_be().to_vec(), + )) + } + Primitive::U256(_) => { + let value = row.try_get::(&column_name)?; + Some(proto::types::value::ValueType::StringValue(value)) } }; - proto::types::Member { - name, - member_type: Some(proto::types::member::MemberType::Value( - proto::types::Value { value_type: Some(value_type) }, - )), - } + Some(proto::types::ty::TyType::Primitive(proto::types::Primitive { + value: Some(proto::types::Value { value_type }), + r#type: primitive.to_numeric() as i32, + })) } Ty::Enum(enum_ty) => { let value = row.try_get::(&column_name)?; let options = enum_ty .options .iter() - .map(|e| e.name.to_string()) - .collect::>(); + .map(|r#enum| proto::types::EnumOption { + name: r#enum.name.clone(), + ty: None, + }) + .collect::>(); let option = - options.iter().position(|o| o == &value).expect("wrong enum value") + options.iter().position(|o| o.name == value).expect("wrong enum value") as u32; - proto::types::Member { - name: enum_ty.name.clone(), - member_type: Some(proto::types::member::MemberType::Enum( - proto::types::Enum { option, options }, - )), - } + + Some(proto::types::ty::TyType::Enum(proto::types::Enum { + option, + options, + name: member.ty.name(), + })) } Ty::Struct(struct_ty) => { let path = [path, &struct_ty.name].join("$"); - proto::types::Member { - name, - member_type: Some(proto::types::member::MemberType::Struct( - Self::map_row_to_model(&path, struct_ty, row)?, - )), - } + Some(proto::types::ty::TyType::Struct(Self::map_row_to_proto( + &path, struct_ty, row, + )?)) } ty => { unimplemented!("unimplemented type_enum: {ty}"); } }; - Ok(member) + Ok(proto::types::Member { + name, + ty: Some(proto::types::Ty { ty_type }), + key: member.key, + }) }) .collect::, Error>>()?; - Ok(proto::types::Model { name: struct_ty.name.clone(), members }) + Ok(proto::types::Struct { name: struct_ty.name.clone(), children }) } } diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types.rs index c2c834cf42..632d2d763c 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types.rs @@ -1,14 +1,28 @@ use std::collections::HashMap; use std::str::FromStr; -use dojo_types::schema::Ty; +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; use serde::{Deserialize, Serialize}; use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; use starknet_crypto::FieldElement; -use crate::proto; +use crate::client::Error as ClientError; +use crate::proto::{self}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Entity { + pub key: FieldElement, + pub models: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Model { + pub name: String, + pub members: Vec, +} #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { @@ -62,7 +76,13 @@ pub enum ComparisonOperator { } #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub enum Value { +pub struct Value { + pub primitive_type: Primitive, + pub value_type: ValueType, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub enum ValueType { String(String), Int(i64), UInt(u64), @@ -105,7 +125,7 @@ impl TryFrom for dojo_types::WorldMetadata { } } -impl From for proto::types::EntityQuery { +impl From for proto::types::Query { fn from(value: Query) -> Self { Self { clause: Some(value.clause.into()), limit: value.limit, offset: value.offset } } @@ -150,6 +170,34 @@ impl TryFrom for KeysClause { } } +impl TryFrom for Entity { + type Error = ClientError; + fn try_from(entity: proto::types::Entity) -> Result { + Ok(Self { + key: FieldElement::from_byte_slice_be(&entity.key).map_err(ClientError::SliceError)?, + models: entity + .models + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for Model { + type Error = ClientError; + fn try_from(model: proto::types::Model) -> Result { + Ok(Self { + name: model.name, + members: model + .members + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + impl From for proto::types::MemberClause { fn from(value: MemberClause) -> Self { Self { @@ -173,26 +221,137 @@ impl From for proto::types::CompositeClause { impl From for proto::types::Value { fn from(value: Value) -> Self { - match value { - Value::String(val) => { - Self { value_type: Some(proto::types::value::ValueType::StringValue(val)) } + let value_type = match value.value_type { + ValueType::String(val) => Some(proto::types::value::ValueType::StringValue(val)), + ValueType::Int(val) => Some(proto::types::value::ValueType::IntValue(val)), + ValueType::UInt(val) => Some(proto::types::value::ValueType::UintValue(val)), + ValueType::Bool(val) => Some(proto::types::value::ValueType::BoolValue(val)), + ValueType::Bytes(val) => Some(proto::types::value::ValueType::ByteValue(val)), + }; + + Self { value_type } + } +} + +impl From for EnumOption { + fn from(option: proto::types::EnumOption) -> Self { + EnumOption { name: option.name, ty: Ty::Tuple(vec![]) } + } +} + +impl From for Enum { + fn from(r#enum: proto::types::Enum) -> Self { + Enum { + name: r#enum.name.clone(), + option: Some(r#enum.option as u8), + options: r#enum.options.into_iter().map(Into::into).collect::>(), + } + } +} + +impl TryFrom for Struct { + type Error = ClientError; + fn try_from(r#struct: proto::types::Struct) -> Result { + Ok(Struct { + name: r#struct.name, + children: r#struct + .children + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl From for proto::types::Model { + fn from(r#struct: proto::types::Struct) -> Self { + Self { name: r#struct.name, members: r#struct.children } + } +} + +// FIX: weird catch-22 issue - prost Enum has `try_from` trait we can use, however, using it results +// in wasm compile err about From missing. Implementing that trait results in clippy error +// about duplicate From... Workaround is to use deprecated `from_i32` and allow deprecation +// warning. +#[allow(deprecated)] +impl TryFrom for Primitive { + type Error = ClientError; + fn try_from(primitive: proto::types::Primitive) -> Result { + let primitive_type = primitive.r#type; + let value_type = primitive + .value + .ok_or(ClientError::MissingExpectedData)? + .value_type + .ok_or(ClientError::MissingExpectedData)?; + + let primitive = match &value_type { + proto::types::value::ValueType::BoolValue(bool) => Primitive::Bool(Some(*bool)), + proto::types::value::ValueType::UintValue(int) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U8) => Primitive::U8(Some(*int as u8)), + Some(proto::types::PrimitiveType::U16) => Primitive::U16(Some(*int as u16)), + Some(proto::types::PrimitiveType::U32) => Primitive::U32(Some(*int as u32)), + Some(proto::types::PrimitiveType::U64) => Primitive::U64(Some(*int)), + Some(proto::types::PrimitiveType::Usize) => Primitive::USize(Some(*int as u32)), + _ => return Err(ClientError::UnsupportedType), + } } - Value::Int(val) => { - Self { value_type: Some(proto::types::value::ValueType::IntValue(val)) } + proto::types::value::ValueType::ByteValue(bytes) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U128) + | Some(proto::types::PrimitiveType::Felt252) + | Some(proto::types::PrimitiveType::ClassHash) + | Some(proto::types::PrimitiveType::ContractAddress) => { + Primitive::Felt252(Some( + FieldElement::from_byte_slice_be(bytes) + .map_err(ClientError::SliceError)?, + )) + } + _ => return Err(ClientError::UnsupportedType), + } } - Value::UInt(val) => { - Self { value_type: Some(proto::types::value::ValueType::UintValue(val)) } + proto::types::value::ValueType::StringValue(_string) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U256) => { + // TODO: Handle u256 + Primitive::U256(None) + } + _ => return Err(ClientError::UnsupportedType), + } } - Value::Bool(val) => { - Self { value_type: Some(proto::types::value::ValueType::BoolValue(val)) } + _ => { + return Err(ClientError::UnsupportedType); } - Value::Bytes(val) => { - Self { value_type: Some(proto::types::value::ValueType::ByteValue(val)) } + }; + + Ok(primitive) + } +} + +impl TryFrom for Ty { + type Error = ClientError; + fn try_from(ty: proto::types::Ty) -> Result { + match ty.ty_type.ok_or(ClientError::MissingExpectedData)? { + proto::types::ty::TyType::Primitive(primitive) => { + Ok(Ty::Primitive(primitive.try_into()?)) } + proto::types::ty::TyType::Struct(r#struct) => Ok(Ty::Struct(r#struct.try_into()?)), + proto::types::ty::TyType::Enum(r#enum) => Ok(Ty::Enum(r#enum.into())), } } } +impl TryFrom for Member { + type Error = ClientError; + fn try_from(member: proto::types::Member) -> Result { + Ok(Member { + name: member.name, + ty: member.ty.ok_or(ClientError::MissingExpectedData)?.try_into()?, + key: member.key, + }) + } +} + impl TryFrom for StorageEntry { type Error = FromStrError; fn try_from(value: proto::types::StorageEntry) -> Result { From b06be64e253015c18a50bd8937655a0b4c1bb2e1 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 12:54:47 +0200 Subject: [PATCH 050/192] Fix devcontainer PR tagging --- .github/workflows/devcontainer.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 810e0356cf..acd7b3f6ca 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -11,6 +11,12 @@ jobs: build-and-push: runs-on: ubuntu-latest + env: + DOCKER_TAG: latest + + outputs: + tag_name: ${{ steps.release_info.outputs.tag_name }} + steps: - name: Checkout repository uses: actions/checkout@v2 @@ -27,13 +33,20 @@ jobs: - name: Set Docker tag for release event if: github.event_name == 'release' - run: echo "DOCKER_TAG=${{ github.event.release.tag_name }}" >> $GITHUB_ENV + run: | + echo "DOCKER_TAG=${{ github.event.release.tag_name }}" >> $GITHUB_ENV + echo "tag_name=$DOCKER_TAG" >> $GITHUB_OUTPUT - name: Set Docker tag for push event if: github.event_name == 'push' run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c 1-7) echo "DOCKER_TAG=$SHORT_SHA" >> $GITHUB_ENV + + - name: Set outputs + id: release_info + run: | + echo "tag_name=${{ env.DOCKER_TAG }}" >> $GITHUB_OUTPUT - name: Build and push Docker image uses: docker/build-push-action@v2 @@ -58,7 +71,7 @@ jobs: - name: Update devcontainer.json run: | - sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.DOCKER_TAG }}|" .devcontainer/devcontainer.json + sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - name: Setup Git credentials run: | @@ -68,14 +81,15 @@ jobs: - name: Commit and push changes run: | git add .devcontainer/devcontainer.json - git commit -m "Update devcontainer image hash: ${{needs.build-and-push.outputs.DOCKER_TAG}}" - git push -b devcontainer-${{needs.build-and-push.outputs.DOCKER_TAG}} + git commit -m "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" + git checkout -b devcontainer-${{ needs.build-and-push.outputs.tag_name }} + git push --set-upstream origin devcontainer--${{ needs.build-and-push.outputs.tag_name }} - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - title: "Update devcontainer image hash: ${{needs.build-and-push.outputs.DOCKER_TAG}}" + title: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" commit-message: "Update devcontainer image hash" branch: update-devcontainer-image-hash base: main From 724fbe82d6a6f7d7e5686c4986e33c5bbd7c38b2 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 18:34:42 +0200 Subject: [PATCH 051/192] Fix amd64 docker target --- .github/workflows/release.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa58af603f..4a14f85788 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -232,8 +232,8 @@ jobs: const prunePrereleases = require('./.github/scripts/prune-prereleases.js') await prunePrereleases({github, context}) - docker-build-and-push-linux-arm64: - name: Build and push linux-arm64 docker image + docker-build-and-push-linux-amd64: + name: Build and push linux-amd64 docker image runs-on: ubuntu-latest-4-cores needs: [prepare, release] @@ -260,15 +260,16 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v4 with: - push: true - tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} - platforms: linux/arm64 - target: arm64 build-contexts: | artifacts=artifacts + push: true + provenance: false + tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} + platforms: linux/amd64 + target: amd64 - docker-build-and-push-linux-amd64: - name: Build and push linux-amd64 docker image + docker-build-and-push-linux-arm64: + name: Build and push linux-arm64 docker image runs-on: ubuntu-latest-4-cores needs: [prepare, release] @@ -295,9 +296,11 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v4 with: - build-contexts: | - artifacts=artifacts push: true + provenance: false tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} - platforms: linux/amd64 - target: amd64 + platforms: linux/arm64 + target: arm64 + build-contexts: | + artifacts=artifacts + From 6dd5d12529c927ca9ad744f5979a92ef19dd0816 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 20:17:09 +0200 Subject: [PATCH 052/192] Use rustls-tls in bench crate --- Cargo.lock | 814 +++++++++++++++++++------------------- crates/benches/Cargo.toml | 12 +- 2 files changed, 408 insertions(+), 418 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57c8f1433e..5798ee0cf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,9 +345,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-compression" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "brotli", "flate2", @@ -361,9 +361,9 @@ dependencies = [ [[package]] name = "async-graphql" -version = "6.0.9" +version = "6.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117113a7ff4a98f2a864fa7a5274033b0907fce65dc8464993c75033f8074f90" +checksum = "298a5d587d6e6fdb271bf56af2dc325a80eb291fd0fc979146584b9a05494a8c" dependencies = [ "async-graphql-derive", "async-graphql-parser", @@ -378,7 +378,7 @@ dependencies = [ "futures-util", "handlebars", "http", - "indexmap 2.0.2", + "indexmap 2.1.0", "mime", "multer", "num-traits 0.2.17", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "6.0.9" +version = "6.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4bb7b7b2344d24af860776b7fe4e4ee4a67cd965f076048d023f555703b854" +checksum = "c7f329c7eb9b646a72f70c9c4b516c70867d356ec46cb00dcac8ad343fd006b0" dependencies = [ "Inflector", "async-graphql-parser", @@ -406,15 +406,15 @@ dependencies = [ "proc-macro2", "quote", "strum 0.25.0", - "syn 2.0.38", + "syn 2.0.39", "thiserror", ] [[package]] name = "async-graphql-parser" -version = "6.0.9" +version = "6.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47e1c1ff6cb7cae62c9cd768d76475cc68f156d8234b024fd2499ad0e91da21" +checksum = "6139181845757fd6a73fbb8839f3d036d7150b798db0e9bb3c6e83cdd65bd53b" dependencies = [ "async-graphql-value", "pest", @@ -424,21 +424,21 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "6.0.9" +version = "6.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2270df3a642efce860ed06fbcf61fc6db10f83c2ecb5613127fb453c82e012a4" +checksum = "323a5143f5bdd2030f45e3f2e0c821c9b1d36e79cf382129c64299c50a7f3750" dependencies = [ "bytes", - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_json", ] [[package]] name = "async-graphql-warp" -version = "6.0.9" +version = "6.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d54ac2c41da8f2bc2b5976227d585e787b55f696660d26fd6e1bee50e740f98" +checksum = "fa68237ec9f2190cae295122ba45612b992658fbc372d142c6c6981cc70a9eac" dependencies = [ "async-graphql", "futures-util", @@ -454,7 +454,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -476,7 +476,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -487,7 +487,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", "regex-automata 0.4.3", @@ -1027,7 +1027,7 @@ dependencies = [ "log", "lsp-types", "salsa", - "scarb-metadata 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scarb-metadata 1.9.0", "serde", "serde_json", "tokio", @@ -1050,7 +1050,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-utils", "id-arena", - "indexmap 2.0.2", + "indexmap 2.1.0", "itertools 0.11.0", "log", "num-bigint", @@ -1106,7 +1106,7 @@ checksum = "d2bbbfe1934e11fe3cce4f23cdccd22341ed63af5d76e593234288dd4ba06f56" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1410,7 +1410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15edcd2fba78af9b753614885464c9b5bf6041b7decba46587c2b0babc4197ac" dependencies = [ "env_logger", - "indexmap 2.0.2", + "indexmap 2.1.0", "itertools 0.11.0", "log", "num-bigint", @@ -1431,7 +1431,7 @@ dependencies = [ "bitvec", "cairo-felt", "generic-array", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "hex", "keccak", "lazy_static", @@ -1461,25 +1461,11 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.17.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" dependencies = [ - "camino", - "cargo-platform", - "semver", "serde", - "serde_json", - "thiserror", ] [[package]] @@ -1539,9 +1525,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ "clap_builder", "clap_derive", @@ -1559,9 +1545,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" dependencies = [ "anstream", "anstyle", @@ -1587,7 +1573,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1768,9 +1754,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -1778,9 +1764,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core2" @@ -1811,9 +1797,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" @@ -1910,9 +1896,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", @@ -1938,7 +1924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2005,7 +1991,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2027,7 +2013,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2037,7 +2023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core 0.9.9", @@ -2045,15 +2031,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2061,9 +2047,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2449,15 +2435,15 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", @@ -2478,9 +2464,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -2539,9 +2525,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -2567,12 +2553,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2647,9 +2633,9 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad13497f6e0a24292fc7b408e30d22fe9dc262da1f40d7b542c3a44e7fc0476" +checksum = "1a5344eea9b20effb5efeaad29418215c4d27017639fd1f908260f59cbbd226e" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2663,9 +2649,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e9e8acd0ed348403cc73a670c24daba3226c40b98dc1a41903766b3ab6240a" +checksum = "8c405f24ea3a517899ba7985385c43dc4a7eb1209af3b1e0a1a32d7dcc7f8d09" dependencies = [ "ethers-core", "once_cell", @@ -2675,9 +2661,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d79269278125006bb0552349c03593ffa9702112ca88bc7046cc669f148fb47c" +checksum = "0111ead599d17a7bff6985fd5756f39ca7033edc79a31b23026a8d5d64fa95cd" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2694,9 +2680,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95a43c939b2e4e2f3191c5ad4a1f279780b8a39139c9905b43a7433531e2ab" +checksum = "51258120c6b47ea9d9bec0d90f9e8af71c977fbefbef8213c91bfed385fe45eb" dependencies = [ "Inflector", "const-hex", @@ -2711,16 +2697,16 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.38", - "toml 0.7.8", + "syn 2.0.39", + "toml 0.8.8", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9ce44906fc871b3ee8c69a695ca7ec7f70e50cb379c9b9cb5e532269e492f6" +checksum = "936e7a0f1197cee2b62dc89f63eff3201dbf87c283ff7e18d86d38f83b845483" dependencies = [ "Inflector", "const-hex", @@ -2729,18 +2715,18 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "ethers-core" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a17f0708692024db9956b31d7a20163607d2745953f5ae8125ab368ba280ad" +checksum = "2f03e0bdc216eeb9e355b90cf610ef6c5bb8aca631f97b5ae9980ce34ea7878d" dependencies = [ "arrayvec", "bytes", - "cargo_metadata 0.17.0", + "cargo_metadata", "chrono", "const-hex", "elliptic-curve", @@ -2755,7 +2741,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.38", + "syn 2.0.39", "tempfile", "thiserror", "tiny-keccak", @@ -2764,10 +2750,11 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e53451ea4a8128fbce33966da71132cf9e1040dcfd2a2084fd7733ada7b2045" +checksum = "abbac2c890bdbe0f1b8e549a53b00e2c4c1de86bb077c1094d1f38cdf9381a56" dependencies = [ + "chrono", "ethers-core", "reqwest", "semver", @@ -2779,9 +2766,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473f1ccd0c793871bbc248729fa8df7e6d2981d6226e4343e3bbaa9281074d5d" +checksum = "681ece6eb1d10f7cf4f873059a77c04ff1de4f35c63dd7bccde8f438374fcb93" dependencies = [ "async-trait", "auto_impl", @@ -2806,9 +2793,9 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6838fa110e57d572336178b7c79e94ff88ef976306852d8cb87d9e5b1fc7c0b5" +checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816" dependencies = [ "async-trait", "auto_impl", @@ -2843,9 +2830,9 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea44bec930f12292866166f9ddbea6aa76304850e4d8dcd66dc492b43d00ff1" +checksum = "0cb1b714e227bbd2d8c53528adb580b203009728b17d0d0e4119353aa9bc5532" dependencies = [ "async-trait", "coins-bip32", @@ -2862,9 +2849,9 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de34e484e7ae3cab99fbfd013d6c5dc7f9013676a4e0e414d8b12e1213e8b3ba" +checksum = "a64f710586d147864cff66540a6d64518b9ff37d73ef827fee430538265b595f" dependencies = [ "cfg-if", "const-hex", @@ -2900,9 +2887,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" dependencies = [ "indenter", "once_cell", @@ -3006,26 +2993,11 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3142,7 +3114,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3196,9 +3168,9 @@ dependencies = [ [[package]] name = "genco" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fd234893ffe9cf5b81224ebb1d21bbe2eeb94d95bac3ea25c97cba7293304d" +checksum = "98d7af598790738fee616426e669360fa361273b1b9c9b7f30c92fa627605cad" dependencies = [ "genco-macros", "relative-path", @@ -3207,13 +3179,13 @@ dependencies = [ [[package]] name = "genco-macros" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1c8cd3de2f32ee05ba2adaa90f8d0c354ffa0adeb2d186978d7ae70e5025e9" +checksum = "d4cf186fea4af17825116f72932fe52cce9a13bae39ff63b4dc0cfdb3fb4bde1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3229,9 +3201,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -3242,9 +3214,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix" @@ -3559,7 +3531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16" dependencies = [ "gix-hash", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "parking_lot 0.12.1", ] @@ -3617,7 +3589,7 @@ checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4015,15 +3987,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -4072,9 +4044,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -4082,7 +4054,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -4091,9 +4063,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" dependencies = [ "log", "pest", @@ -4123,9 +4095,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash 0.8.6", "allocator-api2", @@ -4147,14 +4119,14 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] name = "hdrhistogram" -version = "7.5.2" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ "byteorder", "num-traits 0.2.17", @@ -4234,9 +4206,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -4356,7 +4328,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.8", + "rustls 0.21.9", "tokio", "tokio-rustls 0.24.1", ] @@ -4373,19 +4345,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.58" @@ -4423,9 +4382,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -4433,17 +4392,16 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" dependencies = [ + "crossbeam-deque", "globset", - "lazy_static", "log", "memchr", - "regex", + "regex-automata 0.4.3", "same-file", - "thread_local", "walkdir", "winapi-util", ] @@ -4540,12 +4498,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "serde", ] @@ -4725,9 +4683,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -4846,9 +4804,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" dependencies = [ "cfg-if", "ecdsa", @@ -5008,9 +4966,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" @@ -5028,6 +4986,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "libsqlite3-sys" version = "0.24.2" @@ -5041,9 +5010,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -5288,24 +5257,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndarray" version = "0.13.1" @@ -5498,7 +5449,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5568,50 +5519,12 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "openssl" -version = "0.10.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -5645,9 +5558,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parity-scale-codec" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec", "bitvec", @@ -5659,11 +5572,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -5797,9 +5710,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -5832,7 +5745,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5853,7 +5766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.2", + "indexmap 2.1.0", ] [[package]] @@ -5896,7 +5809,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5940,7 +5853,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -6050,7 +5963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -6112,9 +6025,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -6131,9 +6044,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", @@ -6143,7 +6056,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", @@ -6161,12 +6074,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive 0.12.1", + "prost-derive 0.12.3", ] [[package]] @@ -6193,9 +6106,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", "heck 0.4.1", @@ -6205,10 +6118,10 @@ dependencies = [ "once_cell", "petgraph", "prettyplease 0.2.15", - "prost 0.12.1", - "prost-types 0.12.1", + "prost 0.12.3", + "prost-types 0.12.3", "regex", - "syn 2.0.38", + "syn 2.0.39", "tempfile", "which", ] @@ -6228,15 +6141,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -6250,11 +6163,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "prost 0.12.1", + "prost 0.12.3", ] [[package]] @@ -6372,12 +6285,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] @@ -6453,30 +6366,27 @@ dependencies = [ "http-body", "hyper", "hyper-rustls 0.24.2", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "winreg", ] @@ -6507,9 +6417,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" dependencies = [ "cc", "getrandom", @@ -6579,15 +6489,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6604,12 +6514,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", - "ring 0.17.5", + "ring 0.17.6", "rustls-webpki", "sct", ] @@ -6628,9 +6538,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64 0.21.5", ] @@ -6641,7 +6551,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", + "ring 0.17.6", "untrusted 0.9.0", ] @@ -6778,7 +6688,7 @@ dependencies = [ "pathdiff", "petgraph", "scarb-build-metadata", - "scarb-metadata 1.8.0 (git+https://github.com/software-mansion/scarb?rev=0c8def3)", + "scarb-metadata 1.8.0", "scarb-ui", "semver", "serde", @@ -6792,10 +6702,10 @@ dependencies = [ "tar", "thiserror", "tokio", - "toml 0.8.6", + "toml 0.8.8", "toml_edit 0.20.7", "tracing", - "tracing-log", + "tracing-log 0.1.4", "tracing-subscriber", "typed-builder", "url", @@ -6812,16 +6722,16 @@ name = "scarb-build-metadata" version = "2.3.1" source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" dependencies = [ - "cargo_metadata 0.18.1", + "cargo_metadata", ] [[package]] name = "scarb-metadata" version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1939d4a36ba77bc4a23024f42e16e307b74fe451d8efa23aff8916fdde6a83" +source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" dependencies = [ "camino", + "derive_builder", "semver", "serde", "serde_json", @@ -6830,11 +6740,11 @@ dependencies = [ [[package]] name = "scarb-metadata" -version = "1.8.0" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf294b35e5abed4510b98150fbdfad402111cb05532b38d8569a1c3edea6d1a6" dependencies = [ "camino", - "derive_builder", "semver", "serde", "serde_json", @@ -6851,7 +6761,7 @@ dependencies = [ "clap", "console", "indicatif", - "scarb-metadata 1.8.0 (git+https://github.com/software-mansion/scarb?rev=0c8def3)", + "scarb-metadata 1.8.0", "serde", "serde_json", ] @@ -6867,9 +6777,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -6880,9 +6790,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -6920,7 +6830,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", + "ring 0.17.6", "untrusted 0.9.0", ] @@ -6984,9 +6894,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -7013,13 +6923,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7063,7 +6973,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7112,7 +7022,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7147,7 +7057,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7231,9 +7141,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core", @@ -7274,9 +7184,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smol_str" @@ -7347,9 +7257,9 @@ dependencies = [ [[package]] name = "solang-parser" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb9fa2fa2fa6837be8a2495486ff92e3ffe68a99b6eeba288e139efdd842457" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" dependencies = [ "itertools 0.11.0", "lalrpop", @@ -7399,7 +7309,7 @@ dependencies = [ "thiserror", "tokio", "tracing", - "tracing-log", + "tracing-log 0.1.4", "url", ] @@ -7420,9 +7330,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -7505,7 +7415,7 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "uuid 1.5.0", + "uuid 1.6.1", "webpki-roots 0.22.6", ] @@ -7560,9 +7470,9 @@ dependencies = [ [[package]] name = "starknet-accounts" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a54da2cb18f8083845c0d3bbe1d68758246b94db4b7171e1cdbdc09cefd4b0c" +checksum = "c7062b020f65d9da7f9dd9f1d97bfb644e881cda8ddb999799a799e6f2e408dd" dependencies = [ "async-trait", "auto_impl", @@ -7589,9 +7499,9 @@ dependencies = [ [[package]] name = "starknet-core" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260537859a788fec3e262a656fe728c6f7b4bf6a432115bace62f047ea184eeb" +checksum = "7f1683ca7c63f0642310eddedb7d35056d8306084dff323d440711065c63ed87" dependencies = [ "base64 0.21.5", "flate2", @@ -7653,7 +7563,7 @@ checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ "starknet-curve 0.4.0", "starknet-ff", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7676,9 +7586,9 @@ dependencies = [ [[package]] name = "starknet-ff" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2cb1d9c0a50380cddab99cb202c6bfb3332728a2769bd0ca2ee80b0b390dd4" +checksum = "7584bc732e4d2a8ccebdd1dda8236f7940a79a339e30ebf338d45c329659e36c" dependencies = [ "ark-ff", "bigdecimal", @@ -7696,7 +7606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f66fe05edab7ee6752a0aff3e14508001191083f3c6d0b6fa14f7008a96839b0" dependencies = [ "starknet-core", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7826,7 +7736,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7837,9 +7747,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svm-rs" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0cc95be7cc2c384a2f57cac56548d2178650905ebe5725bc8970ccc25529060" +checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" dependencies = [ "dirs 5.0.1", "fs2", @@ -7868,9 +7778,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -7959,9 +7869,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -7980,7 +7890,7 @@ checksum = "f66edd6b6cd810743c0c71e1d085e92b01ce6a72782032e3f794c8284fe4bcdd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8000,7 +7910,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8090,9 +8000,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -8119,23 +8029,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn 2.0.39", ] [[package]] @@ -8155,7 +8055,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.8", + "rustls 0.21.9", "tokio", ] @@ -8178,11 +8078,11 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.8", + "rustls 0.21.9", "tokio", "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", ] [[package]] @@ -8223,14 +8123,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.7", + "toml_edit 0.21.0", ] [[package]] @@ -8248,7 +8148,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -8261,7 +8161,20 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -8309,7 +8222,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.1", + "prost 0.12.3", "tokio", "tokio-stream", "tower", @@ -8339,9 +8252,9 @@ checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ "prettyplease 0.2.15", "proc-macro2", - "prost-build 0.12.1", + "prost-build 0.12.3", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8350,8 +8263,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fa37c513df1339d197f4ba21d28c918b9ef1ac1768265f11ecb6b7f1cba1b76" dependencies = [ - "prost 0.12.1", - "prost-types 0.12.1", + "prost 0.12.3", + "prost-types 0.12.3", "tokio", "tokio-stream", "tonic 0.10.2", @@ -8414,7 +8327,7 @@ dependencies = [ "futures-util", "parking_lot 0.12.1", "prost 0.11.9", - "prost 0.12.1", + "prost 0.12.3", "serde", "serde_json", "starknet", @@ -8514,7 +8427,7 @@ dependencies = [ "hyper", "parking_lot 0.12.1", "prost 0.11.9", - "prost 0.12.1", + "prost 0.12.3", "rand", "rayon", "serde", @@ -8627,7 +8540,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid 1.5.0", + "uuid 1.6.1", ] [[package]] @@ -8696,7 +8609,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8730,6 +8643,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -8742,9 +8666,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -8757,7 +8681,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] @@ -8780,7 +8704,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.21.8", + "rustls 0.21.9", "sha1", "thiserror", "url", @@ -8804,7 +8728,7 @@ checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8848,9 +8772,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unescaper" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96a44ae11e25afb520af4534fd7b0bd8cd613e35a78def813b8cf41631fa3c8" +checksum = "d8f0f68e58d297ba8b22b8b5a96a87b863ba6bb46aaf51e19a4b02c5a6dd5b7f" dependencies = [ "thiserror", ] @@ -8872,9 +8796,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bom" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" @@ -8935,9 +8859,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -8969,9 +8893,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", ] @@ -9061,9 +8985,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -9071,24 +8995,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -9098,9 +9022,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9108,22 +9032,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-streams" @@ -9140,9 +9064,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -9154,7 +9078,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.5", + "ring 0.17.6", "untrusted 0.9.0", ] @@ -9169,9 +9093,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "which" @@ -9252,6 +9176,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9282,6 +9215,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9294,6 +9242,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9306,6 +9260,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9318,6 +9278,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9330,6 +9296,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9342,6 +9314,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9354,6 +9332,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9366,11 +9350,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -9451,29 +9441,29 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.20" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd66a62464e3ffd4e37bd09950c2b9dd6c4f8767380fabba0d523f9a775bc85a" +checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.20" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255c4596d41e6916ced49cfafea18727b24d67878fa180ddfd69b9df34fd1726" +checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -9486,7 +9476,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml index 6e98071c43..010ab448f7 100644 --- a/crates/benches/Cargo.toml +++ b/crates/benches/Cargo.toml @@ -1,19 +1,19 @@ [package] +edition = "2021" name = "benches" version = "0.1.0" -edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proptest = "1.3.1" -reqwest = { version="0.11.18", features=["blocking", "json"] } clap_builder = "4.4.6" +proptest = "1.3.1" +reqwest = { version = "0.11.18", features = [ "blocking", "json", "rustls-tls" ], default-features = false } -sozo = { path = "../sozo" } -starknet.workspace = true anyhow.workspace = true +futures.workspace = true hex.workspace = true lazy_static.workspace = true -futures.workspace = true +sozo = { path = "../sozo" } +starknet.workspace = true tokio.workspace = true From 112cce746a22a5e60aa0f99061c9dd2b4f68d426 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 1 Dec 2023 23:16:52 +0200 Subject: [PATCH 053/192] Update release ci approach to use a single docker build step --- .github/workflows/release.yml | 75 ++++++++++------------------------- Dockerfile | 20 +++------- 2 files changed, 27 insertions(+), 68 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a14f85788..ef0597e95e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,6 +81,10 @@ jobs: name: ${{ matrix.job.target }} (${{ matrix.job.os }}) needs: prepare runs-on: ${{ matrix.job.os }} + env: + PLATFORM_NAME: ${{ matrix.job.platform }} + TARGET: ${{ matrix.job.target }} + ARCH: ${{ matrix.job.arch }} strategy: matrix: job: @@ -148,9 +152,6 @@ jobs: - name: Archive binaries id: artifacts env: - PLATFORM_NAME: ${{ matrix.job.platform }} - TARGET: ${{ matrix.job.target }} - ARCH: ${{ matrix.job.arch }} VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} run: | if [ "$PLATFORM_NAME" == "linux" ]; then @@ -193,18 +194,22 @@ jobs: files: | ${{ steps.artifacts.outputs.file_name }} + # We move binaries so they match $TARGETPLATFORM in the Docker build + - name: Move Binaries + if: ${{ env.PLATFORM_NAME == 'linux' }} + run: | + mkdir -p $PLATFORM_NAME/$ARCH + mv target/$TARGET/release/katana $PLATFORM_NAME/$ARCH + mv target/$TARGET/release/sozo $PLATFORM_NAME/$ARCH + mv target/$TARGET/release/torii $PLATFORM_NAME/$ARCH + shell: bash + # Upload these for use with the Docker build later - name: Upload binaries uses: actions/upload-artifact@v3 with: name: binaries - path: | - target/x86_64-unknown-linux-gnu/release/katana - target/x86_64-unknown-linux-gnu/release/sozo - target/x86_64-unknown-linux-gnu/release/torii - target/aarch64-unknown-linux-gnu/release/katana - target/aarch64-unknown-linux-gnu/release/sozo - target/aarch64-unknown-linux-gnu/release/torii + path: ${{ env.PLATFORM_NAME }} retention-days: 1 cleanup: @@ -232,8 +237,8 @@ jobs: const prunePrereleases = require('./.github/scripts/prune-prereleases.js') await prunePrereleases({github, context}) - docker-build-and-push-linux-amd64: - name: Build and push linux-amd64 docker image + docker-build-and-push: + name: Build and push docker image runs-on: ubuntu-latest-4-cores needs: [prepare, release] @@ -245,43 +250,10 @@ jobs: uses: actions/download-artifact@v3 with: name: binaries - path: artifacts - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push docker image - uses: docker/build-push-action@v4 - with: - build-contexts: | - artifacts=artifacts - push: true - provenance: false - tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} - platforms: linux/amd64 - target: amd64 - - docker-build-and-push-linux-arm64: - name: Build and push linux-arm64 docker image - runs-on: ubuntu-latest-4-cores - needs: [prepare, release] - - steps: - - name: Checkout repository - uses: actions/checkout@v2 + path: artifacts/linux - - name: Download binaries - uses: actions/download-artifact@v3 - with: - name: binaries - path: artifacts + - name: Display structure of downloaded files + run: ls -R - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -294,13 +266,10 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v3 with: push: true - provenance: false tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} - platforms: linux/arm64 - target: arm64 + platforms: linux/amd64,linux/arm64 build-contexts: | artifacts=artifacts - diff --git a/Dockerfile b/Dockerfile index 5d959e7fa8..18c996cead 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,15 @@ FROM debian:bookworm-slim as base +ARG TARGETPLATFORM + LABEL description="Dojo is a provable game engine and toolchain for building onchain games and autonomous worlds with Cairo" \ authors="tarrence " \ source="https://github.com/dojoengine/dojo" \ documentation="https://book.dojoengine.org/" -FROM base as amd64 - -COPY --from=artifacts x86_64-unknown-linux-gnu/release/katana /usr/local/bin/katana -COPY --from=artifacts x86_64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo -COPY --from=artifacts x86_64-unknown-linux-gnu/release/torii /usr/local/bin/torii - -RUN chmod +x /usr/local/bin/katana \ - && chmod +x /usr/local/bin/sozo \ - && chmod +x /usr/local/bin/torii - -FROM base as arm64 - -COPY --from=artifacts aarch64-unknown-linux-gnu/release/katana /usr/local/bin/katana -COPY --from=artifacts aarch64-unknown-linux-gnu/release/sozo /usr/local/bin/sozo -COPY --from=artifacts aarch64-unknown-linux-gnu/release/torii /usr/local/bin/torii +COPY --from=artifacts $TARGETPLATFORM/katana /usr/local/bin/katana +COPY --from=artifacts $TARGETPLATFORM/sozo /usr/local/bin/sozo +COPY --from=artifacts $TARGETPLATFORM/torii /usr/local/bin/torii RUN chmod +x /usr/local/bin/katana \ && chmod +x /usr/local/bin/sozo \ From cdbaca4121b1642e1a8ca40d6831bad73d28ed26 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 4 Dec 2023 08:26:49 -0500 Subject: [PATCH 054/192] Prepare v0.3.15 --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 4 +-- examples/spawn-and-move/Scarb.toml | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5798ee0cf3..86032b56cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2267,7 +2267,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2336,7 +2336,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "starknet", @@ -2344,7 +2344,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "assert_fs", @@ -2375,7 +2375,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.14" +version = "0.3.15" dependencies = [ "crypto-bigint", "hex", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "assert_fs", @@ -4818,7 +4818,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.14" +version = "0.3.15" dependencies = [ "assert_matches", "clap", @@ -4836,7 +4836,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "assert_matches", @@ -4867,7 +4867,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "assert_matches", @@ -7271,7 +7271,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "assert_fs", @@ -8316,7 +8316,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.14" +version = "0.3.15" dependencies = [ "async-trait", "camino", @@ -8342,7 +8342,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "async-trait", @@ -8378,7 +8378,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "async-graphql", @@ -8416,7 +8416,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.14" +version = "0.3.15" dependencies = [ "bytes", "crypto-bigint", @@ -8454,7 +8454,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index a4085f9557..f1254fa576 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.14" +version = "0.3.15" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index f862d12167..e823bee6d9 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.3.14" +version = "0.3.15" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 1cda391372..aea83290b9 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.14" +version = "0.3.15" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 348228fbc6..3b99dabe1d 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.14" +version = "0.3.15" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 87ccac7512..6d7f9768f6 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,14 +3,14 @@ version = 1 [[package]] name = "dojo" -version = "0.3.13" +version = "0.3.15" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.13" +version = "0.3.15" dependencies = [ "dojo", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 5c80c1f677..876b73d7ba 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.14" +version = "0.3.15" [cairo] sierra-replace-ids = true From a4b708f098c1909b0e2e7e0bdca1878bd73df9b0 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 5 Dec 2023 14:07:54 +0800 Subject: [PATCH 055/192] fix(sozo-migrate): change call block to pending (#1239) --- crates/dojo-world/src/migration/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/dojo-world/src/migration/mod.rs b/crates/dojo-world/src/migration/mod.rs index f58c2ba662..0343284de8 100644 --- a/crates/dojo-world/src/migration/mod.rs +++ b/crates/dojo-world/src/migration/mod.rs @@ -162,7 +162,7 @@ pub trait Deployable: Declarable + Sync { calldata: vec![], entry_point_selector: get_selector_from_name("base").unwrap(), }, - BlockId::Tag(BlockTag::Latest), + BlockId::Tag(BlockTag::Pending), ) .await .map_err(MigrationError::Provider)?; From c6d82975de97e1ae68526ad2ef0d8d37905927ba Mon Sep 17 00:00:00 2001 From: GianMarco Date: Tue, 5 Dec 2023 14:29:39 -0500 Subject: [PATCH 056/192] Refactor model members in GraphQL to camelCase (#1215) * Implement camelCase convetion * update susbcriptions and events * WIP: metadata test * fmt * fix merge * pass metadata test * tests models, WIP * refactor subscriptions and events to mixed_case format * Pass models_tests * pass entities_test * clean * keep cover_uri to snake_case * fix models format * add dependency for camelCase --- Cargo.lock | 1 + crates/torii/graphql/Cargo.toml | 5 ++- crates/torii/graphql/src/mapping.rs | 44 +++++++++---------- .../graphql/src/object/connection/mod.rs | 8 ++-- .../src/object/connection/page_info.rs | 8 ++-- crates/torii/graphql/src/object/entity.rs | 7 +-- crates/torii/graphql/src/object/event.rs | 4 +- .../torii/graphql/src/object/metadata/mod.rs | 4 +- crates/torii/graphql/src/object/model.rs | 6 +-- crates/torii/graphql/src/query/mod.rs | 8 +++- .../torii/graphql/src/tests/entities_test.rs | 12 ++--- .../torii/graphql/src/tests/metadata_test.rs | 17 ++++--- crates/torii/graphql/src/tests/mod.rs | 31 +++---------- crates/torii/graphql/src/tests/models_test.rs | 12 ++--- .../graphql/src/tests/subscription_test.rs | 30 ++++++------- 15 files changed, 92 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86032b56cd..f2f30351fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8388,6 +8388,7 @@ dependencies = [ "base64 0.21.5", "camino", "chrono", + "convert_case 0.6.0", "dojo-test-utils", "dojo-types", "dojo-world", diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml index 242bf96213..2b7854c59a 100644 --- a/crates/torii/graphql/Cargo.toml +++ b/crates/torii/graphql/Cargo.toml @@ -16,6 +16,7 @@ async-recursion = "1.0.5" async-trait.workspace = true base64.workspace = true chrono.workspace = true +convert_case = "0.6.0" dojo-types = { path = "../../dojo-types" } lazy_static.workspace = true scarb-ui.workspace = true @@ -28,8 +29,8 @@ thiserror.workspace = true tokio-stream = "0.1.11" tokio-util = "0.7.7" tokio.workspace = true -torii-core = { path = "../core" } toml.workspace = true +torii-core = { path = "../core" } tracing.workspace = true url.workspace = true warp.workspace = true @@ -39,7 +40,7 @@ camino.workspace = true dojo-test-utils = { path = "../../dojo-test-utils", features = [ "build-examples" ] } dojo-world = { path = "../../dojo-world" } scarb.workspace = true +serial_test = "2.0.0" sozo = { path = "../../sozo" } starknet-crypto.workspace = true starknet.workspace = true -serial_test = "2.0.0" diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index c780369fd9..64c49aa0ab 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -11,13 +11,13 @@ lazy_static! { pub static ref ENTITY_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), (Name::new("keys"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))), - (Name::new("event_id"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("eventId"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( - Name::new("created_at"), + Name::new("createdAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), ( - Name::new("updated_at"), + Name::new("updatedAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), ]); @@ -26,35 +26,35 @@ lazy_static! { (Name::new("keys"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))), (Name::new("data"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))), ( - Name::new("created_at"), + Name::new("createdAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), - (Name::new("transaction_hash"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("transactionHash"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]); pub static ref MODEL_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( - Name::new("class_hash"), + Name::new("classHash"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), ), ( - Name::new("transaction_hash"), + Name::new("transactionHash"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), ), ( - Name::new("created_at"), + Name::new("createdAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), ]); pub static ref TRANSACTION_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), ( - Name::new("transaction_hash"), + Name::new("transactionHash"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( - Name::new("sender_address"), + Name::new("senderAddress"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( @@ -62,7 +62,7 @@ lazy_static! { TypeData::Simple(TypeRef::named_list(Primitive::Felt252(None).to_string())) ), ( - Name::new("max_fee"), + Name::new("maxFee"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( @@ -74,21 +74,18 @@ lazy_static! { TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( - Name::new("created_at"), + Name::new("createdAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), ]); pub static ref PAGE_INFO_TYPE_MAPPING: TypeMapping = TypeMapping::from([ - (Name::new("has_previous_page"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))), - (Name::new("has_next_page"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))), + (Name::new("hasPreviousPage"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))), + (Name::new("hasNextPage"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))), ( - Name::new("start_cursor"), - TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())), - ), - ( - Name::new("end_cursor"), + Name::new("startCursor"), TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())), ), + (Name::new("endCursor"), TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())),), ]); pub static ref SOCIAL_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), @@ -102,6 +99,7 @@ lazy_static! { (Name::new("cover_uri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("socials"), TypeData::Simple(TypeRef::named_list(SOCIAL_TYPE_NAME))) ]); + // Todo: refactor this to use the same type as the one in dojo-world pub static ref METADATA_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), (Name::new("uri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), @@ -109,14 +107,14 @@ lazy_static! { Name::new("content"), TypeData::Nested((TypeRef::named(CONTENT_TYPE_NAME), IndexMap::new())) ), - (Name::new("icon_img"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("cover_img"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("iconImg"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("coverImg"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( - Name::new("created_at"), + Name::new("createdAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())) ), ( - Name::new("updated_at"), + Name::new("updatedAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())) ), ]); diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index 63b796e31b..7785188785 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -40,9 +40,9 @@ impl ConnectionObject { Name::new("edges"), TypeData::Simple(TypeRef::named_list(format!("{}Edge", type_name))), ), - (Name::new("total_count"), TypeData::Simple(TypeRef::named_nn(TypeRef::INT))), + (Name::new("totalCount"), TypeData::Simple(TypeRef::named_nn(TypeRef::INT))), ( - Name::new("page_info"), + Name::new("pageInfo"), TypeData::Nested((TypeRef::named_nn(PAGE_INFO_TYPE_NAME), IndexMap::new())), ), ]); @@ -141,8 +141,8 @@ pub fn connection_output( .collect::>>(); Ok(ValueMapping::from([ - (Name::new("total_count"), Value::from(total_count)), + (Name::new("totalCount"), Value::from(total_count)), (Name::new("edges"), Value::List(model_edges?)), - (Name::new("page_info"), PageInfoObject::value(page_info)), + (Name::new("pageInfo"), PageInfoObject::value(page_info)), ])) } diff --git a/crates/torii/graphql/src/object/connection/page_info.rs b/crates/torii/graphql/src/object/connection/page_info.rs index 126f00f7aa..7a27b5b98e 100644 --- a/crates/torii/graphql/src/object/connection/page_info.rs +++ b/crates/torii/graphql/src/object/connection/page_info.rs @@ -33,17 +33,17 @@ impl ObjectTrait for PageInfoObject { impl PageInfoObject { pub fn value(page_info: PageInfo) -> Value { Value::Object(IndexMap::from([ - (Name::new("has_previous_page"), Value::from(page_info.has_previous_page)), - (Name::new("has_next_page"), Value::from(page_info.has_next_page)), + (Name::new("hasPreviousPage"), Value::from(page_info.has_previous_page)), + (Name::new("hasNextPage"), Value::from(page_info.has_next_page)), ( - Name::new("start_cursor"), + Name::new("startCursor"), match page_info.start_cursor { Some(val) => Value::from(val), None => Value::Null, }, ), ( - Name::new("end_cursor"), + Name::new("endCursor"), match page_info.end_cursor { Some(val) => Value::from(val), None => Value::Null, diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 898bb5fdbd..aa19481648 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -115,13 +115,13 @@ impl EntityObject { IndexMap::from([ (Name::new("id"), Value::from(entity.id)), (Name::new("keys"), Value::from(keys)), - (Name::new("event_id"), Value::from(entity.event_id)), + (Name::new("eventId"), Value::from(entity.event_id)), ( - Name::new("created_at"), + Name::new("createdAt"), Value::from(entity.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), ), ( - Name::new("updated_at"), + Name::new("updatedAt"), Value::from(entity.updated_at.format("%Y-%m-%d %H:%M:%S").to_string()), ), ]) @@ -134,6 +134,7 @@ fn model_union_field() -> Field { match ctx.parent_value.try_to_value()? { Value::Object(indexmap) => { let mut conn = ctx.data::>()?.acquire().await?; + let entity_id = extract::(indexmap, "id")?; let model_ids: Vec<(String,)> = sqlx::query_as("SELECT model_id from entity_model WHERE entity_id = ?") diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index 31b660c2b9..7a7e28377f 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -102,9 +102,9 @@ impl EventObject { (Name::new("id"), Value::from(event.id)), (Name::new("keys"), Value::from(keys)), (Name::new("data"), Value::from(data)), - (Name::new("transaction_hash"), Value::from(event.transaction_hash)), + (Name::new("transactionHash"), Value::from(event.transaction_hash)), ( - Name::new("created_at"), + Name::new("createdAt"), Value::from(event.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), ), ]) diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index 4a00b2dd43..94e8171d4a 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -122,9 +122,9 @@ fn metadata_connection_output( .collect::>>(); Ok(ValueMapping::from([ - (Name::new("total_count"), Value::from(total_count)), + (Name::new("totalCount"), Value::from(total_count)), (Name::new("edges"), Value::List(edges?)), - (Name::new("page_info"), PageInfoObject::value(page_info)), + (Name::new("pageInfo"), PageInfoObject::value(page_info)), ])) } diff --git a/crates/torii/graphql/src/object/model.rs b/crates/torii/graphql/src/object/model.rs index a23e8b18a6..402bfe7935 100644 --- a/crates/torii/graphql/src/object/model.rs +++ b/crates/torii/graphql/src/object/model.rs @@ -60,10 +60,10 @@ impl ModelObject { IndexMap::from([ (Name::new("id"), Value::from(model.id)), (Name::new("name"), Value::from(model.name)), - (Name::new("class_hash"), Value::from(model.class_hash)), - (Name::new("transaction_hash"), Value::from(model.transaction_hash)), + (Name::new("classHash"), Value::from(model.class_hash)), + (Name::new("transactionHash"), Value::from(model.transaction_hash)), ( - Name::new("created_at"), + Name::new("createdAt"), Value::from(model.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), ), ]) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index b256164632..140dcc8caa 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use async_graphql::dynamic::TypeRef; use async_graphql::{Name, Value}; +use convert_case::{Case, Casing}; use dojo_types::primitive::{Primitive, SqlType}; use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; @@ -144,8 +145,11 @@ fn fetch_value( type_name: &str, is_external: bool, ) -> sqlx::Result { - let column_name = - if is_external { format!("external_{}", field_name) } else { field_name.to_string() }; + let column_name = if is_external { + format!("external_{}", field_name) + } else { + field_name.to_string().to_case(Case::Snake) + }; match Primitive::from_str(type_name) { // fetch boolean diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index e41da42f32..d2e3f31879 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -16,18 +16,18 @@ mod tests { r#" {{ entities {} {{ - total_count + totalCount edges {{ cursor node {{ keys }} }} - page_info {{ - has_previous_page - has_next_page - start_cursor - end_cursor + pageInfo {{ + hasPreviousPage + hasNextPage + startCursor + endCursor }} }} }} diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index d6490421ff..47c16e2949 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -13,13 +13,13 @@ mod tests { const QUERY: &str = r#" { metadatas { - total_count + totalCount edges { cursor node { uri - cover_img - icon_img + coverImg + iconImg content { name description @@ -33,11 +33,11 @@ mod tests { } } } - page_info { - has_previous_page - has_next_page - start_cursor - end_cursor + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor } } } @@ -60,7 +60,6 @@ mod tests { "#, ) .unwrap(); - let world_metadata = dojo_metadata.world.unwrap(); db.update_metadata(&RESOURCE, URI, &world_metadata, &None, &Some(cover_img.to_string())) .await diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 4a55f66a3e..927faf3a0a 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -37,6 +37,7 @@ mod subscription_test; use crate::schema::build_schema; #[derive(Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct Connection { pub total_count: i64, pub edges: Vec>, @@ -50,12 +51,14 @@ pub struct Edge { } #[derive(Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct Entity { pub keys: Option>, pub created_at: Option, } #[derive(Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] // same as type from `async-graphql` but derive necessary traits // https://docs.rs/async-graphql/6.0.10/async_graphql/types/connection/struct.PageInfo.html pub struct PageInfo { @@ -65,27 +68,6 @@ pub struct PageInfo { pub end_cursor: Option, } -#[derive(Deserialize, Debug)] -pub struct Moves { - pub __typename: String, - pub remaining: u32, - pub last_direction: String, - pub entity: Option, -} - -#[derive(Deserialize, Debug)] -pub struct Vec2 { - pub x: u32, - pub y: u32, -} - -#[derive(Deserialize, Debug)] -pub struct Position { - pub __typename: String, - pub vec: Vec2, - pub entity: Option, -} - #[derive(Deserialize, Debug, PartialEq)] pub struct Record { pub __typename: String, @@ -160,6 +142,7 @@ pub struct Content { } #[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct Metadata { pub uri: String, pub icon_img: String, @@ -210,7 +193,7 @@ pub async fn model_fixtures(db: &mut Sql) { ty: Ty::Primitive(Primitive::U32(None)), }, Member { - name: "type_u16".to_string(), + name: "typeU16".to_string(), key: false, ty: Ty::Primitive(Primitive::U16(None)), }, @@ -220,7 +203,7 @@ pub async fn model_fixtures(db: &mut Sql) { ty: Ty::Primitive(Primitive::U64(None)), }, Member { - name: "type_bool".to_string(), + name: "typeBool".to_string(), key: false, ty: Ty::Primitive(Primitive::Bool(None)), }, @@ -230,7 +213,7 @@ pub async fn model_fixtures(db: &mut Sql) { ty: Ty::Primitive(Primitive::Felt252(None)), }, Member { - name: "type_contract_address".to_string(), + name: "typeContractAddress".to_string(), key: true, ty: Ty::Primitive(Primitive::ContractAddress(None)), }, diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index b033512071..10d4ef6bbe 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -15,7 +15,7 @@ mod tests { r#" {{ recordModels {} {{ - total_count + totalCount edges {{ cursor node {{ @@ -57,11 +57,11 @@ mod tests { }} }} }} - page_info {{ - has_previous_page - has_next_page - start_cursor - end_cursor + pageInfo {{ + hasPreviousPage + hasNextPage + startCursor + endCursor }} }} }} diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index 89b851674e..879d209dec 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -34,11 +34,11 @@ mod tests { "__typename": model_name, "depth": "Zero", "record_id": 0, - "type_u16": 1, + "typeU16": 1, "type_u64": 1, - "type_bool": true, + "typeBool": true, "type_felt": format!("{:#x}", FieldElement::from(1u128)), - "type_contract_address": format!("{:#x}", FieldElement::ONE) + "typeContractAddress": format!("{:#x}", FieldElement::ONE) }] } }); @@ -73,7 +73,7 @@ mod tests { ty: Ty::Primitive(Primitive::U8(Some(0))), }, Member { - name: "type_u16".to_string(), + name: "typeU16".to_string(), key: false, ty: Ty::Primitive(Primitive::U16(Some(1))), }, @@ -83,7 +83,7 @@ mod tests { ty: Ty::Primitive(Primitive::U64(Some(1))), }, Member { - name: "type_bool".to_string(), + name: "typeBool".to_string(), key: false, ty: Ty::Primitive(Primitive::Bool(Some(true))), }, @@ -93,7 +93,7 @@ mod tests { ty: Ty::Primitive(Primitive::Felt252(Some(FieldElement::from(1u128)))), }, Member { - name: "type_contract_address".to_string(), + name: "typeContractAddress".to_string(), key: true, ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), }, @@ -119,11 +119,11 @@ mod tests { ... on Record { depth record_id - type_u16 + typeU16 type_u64 - type_bool + typeBool type_felt - type_contract_address + typeContractAddress } } } @@ -156,7 +156,7 @@ mod tests { "depth": "Zero", "record_id": 0, "type_felt": format!("{:#x}", FieldElement::from(1u128)), - "type_contract_address": format!("{:#x}", FieldElement::ONE) + "typeContractAddress": format!("{:#x}", FieldElement::ONE) }] } }); @@ -196,7 +196,7 @@ mod tests { ty: Ty::Primitive(Primitive::Felt252(Some(FieldElement::from(1u128)))), }, Member { - name: "type_contract_address".to_string(), + name: "typeContractAddress".to_string(), key: true, ty: Ty::Primitive(Primitive::ContractAddress(Some(FieldElement::ONE))), }, @@ -223,7 +223,7 @@ mod tests { depth record_id type_felt - type_contract_address + typeContractAddress } } } @@ -256,7 +256,7 @@ mod tests { let model = Ty::Struct(Struct { name: model_name, children: vec![Member { - name: "subrecord_id".to_string(), + name: "subrecordId".to_string(), key: true, ty: Ty::Primitive(Primitive::U32(None)), }], @@ -369,7 +369,7 @@ mod tests { eventEmitted (keys: ["*", "{:#x}"]) {{ keys data - transaction_hash + transactionHash }} }} "#, @@ -385,7 +385,7 @@ mod tests { ], "data": vec![ format!("{:#x}", FieldElement::from_str("0xc0de").unwrap()), format!("{:#x}", FieldElement::from_str("0xface").unwrap()) - ], "transaction_hash": format!("{:#x}", FieldElement::ZERO)} + ], "transactionHash": format!("{:#x}", FieldElement::ZERO)} }); assert_eq!(response_value, expected_value); From 48d04d1ecb5e97567d4dfa70f018efa94802a45f Mon Sep 17 00:00:00 2001 From: Yun Date: Wed, 6 Dec 2023 03:10:03 -0800 Subject: [PATCH 057/192] Torii grpc handle entities_all query (#1240) --- Cargo.lock | 561 +++++++++++++++++++--- Cargo.toml | 2 +- crates/torii/client/src/client/mod.rs | 10 +- crates/torii/core/src/cache.rs | 23 +- crates/torii/core/src/error.rs | 2 + crates/torii/core/src/model.rs | 11 +- crates/torii/core/src/sql.rs | 8 +- crates/torii/graphql/src/object/entity.rs | 2 +- crates/torii/graphql/src/query/data.rs | 9 +- crates/torii/graphql/src/query/mod.rs | 7 +- crates/torii/graphql/src/schema.rs | 2 +- crates/torii/grpc/src/server/mod.rs | 144 ++++-- crates/torii/grpc/src/types.rs | 4 +- crates/torii/server/src/cli.rs | 3 +- 14 files changed, 649 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2f30351fd..895fd55ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-compression" version = "0.4.5" @@ -359,6 +370,35 @@ dependencies = [ "zstd-safe 7.0.0", ] +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 1.9.0", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "async-graphql" version = "6.0.11" @@ -446,6 +486,35 @@ dependencies = [ "warp", ] +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + [[package]] name = "async-recursion" version = "1.0.5" @@ -457,6 +526,32 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -479,6 +574,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.74" @@ -503,13 +604,29 @@ dependencies = [ [[package]] name = "atoi" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits 0.2.17", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand", +] + [[package]] name = "auto_impl" version = "1.1.0" @@ -695,6 +812,9 @@ name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -758,6 +878,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite", + "log", +] + [[package]] name = "brotli" version = "3.4.0" @@ -1673,6 +1808,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.7" @@ -2078,6 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -2561,6 +2706,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "eth-keystore" version = "0.5.0" @@ -2913,6 +3069,15 @@ dependencies = [ "serde", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -2977,13 +3142,12 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "pin-project", "spin 0.9.8", ] @@ -3018,7 +3182,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix", + "rustix 0.38.26", "windows-sys 0.48.0", ] @@ -3081,13 +3245,13 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.11.2", + "parking_lot 0.12.1", ] [[package]] @@ -3096,6 +3260,21 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-locks" version = "0.7.1" @@ -3727,7 +3906,7 @@ dependencies = [ "gix-command", "gix-config-value", "parking_lot 0.12.1", - "rustix", + "rustix 0.38.26", "thiserror", ] @@ -3910,7 +4089,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" dependencies = [ - "fastrand", + "fastrand 2.0.1", ] [[package]] @@ -4186,6 +4365,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -4580,6 +4768,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipfs-api-backend-hyper" version = "0.6.0" @@ -4644,7 +4843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix", + "rustix 0.38.26", "windows-sys 0.48.0", ] @@ -4923,6 +5122,15 @@ dependencies = [ "libc", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lalrpop" version = "0.20.0" @@ -4999,15 +5207,21 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.24.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -5029,6 +5243,9 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "value-bag", +] [[package]] name = "lru" @@ -5355,6 +5572,23 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits 0.2.17", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.2.4" @@ -5375,6 +5609,17 @@ dependencies = [ "num-traits 0.2.17", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.17", +] + [[package]] name = "num-modular" version = "0.5.1" @@ -5582,6 +5827,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.11.2" @@ -5708,6 +5959,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -5868,6 +6128,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -5884,6 +6155,22 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "portable-atomic" version = "1.5.1" @@ -6386,7 +6673,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots", "winreg", ] @@ -6460,6 +6747,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits 0.2.17", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -6487,6 +6794,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.26" @@ -6496,7 +6817,7 @@ dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.12", "windows-sys 0.52.0", ] @@ -7362,23 +7683,27 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] name = "sqlx-core" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ - "ahash 0.7.7", + "ahash 0.8.6", + "async-io", + "async-std", "atoi", - "bitflags 1.3.2", "byteorder", "bytes", "chrono", @@ -7387,44 +7712,53 @@ dependencies = [ "dotenvy", "either", "event-listener", - "flume", "futures-channel", "futures-core", - "futures-executor", "futures-intrusive", + "futures-io", "futures-util", "hashlink", "hex", - "indexmap 1.9.3", - "itoa", - "libc", - "libsqlite3-sys", + "indexmap 2.1.0", "log", "memchr", "once_cell", "paste", "percent-encoding", - "rustls 0.20.9", - "rustls-pemfile", "serde", + "serde_json", "sha2", "smallvec", "sqlformat", - "sqlx-rt", - "stringprep", "thiserror", + "tokio", "tokio-stream", + "tracing", "url", "uuid 1.6.1", - "webpki-roots 0.22.6", ] [[package]] name = "sqlx-macros" -version = "0.6.3" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" dependencies = [ + "async-std", + "atomic-write-file", "dotenvy", "either", "heck 0.4.1", @@ -7436,20 +7770,124 @@ dependencies = [ "serde_json", "sha2", "sqlx-core", - "sqlx-rt", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "syn 1.0.109", + "tempfile", + "tokio", "url", ] [[package]] -name = "sqlx-rt" -version = "0.6.3" +name = "sqlx-mysql" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.1", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid 1.6.1", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.1", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", "once_cell", - "tokio", - "tokio-rustls 0.23.4", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid 1.6.1", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "regex", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid 1.6.1", ] [[package]] @@ -7850,9 +8288,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.26", "windows-sys 0.48.0", ] @@ -8082,7 +8520,7 @@ dependencies = [ "tokio", "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots 0.25.3", + "webpki-roots", ] [[package]] @@ -8870,6 +9308,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -8907,6 +9351,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" + [[package]] name = "vcpkg" version = "0.2.15" @@ -8928,6 +9378,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.4.0" @@ -9083,15 +9539,6 @@ dependencies = [ "untrusted 0.9.0", ] -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.3" @@ -9107,9 +9554,15 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.26", ] +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index f1254fa576..ea2b044bb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ serde = { version = "1.0.156", features = [ "derive" ] } serde_json = "1.0" serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } -sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] } +sqlx = { version = "0.7.2", features = [ "chrono", "macros", "runtime-async-std", "runtime-tokio", "sqlite", "uuid", "regexp" ] } starknet = "0.7.0" starknet-crypto = "0.6.1" starknet_api = { git = "https://github.com/starkware-libs/starknet-api", rev = "ecc9b6946ef13003da202838e4124a9ad2efabb0" } diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index 7bed643a32..9520ef7782 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -99,12 +99,12 @@ impl Client { self.subscribed_entities.entities_keys.read() } - /// Retrieves entities matching specified keys and/or model name in query parameter. + /// Retrieves entities matching query parameter. /// - /// The query can include keys and a model name, both optional. Without parameters, it fetches - /// all entities, which is less efficient as it requires additional queries for each - /// entity's model data. Specifying a model name optimizes the process by limiting the - /// retrieval to entities with that model, requiring just one query. + /// The query param includes an optional clause for filtering. Without clause, it fetches ALL + /// entities, this is less efficient as it requires an additional query for each entity's + /// model data. Specifying a clause can optimize the query by limiting the retrieval to specific + /// type of entites matching keys and/or models. pub async fn entities(&self, query: Query) -> Result, Error> { let mut grpc_client = self.inner.write().await; let RetrieveEntitiesResponse { entities } = grpc_client.retrieve_entities(query).await?; diff --git a/crates/torii/core/src/cache.rs b/crates/torii/core/src/cache.rs index dc8d39bc63..0a3ae7c1ec 100644 --- a/crates/torii/core/src/cache.rs +++ b/crates/torii/core/src/cache.rs @@ -11,18 +11,27 @@ type ModelName = String; pub struct ModelCache { pool: SqlitePool, - schemas: RwLock>, + cache: RwLock>, } impl ModelCache { pub fn new(pool: SqlitePool) -> Self { - Self { pool, schemas: RwLock::new(HashMap::new()) } + Self { pool, cache: RwLock::new(HashMap::new()) } + } + + pub async fn schemas(&self, models: Vec<&str>) -> Result, Error> { + let mut schemas = Vec::with_capacity(models.len()); + for model in models { + schemas.push(self.schema(model).await?); + } + + Ok(schemas) } pub async fn schema(&self, model: &str) -> Result { { - let schemas = self.schemas.read().await; - if let Some(schema) = schemas.get(model) { + let cache = self.cache.read().await; + if let Some(schema) = cache.get(model) { return Ok(schema.clone()); } } @@ -44,13 +53,13 @@ impl ModelCache { } let ty = parse_sql_model_members(model, &model_members); - let mut schemas = self.schemas.write().await; - schemas.insert(model.into(), ty.clone()); + let mut cache = self.cache.write().await; + cache.insert(model.into(), ty.clone()); Ok(ty) } pub async fn clear(&self) { - self.schemas.write().await.clear(); + self.cache.write().await.clear(); } } diff --git a/crates/torii/core/src/error.rs b/crates/torii/core/src/error.rs index 0d73633076..43f8b628be 100644 --- a/crates/torii/core/src/error.rs +++ b/crates/torii/core/src/error.rs @@ -35,6 +35,8 @@ pub enum ParseError { pub enum QueryError { #[error("unsupported query")] UnsupportedQuery, + #[error("missing param: {0}")] + MissingParam(String), #[error("model not found: {0}")] ModelNotFound(String), #[error("exceeds sqlite `JOIN` limit (64)")] diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 883470e1b1..cf0bd22154 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -201,11 +201,11 @@ pub fn build_sql_query(model_schemas: &Vec) -> Result { let selections_clause = global_selections.join(", "); let join_clause = global_tables .into_iter() - .map(|table| format!(" LEFT JOIN {table} ON entities.id = {table}.entity_id")) + .map(|table| format!(" JOIN {table} ON entities.id = {table}.entity_id")) .collect::>() .join(" "); - Ok(format!("SELECT entities.keys, {selections_clause} FROM entities{join_clause}")) + Ok(format!("SELECT entities.id, entities.keys, {selections_clause} FROM entities{join_clause}")) } /// Populate the values of a Ty (schema) from SQLite row. @@ -504,14 +504,9 @@ mod tests { }); let query = build_sql_query(&vec![ty]).unwrap(); - println!("{query}"); assert_eq!( query, - "SELECT entities.keys, Position.external_name AS \"Position.name\", \ - Position.external_age AS \"Position.age\", Position$Vec2.external_x AS \ - \"Position$Vec2.x\", Position$Vec2.external_y AS \"Position$Vec2.y\" FROM entities \ - LEFT JOIN Position ON entities.id = Position.entity_id LEFT JOIN Position$Vec2 ON \ - entities.id = Position$Vec2.entity_id" + r#"SELECT entities.id, entities.keys, Position.external_name AS "Position.name", Position.external_age AS "Position.age", Position$Vec2.external_x AS "Position$Vec2.x", Position$Vec2.external_y AS "Position$Vec2.y" FROM entities JOIN Position ON entities.id = Position.entity_id JOIN Position$Vec2 ON entities.id = Position$Vec2.entity_id"# ); } } diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index b56ae45e84..3d99a1c6b3 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -53,7 +53,7 @@ impl Sql { let indexer_query = sqlx::query_as::<_, (i64,)>("SELECT head FROM indexers WHERE id = ?") .bind(format!("{:#x}", self.world_address)); - let indexer: (i64,) = indexer_query.fetch_one(&mut conn).await?; + let indexer: (i64,) = indexer_query.fetch_one(&mut *conn).await?; Ok(indexer.0.try_into().expect("doesn't fit in u64")) } @@ -68,7 +68,7 @@ impl Sql { let mut conn: PoolConnection = self.pool.acquire().await?; let meta: World = sqlx::query_as("SELECT * FROM worlds WHERE id = ?") .bind(format!("{:#x}", self.world_address)) - .fetch_one(&mut conn) + .fetch_one(&mut *conn) .await?; Ok(meta) @@ -223,14 +223,14 @@ impl Sql { .bind(format!("{:#x}", key)); let mut conn: PoolConnection = self.pool.acquire().await?; - let row: (i32, String, String) = query.fetch_one(&mut conn).await?; + let row: (i32, String, String) = query.fetch_one(&mut *conn).await?; Ok(serde_json::from_str(&row.2).unwrap()) } pub async fn entities(&self, model: String) -> Result>> { let query = sqlx::query_as::<_, (i32, String, String)>("SELECT * FROM ?").bind(model); let mut conn: PoolConnection = self.pool.acquire().await?; - let mut rows = query.fetch_all(&mut conn).await?; + let mut rows = query.fetch_all(&mut *conn).await?; Ok(rows.drain(..).map(|row| serde_json::from_str(&row.2).unwrap()).collect()) } diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index aa19481648..fc0e90077c 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -139,7 +139,7 @@ fn model_union_field() -> Field { let model_ids: Vec<(String,)> = sqlx::query_as("SELECT model_id from entity_model WHERE entity_id = ?") .bind(&entity_id) - .fetch_all(&mut conn) + .fetch_all(&mut *conn) .await?; let mut results: Vec> = Vec::new(); diff --git a/crates/torii/graphql/src/query/data.rs b/crates/torii/graphql/src/query/data.rs index de04e12e49..80e0c80dc0 100644 --- a/crates/torii/graphql/src/query/data.rs +++ b/crates/torii/graphql/src/query/data.rs @@ -1,7 +1,6 @@ use async_graphql::connection::PageInfo; -use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; -use sqlx::{Result, Row, Sqlite}; +use sqlx::{Result, Row, SqliteConnection}; use super::filter::{Filter, FilterValue}; use super::order::{CursorDirection, Direction, Order}; @@ -9,7 +8,7 @@ use crate::constants::DEFAULT_LIMIT; use crate::object::connection::{cursor, ConnectionArguments}; pub async fn count_rows( - conn: &mut PoolConnection, + conn: &mut SqliteConnection, table_name: &str, keys: &Option>, filters: &Option>, @@ -26,7 +25,7 @@ pub async fn count_rows( } pub async fn fetch_single_row( - conn: &mut PoolConnection, + conn: &mut SqliteConnection, table_name: &str, id_column: &str, id: &str, @@ -37,7 +36,7 @@ pub async fn fetch_single_row( #[allow(clippy::too_many_arguments)] pub async fn fetch_multiple_rows( - conn: &mut PoolConnection, + conn: &mut SqliteConnection, table_name: &str, id_column: &str, keys: &Option>, diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 140dcc8caa..c7ff3afd09 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -4,9 +4,8 @@ use async_graphql::dynamic::TypeRef; use async_graphql::{Name, Value}; use convert_case::{Case, Casing}; use dojo_types::primitive::{Primitive, SqlType}; -use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; -use sqlx::{Row, Sqlite}; +use sqlx::{Row, SqliteConnection}; use torii_core::sql::FELT_DELIMITER; use crate::constants::{BOOLEAN_TRUE, ENTITY_ID_COLUMN, INTERNAL_ENTITY_ID_KEY}; @@ -18,7 +17,7 @@ pub mod filter; pub mod order; pub async fn type_mapping_query( - conn: &mut PoolConnection, + conn: &mut SqliteConnection, model_id: &str, ) -> sqlx::Result { let model_members = fetch_model_members(conn, model_id).await?; @@ -29,7 +28,7 @@ pub async fn type_mapping_query( } async fn fetch_model_members( - conn: &mut PoolConnection, + conn: &mut SqliteConnection, model_id: &str, ) -> sqlx::Result> { sqlx::query_as( diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 8f28ad089a..32db9f090a 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -104,7 +104,7 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { async fn build_objects(pool: &SqlitePool) -> Result<(Vec>, Union)> { let mut conn = pool.acquire().await?; - let models: Vec = sqlx::query_as("SELECT * FROM models").fetch_all(&mut conn).await?; + let models: Vec = sqlx::query_as("SELECT * FROM models").fetch_all(&mut *conn).await?; // predefined objects let mut objects: Vec> = vec![ diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index dccb730239..de83ad13cb 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -108,6 +108,43 @@ impl DojoWorld { }) } + async fn entities_all( + &self, + limit: u32, + offset: u32, + ) -> Result, Error> { + let query = r#" + SELECT entities.id, group_concat(entity_model.model_id) as model_names + FROM entities + JOIN entity_model ON entities.id = entity_model.entity_id + GROUP BY entities.id + ORDER BY entities.event_id DESC + LIMIT ? OFFSET ? + "#; + let db_entities: Vec<(String, String)> = + sqlx::query_as(query).bind(limit).bind(offset).fetch_all(&self.pool).await?; + + let mut entities = Vec::with_capacity(db_entities.len()); + for (entity_id, models_str) in db_entities { + let model_names: Vec<&str> = models_str.split(',').collect(); + let schemas = self.model_cache.schemas(model_names).await?; + + let entity_query = format!("{} WHERE entities.id = ?", build_sql_query(&schemas)?); + let row = sqlx::query(&entity_query).bind(&entity_id).fetch_one(&self.pool).await?; + + let mut models = Vec::with_capacity(schemas.len()); + for schema in schemas { + let struct_ty = schema.as_struct().expect("schema should be struct"); + models.push(Self::map_row_to_struct(&schema.name(), struct_ty, &row)?.into()); + } + + let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; + entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) + } + + Ok(entities) + } + async fn entities_by_keys( &self, keys_clause: proto::types::KeysClause, @@ -128,45 +165,36 @@ impl DojoWorld { .collect::, Error>>()?; let keys_pattern = keys.join("/") + "/%"; - let query = r#" - SELECT entities.id, group_concat(entity_model.model_id) as model_names + let models_query = format!( + r#" + SELECT group_concat(entity_model.model_id) as model_names FROM entities JOIN entity_model ON entities.id = entity_model.entity_id WHERE entities.keys LIKE ? GROUP BY entities.id - ORDER BY entities.event_id DESC - LIMIT ? OFFSET ? - "#; - let db_entities: Vec<(String, String)> = sqlx::query_as(query) + HAVING model_names REGEXP '(^|,){}(,|$)' + LIMIT 1 + "#, + keys_clause.model + ); + let (models_str,): (String,) = + sqlx::query_as(&models_query).bind(&keys_pattern).fetch_one(&self.pool).await?; + + let model_names = models_str.split(',').collect::>(); + let schemas = self.model_cache.schemas(model_names).await?; + + let entities_query = format!( + "{} WHERE entities.keys LIKE ? ORDER BY entities.event_id DESC LIMIT ? OFFSET ?", + build_sql_query(&schemas)? + ); + let db_entities = sqlx::query(&entities_query) .bind(&keys_pattern) .bind(limit) .bind(offset) .fetch_all(&self.pool) .await?; - let mut entities = Vec::new(); - for (entity_id, models_str) in db_entities { - let model_names: Vec<&str> = models_str.split(',').collect(); - let mut schemas = Vec::new(); - for model in &model_names { - schemas.push(self.model_cache.schema(model).await?); - } - - let entity_query = - format!("{} WHERE {}.entity_id = ?", build_sql_query(&schemas)?, schemas[0].name()); - let row = sqlx::query(&entity_query).bind(&entity_id).fetch_one(&self.pool).await?; - - let mut models = Vec::new(); - for schema in schemas { - let struct_ty = schema.as_struct().expect("schema should be struct"); - models.push(Self::map_row_to_proto(&schema.name(), struct_ty, &row)?.into()); - } - - let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; - entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) - } - - Ok(entities) + db_entities.iter().map(|row| Self::map_row_to_entity(row, &schemas)).collect() } async fn entities_by_attribute( @@ -245,30 +273,54 @@ impl DojoWorld { &self, query: proto::types::Query, ) -> Result { - let clause_type = query - .clause - .ok_or(QueryError::UnsupportedQuery)? - .clause_type - .ok_or(QueryError::UnsupportedQuery)?; - - let entities = match clause_type { - ClauseType::Keys(keys) => { - self.entities_by_keys(keys, query.limit, query.offset).await? - } - ClauseType::Member(attribute) => { - self.entities_by_attribute(attribute, query.limit, query.offset).await? - } - ClauseType::Composite(composite) => { - self.entities_by_composite(composite, query.limit, query.offset).await? + let entities = match query.clause { + None => self.entities_all(query.limit, query.offset).await?, + Some(clause) => { + let clause_type = + clause.clause_type.ok_or(QueryError::MissingParam("clause_type".into()))?; + + match clause_type { + ClauseType::Keys(keys) => { + if keys.keys.is_empty() { + return Err(QueryError::MissingParam("keys".into()).into()); + } + + if keys.model.is_empty() { + return Err(QueryError::MissingParam("model".into()).into()); + } + + self.entities_by_keys(keys, query.limit, query.offset).await? + } + ClauseType::Member(attribute) => { + self.entities_by_attribute(attribute, query.limit, query.offset).await? + } + ClauseType::Composite(composite) => { + self.entities_by_composite(composite, query.limit, query.offset).await? + } + } } }; Ok(RetrieveEntitiesResponse { entities }) } + fn map_row_to_entity(row: &SqliteRow, schemas: &[Ty]) -> Result { + let key = + FieldElement::from_str(&row.get::("id")).map_err(ParseError::FromStr)?; + let models = schemas + .iter() + .map(|schema| { + let struct_ty = schema.as_struct().expect("schema should be struct"); + Self::map_row_to_struct(&schema.name(), struct_ty, row).map(Into::into) + }) + .collect::, Error>>()?; + + Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) + } + /// Helper function to map Sqlite row to proto::types::Struct // TODO: refactor this to use `map_row_to_ty` from core and implement Ty to protobuf conversion - fn map_row_to_proto( + fn map_row_to_struct( path: &str, struct_ty: &Struct, row: &SqliteRow, @@ -337,7 +389,7 @@ impl DojoWorld { } Ty::Struct(struct_ty) => { let path = [path, &struct_ty.name].join("$"); - Some(proto::types::ty::TyType::Struct(Self::map_row_to_proto( + Some(proto::types::ty::TyType::Struct(Self::map_row_to_struct( &path, struct_ty, row, )?)) } diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types.rs index 632d2d763c..ddb6820cc2 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types.rs @@ -26,7 +26,7 @@ pub struct Model { #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { - pub clause: Clause, + pub clause: Option, pub limit: u32, pub offset: u32, } @@ -127,7 +127,7 @@ impl TryFrom for dojo_types::WorldMetadata { impl From for proto::types::Query { fn from(value: Query) -> Self { - Self { clause: Some(value.clause.into()), limit: value.limit, offset: value.offset } + Self { clause: value.clause.map(|c| c.into()), limit: value.limit, offset: value.offset } } } diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index a86e95fda9..23eadaaad7 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -84,7 +84,8 @@ async fn main() -> anyhow::Result<()> { .expect("Error setting Ctrl-C handler"); let database_url = format!("sqlite:{}", &args.database); - let options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true); + let options = + SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true).with_regexp(); let pool = SqlitePoolOptions::new() .min_connections(1) .max_connections(5) From 08b28509f104791d976a0456be45d63839c1e7c6 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 7 Nov 2023 22:48:20 +0900 Subject: [PATCH 058/192] Init `katana-primitives` crate --- Cargo.lock | 7 ++++ Cargo.toml | 1 + crates/katana/primitives/Cargo.toml | 10 +++++ crates/katana/primitives/src/contract.rs | 44 +++++++++++++++++++++ crates/katana/primitives/src/lib.rs | 2 + crates/katana/primitives/src/transaction.rs | 4 ++ 6 files changed, 68 insertions(+) create mode 100644 crates/katana/primitives/Cargo.toml create mode 100644 crates/katana/primitives/src/contract.rs create mode 100644 crates/katana/primitives/src/lib.rs create mode 100644 crates/katana/primitives/src/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 895fd55ab3..04483013fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5064,6 +5064,13 @@ dependencies = [ "url", ] +[[package]] +name = "katana-primitives" +version = "0.3.6" +dependencies = [ + "starknet", +] + [[package]] name = "katana-rpc" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index ea2b044bb5..cdeb2bc49c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/dojo-world", "crates/katana", "crates/katana/core", + "crates/katana/primitives", "crates/katana/rpc", "crates/sozo", "crates/torii/client", diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml new file mode 100644 index 0000000000..59128d26b9 --- /dev/null +++ b/crates/katana/primitives/Cargo.toml @@ -0,0 +1,10 @@ +[package] +description = "Commonly used types in Katana" +edition.workspace = true +name = "katana-primitives" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +starknet.workspace = true diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs new file mode 100644 index 0000000000..93d8c452a8 --- /dev/null +++ b/crates/katana/primitives/src/contract.rs @@ -0,0 +1,44 @@ +use std::fmt; + +use starknet::core::types::FieldElement; +use starknet::core::utils::normalize_address; + +/// Represents the type for a contract storage key. +pub type StorageKey = FieldElement; +/// Represents the type for a contract storage value. +pub type StorageValue = FieldElement; + +/// The canonical hash of a contract class. This is the class hash value of a contract instance. +pub type ClassHash = FieldElement; +/// The hash of a compiled contract class. +pub type CompiledClassHash = FieldElement; + +/// Represents the type for a contract nonce. +pub type Nonce = FieldElement; + +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +pub struct ContractAddress(FieldElement); + +impl ContractAddress { + pub fn new(address: FieldElement) -> Self { + ContractAddress(normalize_address(address)) + } +} + +impl fmt::Display for ContractAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:#x}", self.0) + } +} + +impl From for ContractAddress { + fn from(value: FieldElement) -> Self { + ContractAddress::new(value) + } +} + +impl From for FieldElement { + fn from(value: ContractAddress) -> Self { + value.0 + } +} diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs new file mode 100644 index 0000000000..0727b84b23 --- /dev/null +++ b/crates/katana/primitives/src/lib.rs @@ -0,0 +1,2 @@ +pub mod contract; +pub mod transaction; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs new file mode 100644 index 0000000000..b2e8d1f3e1 --- /dev/null +++ b/crates/katana/primitives/src/transaction.rs @@ -0,0 +1,4 @@ +use starknet::core::types::FieldElement; + +/// The hash of a transaction. +pub type TxHash = FieldElement; From ee589ff0ff69b7389c25a1c181a2b4aa09955ea7 Mon Sep 17 00:00:00 2001 From: Kariy Date: Wed, 8 Nov 2023 01:11:51 +0900 Subject: [PATCH 059/192] Add transaction types --- Cargo.lock | 1 + crates/katana/primitives/Cargo.toml | 1 + crates/katana/primitives/src/block.rs | 12 +++ crates/katana/primitives/src/contract.rs | 3 +- crates/katana/primitives/src/lib.rs | 1 + crates/katana/primitives/src/transaction.rs | 100 +++++++++++++++++++- 6 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 crates/katana/primitives/src/block.rs diff --git a/Cargo.lock b/Cargo.lock index 04483013fc..ed46093a1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5068,6 +5068,7 @@ dependencies = [ name = "katana-primitives" version = "0.3.6" dependencies = [ + "serde", "starknet", ] diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 59128d26b9..d8029e7882 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -7,4 +7,5 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +serde.workspace = true starknet.workspace = true diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs new file mode 100644 index 0000000000..2f0ee26c04 --- /dev/null +++ b/crates/katana/primitives/src/block.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; +use starknet::core::types::FieldElement; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Header { + pub parent_hash: FieldElement, + pub number: u64, + pub gas_price: u128, + pub timestamp: u64, + pub state_root: FieldElement, + pub sequencer_address: FieldElement, +} diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 93d8c452a8..b315992135 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -1,5 +1,6 @@ use std::fmt; +use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use starknet::core::utils::normalize_address; @@ -16,7 +17,7 @@ pub type CompiledClassHash = FieldElement; /// Represents the type for a contract nonce. pub type Nonce = FieldElement; -#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] pub struct ContractAddress(FieldElement); impl ContractAddress { diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 0727b84b23..7a615dcc2c 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -1,2 +1,3 @@ +pub mod block; pub mod contract; pub mod transaction; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index b2e8d1f3e1..0cb2fd8801 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,4 +1,102 @@ -use starknet::core::types::FieldElement; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use starknet::core::types::{Event, FieldElement, MsgToL1}; + +use crate::contract::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; /// The hash of a transaction. pub type TxHash = FieldElement; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Transaction { + Invoke(InvokeTx), + Declare(DeclareTx), + L1Handler(L1HandlerTx), + DeployAccount(DeployAccountTx), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum InvokeTx { + V1(InvokeTxV1), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InvokeTxV1 { + pub transaction_hash: TxHash, + pub nonce: Nonce, + pub max_fee: u128, + pub calldata: Vec, + pub signature: Vec, + pub sender_address: ContractAddress, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DeclareTx { + V1(DeclareTxV1), + V2(DeclareTxV2), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeclareTxV1 { + pub transaction_hash: TxHash, + pub max_fee: u128, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub signature: Vec, + pub sender_address: ContractAddress, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeclareTxV2 { + pub transaction_hash: TxHash, + pub max_fee: u128, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub signature: Vec, + pub sender_address: ContractAddress, + pub compiled_class_hash: CompiledClassHash, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct L1HandlerTx { + pub transaction_hash: TxHash, + pub version: FieldElement, + pub nonce: Nonce, + pub paid_l1_fee: u128, + pub calldata: Vec, + pub contract_address: ContractAddress, + pub entry_point_selector: FieldElement, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeployAccountTx { + pub transaction_hash: TxHash, + pub max_fee: u128, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub version: FieldElement, + pub signature: Vec, + pub contract_address_salt: FieldElement, + pub constructor_calldata: Vec, +} + +/// A transaction finality status. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum FinalityStatus { + AcceptedOnL2, + AcceptedOnL1, +} + +pub type ExecutionResources = HashMap; + +/// The receipt of a transaction containing the outputs of its execution. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Receipt { + pub actual_fee: u128, + pub events: Vec, + pub messages_sent: Vec, + pub revert_error: Option, + pub actual_resources: ExecutionResources, + pub contract_address: Option, +} From ab60c73d3b39449392069414e24e6d1733514318 Mon Sep 17 00:00:00 2001 From: Kariy Date: Wed, 8 Nov 2023 19:39:28 +0900 Subject: [PATCH 060/192] Re-export types and add basic contract info --- crates/katana/primitives/src/block.rs | 2 ++ crates/katana/primitives/src/contract.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 2f0ee26c04..1e8b566419 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; +pub type StateUpdate = starknet::core::types::StateUpdate; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Header { pub parent_hash: FieldElement, diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index b315992135..b2c1c6e1ce 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -17,6 +17,9 @@ pub type CompiledClassHash = FieldElement; /// Represents the type for a contract nonce. pub type Nonce = FieldElement; +pub type SierraClass = starknet::core::types::FlattenedSierraClass; + +/// Represents a contract address. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] pub struct ContractAddress(FieldElement); @@ -43,3 +46,12 @@ impl From for FieldElement { value.0 } } + +/// Represents a generic contract instance information. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenericContractInfo { + /// The nonce of the contract. + pub nonce: Nonce, + /// The hash of the contract class. + pub class_hash: ClassHash, +} From a27ea3534fb083488a9049e26bd3a3c2ffeea119 Mon Sep 17 00:00:00 2001 From: Kariy Date: Sat, 11 Nov 2023 14:12:59 +0300 Subject: [PATCH 061/192] Add more alias types --- crates/katana/primitives/src/block.rs | 16 ++++++++++++---- crates/katana/primitives/src/contract.rs | 3 ++- crates/katana/primitives/src/lib.rs | 2 ++ crates/katana/primitives/src/transaction.rs | 3 ++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 1e8b566419..ed908f56b0 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,14 +1,22 @@ use serde::{Deserialize, Serialize}; -use starknet::core::types::FieldElement; +use crate::contract::ContractAddress; +use crate::FieldElement; + +/// Block state update type. pub type StateUpdate = starknet::core::types::StateUpdate; +/// Block number type. +pub type BlockNumber = u64; +/// Block hash type. +pub type BlockHash = FieldElement; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Header { - pub parent_hash: FieldElement, - pub number: u64, + pub parent_hash: BlockHash, + pub number: BlockNumber, pub gas_price: u128, pub timestamp: u64, pub state_root: FieldElement, - pub sequencer_address: FieldElement, + pub sequencer_address: ContractAddress, } diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index b2c1c6e1ce..61289e2882 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -1,9 +1,10 @@ use std::fmt; use serde::{Deserialize, Serialize}; -use starknet::core::types::FieldElement; use starknet::core::utils::normalize_address; +use crate::FieldElement; + /// Represents the type for a contract storage key. pub type StorageKey = FieldElement; /// Represents the type for a contract storage value. diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 7a615dcc2c..d96f0bab27 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -1,3 +1,5 @@ pub mod block; pub mod contract; pub mod transaction; + +pub type FieldElement = starknet::core::types::FieldElement; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 0cb2fd8801..775668f9c2 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use starknet::core::types::{Event, FieldElement, MsgToL1}; +use starknet::core::types::{Event, MsgToL1}; use crate::contract::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use crate::FieldElement; /// The hash of a transaction. pub type TxHash = FieldElement; From 7085921df46fdf22b3277242e94227545975e6ee Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 16 Nov 2023 16:02:42 +0300 Subject: [PATCH 062/192] Add more primitives types --- Cargo.lock | 2 +- crates/katana/primitives/src/block.rs | 19 +++++++++++++++++++ crates/katana/primitives/src/transaction.rs | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ed46093a1b..3a0315d6b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5066,7 +5066,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.3.6" +version = "0.3.10" dependencies = [ "serde", "starknet", diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index ed908f56b0..e3d8c67b79 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -6,11 +6,18 @@ use crate::FieldElement; /// Block state update type. pub type StateUpdate = starknet::core::types::StateUpdate; +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum BlockHashOrNumber { + Hash(BlockHash), + Num(BlockNumber), +} + /// Block number type. pub type BlockNumber = u64; /// Block hash type. pub type BlockHash = FieldElement; +/// Represents a block header. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Header { pub parent_hash: BlockHash, @@ -20,3 +27,15 @@ pub struct Header { pub state_root: FieldElement, pub sequencer_address: ContractAddress, } + +impl From for BlockHashOrNumber { + fn from(number: BlockNumber) -> Self { + Self::Num(number) + } +} + +impl From for BlockHashOrNumber { + fn from(hash: BlockHash) -> Self { + Self::Hash(hash) + } +} diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 775668f9c2..374eded996 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -8,6 +8,8 @@ use crate::FieldElement; /// The hash of a transaction. pub type TxHash = FieldElement; +/// The sequential number for all the transactions.. +pub type TxNumber = u64; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Transaction { From 35f510bf7540de8ea354820fefed8732d05006f4 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 6 Nov 2023 16:50:53 +0900 Subject: [PATCH 063/192] scaffold db crate --- Cargo.lock | 106 +++++++++++++++++++++++++++- Cargo.toml | 1 + crates/katana/storage/db/Cargo.toml | 10 +++ crates/katana/storage/db/src/lib.rs | 1 + 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 crates/katana/storage/db/Cargo.toml create mode 100644 crates/katana/storage/db/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3a0315d6b5..e65490d022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,6 +786,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.4.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.39", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1627,6 +1647,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1658,6 +1687,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.4.10" @@ -2228,9 +2268,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", "serde", @@ -5064,9 +5104,16 @@ dependencies = [ "url", ] +[[package]] +name = "katana-db" +version = "0.3.14" +dependencies = [ + "reth-libmdbx", +] + [[package]] name = "katana-primitives" -version = "0.3.10" +version = "0.3.14" dependencies = [ "serde", "starknet", @@ -5180,12 +5227,28 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libm" version = "0.2.8" @@ -5958,6 +6021,12 @@ dependencies = [ "hmac", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem" version = "1.1.1" @@ -6685,6 +6754,31 @@ dependencies = [ "winreg", ] +[[package]] +name = "reth-libmdbx" +version = "0.1.0-alpha.12" +source = "git+https://github.com/paradigmxyz/reth.git#b3b84c4e33feb91df415be7c6cdeede3c91c5d1d" +dependencies = [ + "bitflags 2.4.1", + "byteorder", + "derive_more", + "indexmap 2.1.0", + "libc", + "parking_lot 0.12.1", + "reth-mdbx-sys", + "thiserror", +] + +[[package]] +name = "reth-mdbx-sys" +version = "0.1.0-alpha.12" +source = "git+https://github.com/paradigmxyz/reth.git#b3b84c4e33feb91df415be7c6cdeede3c91c5d1d" +dependencies = [ + "bindgen", + "cc", + "libc", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -7449,6 +7543,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook" version = "0.3.17" diff --git a/Cargo.toml b/Cargo.toml index cdeb2bc49c..9d3ad8c520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/katana/core", "crates/katana/primitives", "crates/katana/rpc", + "crates/katana/storage/db", "crates/sozo", "crates/torii/client", "crates/torii/server", diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml new file mode 100644 index 0000000000..72ffc48800 --- /dev/null +++ b/crates/katana/storage/db/Cargo.toml @@ -0,0 +1,10 @@ +[package] +description = "Katana database implementation" +edition.workspace = true +name = "katana-db" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libmdbx = { git = "https://github.com/paradigmxyz/reth.git", version = "0.1.0-alpha.10", package = "reth-libmdbx" } diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/katana/storage/db/src/lib.rs @@ -0,0 +1 @@ + From 12d44ca45512cf6c336b9e06cf208598d615753f Mon Sep 17 00:00:00 2001 From: Kariy Date: Wed, 8 Nov 2023 19:43:03 +0900 Subject: [PATCH 064/192] Initial mdbx implementation --- Cargo.lock | 29 ++- crates/katana/storage/db/Cargo.toml | 8 + crates/katana/storage/db/src/codecs.rs | 73 ++++++ crates/katana/storage/db/src/error.rs | 44 ++++ crates/katana/storage/db/src/lib.rs | 18 ++ crates/katana/storage/db/src/mdbx/cursor.rs | 257 ++++++++++++++++++++ crates/katana/storage/db/src/mdbx/mod.rs | 112 +++++++++ crates/katana/storage/db/src/mdbx/models.rs | 14 ++ crates/katana/storage/db/src/mdbx/tables.rs | 180 ++++++++++++++ crates/katana/storage/db/src/mdbx/tx.rs | 126 ++++++++++ crates/katana/storage/db/src/utils.rs | 76 ++++++ 11 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 crates/katana/storage/db/src/codecs.rs create mode 100644 crates/katana/storage/db/src/error.rs create mode 100644 crates/katana/storage/db/src/mdbx/cursor.rs create mode 100644 crates/katana/storage/db/src/mdbx/mod.rs create mode 100644 crates/katana/storage/db/src/mdbx/models.rs create mode 100644 crates/katana/storage/db/src/mdbx/tables.rs create mode 100644 crates/katana/storage/db/src/mdbx/tx.rs create mode 100644 crates/katana/storage/db/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index e65490d022..5c13cf6a06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,6 +778,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -1582,7 +1591,7 @@ version = "0.8.2" source = "git+https://github.com/dojoengine/cairo-rs.git?rev=262b7eb4b11ab165a2a936a5f914e78aa732d4a2#262b7eb4b11ab165a2a936a5f914e78aa732d4a2" dependencies = [ "anyhow", - "bincode", + "bincode 2.0.0-rc.3", "bitvec", "cairo-felt", "generic-array", @@ -5108,7 +5117,15 @@ dependencies = [ name = "katana-db" version = "0.3.14" dependencies = [ + "anyhow", + "bincode 1.3.3", + "flate2", + "katana-primitives", + "page_size", + "parking_lot 0.12.1", "reth-libmdbx", + "serde", + "thiserror", ] [[package]] @@ -5872,6 +5889,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parity-scale-codec" version = "3.6.9" diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 72ffc48800..1e9f84be70 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -7,4 +7,12 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow.workspace = true +bincode = "1.3.3" +flate2.workspace = true +katana-primitives = { path = "../../primitives" } libmdbx = { git = "https://github.com/paradigmxyz/reth.git", version = "0.1.0-alpha.10", package = "reth-libmdbx" } +page_size = "0.6.0" +parking_lot.workspace = true +serde.workspace = true +thiserror.workspace = true diff --git a/crates/katana/storage/db/src/codecs.rs b/crates/katana/storage/db/src/codecs.rs new file mode 100644 index 0000000000..f562816655 --- /dev/null +++ b/crates/katana/storage/db/src/codecs.rs @@ -0,0 +1,73 @@ +use std::io::Read; + +use flate2::Compression; + +use crate::error::CodecError; + +/// A trait for encoding the key of a table. +pub trait Encode { + type Encoded: AsRef<[u8]>; + fn encode(self) -> Self::Encoded; +} + +pub trait Decode: Sized { + fn decode>(bytes: B) -> Result; +} + +/// A trait for compressing data that are stored in the db. +pub trait Compress { + type Compressed: AsRef<[u8]>; + fn compress(self) -> Self::Compressed; +} + +/// A trait for decompressing data that are read from the db. +pub trait Decompress: Sized { + fn decompress>(bytes: B) -> Result; +} + +impl Encode for T +where + T: serde::Serialize, +{ + type Encoded = Vec; + fn encode(self) -> Self::Encoded { + bincode::serialize(&self).expect("valid encoding") + } +} + +impl Decode for T +where + T: for<'a> serde::Deserialize<'a>, +{ + fn decode>(bytes: B) -> Result { + bincode::deserialize(bytes.as_ref()).map_err(|e| CodecError::Decode(e.to_string())) + } +} + +impl Compress for T +where + T: Encode + serde::Serialize, + ::Encoded: AsRef<[u8]>, +{ + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut compressed = Vec::new(); + flate2::read::DeflateEncoder::new(Encode::encode(self).as_ref(), Compression::best()) + .read_to_end(&mut compressed) + .unwrap(); + compressed + } +} + +impl Decompress for T +where + T: Decode + for<'a> serde::Deserialize<'a>, +{ + fn decompress>(bytes: B) -> Result { + let mut bin = Vec::new(); + flate2::read::DeflateDecoder::new(bytes.as_ref()) + .read_to_end(&mut bin) + .map_err(|e| CodecError::Decompress(e.to_string()))?; + Decode::decode(bin) + } +} diff --git a/crates/katana/storage/db/src/error.rs b/crates/katana/storage/db/src/error.rs new file mode 100644 index 0000000000..b36c41b7a4 --- /dev/null +++ b/crates/katana/storage/db/src/error.rs @@ -0,0 +1,44 @@ +#[derive(Debug, thiserror::Error)] +pub enum DatabaseError { + #[error(transparent)] + Codec(#[from] CodecError), + + #[error("failed to create table: {0}")] + CreateTable(libmdbx::Error), + + #[error("failed to commit transaction: {0}")] + Commit(libmdbx::Error), + + #[error("failed to read: {0}")] + Read(libmdbx::Error), + + #[error("failed to write to table {table} with key {key:?}: {error}")] + Write { error: libmdbx::Error, table: &'static str, key: Box<[u8]> }, + + #[error("failed to open database: {0}")] + OpenDb(libmdbx::Error), + + #[error("failed to retrieve database statistics: {0}")] + Stat(libmdbx::Error), + + #[error("failed to create cursor: {0}")] + CreateCursor(libmdbx::Error), + + #[error("failed to create transaction: {0}")] + CreateTransaction(libmdbx::Error), + + #[error("failed to delete entry: {0}")] + Delete(libmdbx::Error), + + #[error("failed to clear database: {0}")] + Clear(libmdbx::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum CodecError { + #[error("failed to decode data: {0}")] + Decode(String), + + #[error("failed to decompress data: {0}")] + Decompress(String), +} diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 8b13789179..7ce7fa7c86 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -1 +1,19 @@ +//! Code adapted from Paradigm's [`reth`](https://github.com/paradigmxyz/reth/tree/main/crates/storage/db) DB implementation. +use std::path::Path; + +use anyhow::Result; + +pub mod codecs; +pub mod error; +pub mod mdbx; +pub mod utils; + +/// Initialize the database at the given path +pub fn init_db>(path: P) -> Result<()> { + Ok(()) +} + +pub fn open_rw_db>(path: P) -> Result<()> { + Ok(()) +} diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs new file mode 100644 index 0000000000..c2b1465356 --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -0,0 +1,257 @@ +//! Cursor wrapper for libmdbx-sys. + +use std::marker::PhantomData; + +use libmdbx::{self, TransactionKind, WriteFlags, RO, RW}; + +use super::tables::{DupSort, Table}; +use crate::codecs::{Compress, Encode}; +use crate::error::DatabaseError; +use crate::utils::{decode_one, decode_value, KeyValue}; + +/// Read only Cursor. +pub type CursorRO<'tx, T> = Cursor<'tx, RO, T>; +/// Read write cursor. +pub type CursorRW<'tx, T> = Cursor<'tx, RW, T>; + +/// Cursor wrapper to access KV items. +#[derive(Debug)] +pub struct Cursor<'tx, K: TransactionKind, T: Table> { + /// Inner `libmdbx` cursor. + pub(crate) inner: libmdbx::Cursor<'tx, K>, + /// Cache buffer that receives compressed values. + buf: Vec, + /// Phantom data to enforce encoding/decoding. + _dbi: PhantomData, +} + +impl<'tx, K: TransactionKind, T: Table> Cursor<'tx, K, T> { + pub(crate) fn new(inner: libmdbx::Cursor<'tx, K>) -> Self { + Self { inner, buf: Vec::new(), _dbi: PhantomData } + } +} + +/// Takes `(key, value)` from the database and decodes it appropriately. +#[macro_export] +macro_rules! decode { + ($v:expr) => { + $v.map_err($crate::error::DatabaseError::Read)?.map($crate::utils::decoder::).transpose() + }; +} + +impl Cursor<'_, K, T> { + // fn walk(&mut self, start_key: Option) -> Result, DatabaseError> + // where + // Self: Sized, + // { + // let start = if let Some(start_key) = start_key { + // self.inner + // .set_range(start_key.encode().as_ref()) + // .map_err(|e| DatabaseError::Read(e.into()))? + // .map(decoder::) + // } else { + // self.first().transpose() + // }; + + // Ok(Walker::new(self, start)) + // } + + // fn walk_range( + // &mut self, + // range: impl RangeBounds, + // ) -> Result, DatabaseError> + // where + // Self: Sized, + // { + // let start = match range.start_bound().cloned() { + // Bound::Included(key) => self.inner.set_range(key.encode().as_ref()), + // Bound::Excluded(_key) => { + // unreachable!("Rust doesn't allow for Bound::Excluded in starting bounds"); + // } + // Bound::Unbounded => self.inner.first(), + // } + // .map_err(|e| DatabaseError::Read(e.into()))? + // .map(decoder::); + + // Ok(RangeWalker::new(self, start, range.end_bound().cloned())) + // } + + // fn walk_back( + // &mut self, + // start_key: Option, + // ) -> Result, DatabaseError> + // where + // Self: Sized, + // { + // let start = if let Some(start_key) = start_key { + // decode!(self.inner.set_range(start_key.encode().as_ref())) + // } else { + // self.last() + // } + // .transpose(); + + // Ok(ReverseWalker::new(self, start)) + // } +} + +impl Cursor<'_, K, T> { + fn first(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.first()) + } + + fn seek_exact(&mut self, key: ::Key) -> Result>, DatabaseError> { + decode!(self.inner.set_key(key.encode().as_ref())) + } + + fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { + decode!(self.inner.set_range(key.encode().as_ref())) + } + + fn next(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.next()) + } + + fn prev(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.prev()) + } + + fn last(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.last()) + } + + fn current(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.get_current()) + } + + /// Returns the next `(key, value)` pair of a DUPSORT table. + fn next_dup(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.next_dup()) + } + + /// Returns the next `(key, value)` pair skipping the duplicates. + fn next_no_dup(&mut self) -> Result>, DatabaseError> { + decode!(self.inner.next_nodup()) + } + + /// Returns the next `value` of a duplicate `key`. + fn next_dup_val(&mut self) -> Result::Value>, DatabaseError> { + self.inner + .next_dup() + .map_err(|e| DatabaseError::Read(e.into()))? + .map(decode_value::) + .transpose() + } + + fn seek_by_key_subkey( + &mut self, + key: ::Key, + subkey: ::SubKey, + ) -> Result::Value>, DatabaseError> { + self.inner + .get_both_range(key.encode().as_ref(), subkey.encode().as_ref()) + .map_err(|e| DatabaseError::Read(e.into()))? + .map(decode_one::) + .transpose() + } + + // /// Depending on its arguments, returns an iterator starting at: + // /// - Some(key), Some(subkey): a `key` item whose data is >= than `subkey` + // /// - Some(key), None: first item of a specified `key` + // /// - None, Some(subkey): like first case, but in the first key + // /// - None, None: first item in the table + // /// of a DUPSORT table. + // fn walk_dup( + // &mut self, + // key: Option, + // subkey: Option, + // ) -> Result, DatabaseError> { + // let start = match (key, subkey) { + // (Some(key), Some(subkey)) => { + // // encode key and decode it after. + // let key = key.encode().as_ref().to_vec(); + + // self.inner + // .get_both_range(key.as_ref(), subkey.encode().as_ref()) + // .map_err(|e| DatabaseError::Read(e.into()))? + // .map(|val| decoder::((Cow::Owned(key), val))) + // } + // (Some(key), None) => { + // let key = key.encode().as_ref().to_vec(); + + // self.inner + // .set(key.as_ref()) + // .map_err(|e| DatabaseError::Read(e.into()))? + // .map(|val| decoder::((Cow::Owned(key), val))) + // } + // (None, Some(subkey)) => { + // if let Some((key, _)) = self.first()? { + // let key = key.encode().as_ref().to_vec(); + + // self.inner + // .get_both_range(key.as_ref(), subkey.encode().as_ref()) + // .map_err(|e| DatabaseError::Read(e.into()))? + // .map(|val| decoder::((Cow::Owned(key), val))) + // } else { + // let err_code = MDBXError::to_err_code(&MDBXError::NotFound); + // Some(Err(DatabaseError::Read(err_code))) + // } + // } + // (None, None) => self.first().transpose(), + // }; + + // Ok(DupWalker::<'_, T, Self> { cursor: self, start }) + // } +} + +impl Cursor<'_, RW, T> { + /// Database operation that will update an existing row if a specified value already + /// exists in a table, and insert a new row if the specified value doesn't already exist + /// + /// For a DUPSORT table, `upsert` will not actually update-or-insert. If the key already exists, + /// it will append the value to the subkey, even if the subkeys are the same. So if you want + /// to properly upsert, you'll need to `seek_exact` & `delete_current` if the key+subkey was + /// found, before calling `upsert`. + fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + let key = Encode::encode(key); + let value = Compress::compress(value); + self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::UPSERT).map_err(|error| { + DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } + }) + } + + fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + let key = Encode::encode(key); + let value = Compress::compress(value); + self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::NO_OVERWRITE).map_err(|error| { + DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } + }) + } + + /// Appends the data to the end of the table. Consequently, the append operation + /// will fail if the inserted key is less than the last table key + fn append(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + let key = Encode::encode(key); + let value = Compress::compress(value); + self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND).map_err(|error| { + DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } + }) + } + + fn delete_current(&mut self) -> Result<(), DatabaseError> { + self.inner.del(WriteFlags::CURRENT).map_err(|e| DatabaseError::Delete(e.into())) + } +} + +impl Cursor<'_, RW, T> { + fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { + self.inner.del(WriteFlags::NO_DUP_DATA).map_err(DatabaseError::Delete) + } + + fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + let key = Encode::encode(key); + let value = Compress::compress(value); + self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND_DUP).map_err(|error| { + DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } + }) + } +} diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs new file mode 100644 index 0000000000..bb4183f6bd --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -0,0 +1,112 @@ +//! MDBX backend for the database. + +pub mod cursor; +pub mod models; +pub mod tables; +pub mod tx; + +use std::ops::Deref; +use std::path::Path; + +use libmdbx::{ + DatabaseFlags, Environment, EnvironmentFlags, EnvironmentKind, Geometry, Mode, PageSize, + SyncMode, RO, RW, +}; + +use self::tables::{TableType, Tables}; +use self::tx::Tx; +use crate::error::DatabaseError; +use crate::utils; + +const GIGABYTE: usize = 1024 * 1024 * 1024; +const TERABYTE: usize = GIGABYTE * 1024; + +/// MDBX allows up to 32767 readers (`MDBX_READERS_LIMIT`), but we limit it to slightly below that +const DEFAULT_MAX_READERS: u64 = 32_000; + +/// Environment used when opening a MDBX environment. RO/RW. +#[derive(Debug)] +pub enum EnvKind { + /// Read-only MDBX environment. + RO, + /// Read-write MDBX environment. + RW, +} + +/// Wrapper for `libmdbx-sys` environment. +#[derive(Debug)] +pub struct Env(pub libmdbx::Environment); + +impl Env { + /// Opens the database at the specified path with the given `EnvKind`. + /// + /// It does not create the tables, for that call [`Env::create_tables`]. + pub fn open(path: &Path, kind: EnvKind) -> Env { + let mode = match kind { + EnvKind::RO => Mode::ReadOnly, + EnvKind::RW => Mode::ReadWrite { sync_mode: SyncMode::Durable }, + }; + + let mut inner_env = Environment::new(); + inner_env.set_max_dbs(Tables::ALL.len()); + inner_env.set_geometry(Geometry { + // Maximum database size of 1 terabytes + size: Some(0..(1 * TERABYTE)), + // We grow the database in increments of 4 gigabytes + growth_step: Some(4 * GIGABYTE as isize), + // The database never shrinks + shrink_threshold: None, + page_size: Some(PageSize::Set(utils::default_page_size())), + }); + inner_env.set_flags(EnvironmentFlags { + mode, + // We disable readahead because it improves performance for linear scans, but + // worsens it for random access (which is our access pattern outside of sync) + no_rdahead: true, + coalesce: true, + ..Default::default() + }); + // configure more readers + inner_env.set_max_readers(DEFAULT_MAX_READERS); + + let env = Env(inner_env.open(path).unwrap()); + + env + } + + /// Creates all the defined tables, if necessary. + pub fn create_tables(&self) -> Result<(), DatabaseError> { + let tx = self.begin_rw_txn().map_err(|e| DatabaseError::CreateTransaction(e.into()))?; + + for table in Tables::ALL { + let flags = match table.table_type() { + TableType::Table => DatabaseFlags::default(), + TableType::DupSort => DatabaseFlags::DUP_SORT, + }; + + tx.create_db(Some(table.name()), flags) + .map_err(|e| DatabaseError::CreateTable(e.into()))?; + } + + tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?; + + Ok(()) + } +} + +impl<'env, E: EnvironmentKind> Env { + fn tx(&'env self) -> Result, DatabaseError> { + Ok(Tx::new(self.0.begin_ro_txn().map_err(DatabaseError::CreateTransaction)?)) + } + + fn tx_mut(&'env self) -> Result, DatabaseError> { + Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateTransaction)?)) + } +} + +impl Deref for Env { + type Target = Environment; + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/katana/storage/db/src/mdbx/models.rs b/crates/katana/storage/db/src/mdbx/models.rs new file mode 100644 index 0000000000..e9953f5102 --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/models.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +/// The sequential number of all the transactions in the database. +pub type TxNumber = u64; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StoredBlockBodyIndices { + /// The offset in database of the first transaction in the block. + /// + /// `tx_offset` is a key of `Transactions` table. + pub tx_offset: TxNumber, + /// The total number of transactions in the block. + pub tx_count: u64, +} diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/mdbx/tables.rs new file mode 100644 index 0000000000..85ecc60e74 --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/tables.rs @@ -0,0 +1,180 @@ +use katana_primitives::block::{Header, StateUpdate}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, + StorageValue, +}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash}; +use serde::{Deserialize, Serialize}; + +use super::models::{StoredBlockBodyIndices, TxNumber}; +use crate::codecs::{Compress, Decode, Decompress, Encode}; + +pub trait Key: Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone {} +pub trait Value: Compress + Decompress {} + +impl Key for T where T: Serialize + for<'a> Deserialize<'a> + Clone {} +impl Value for T where T: Serialize + for<'a> Deserialize<'a> {} + +/// An asbtraction for a table. +pub trait Table { + /// The name of the table. + const NAME: &'static str; + /// The key type of the table. + type Key: Key; + /// The value type of the table. + type Value: Value; +} + +/// DupSort allows for keys to be repeated in the database. +/// +/// Upstream docs: +pub trait DupSort: Table { + /// Upstream docs: + type SubKey: Key; +} + +/// Enum for the types of tables present in libmdbx. +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum TableType { + /// key value table + Table, + /// Duplicate key value table + DupSort, +} + +pub const NUM_TABLES: usize = 9; + +/// Macro to declare `libmdbx` tables. +#[macro_export] +macro_rules! define_tables_enum { + { [$(($table:ident, $type:expr)),*] } => { + #[derive(Debug, PartialEq, Copy, Clone)] + /// Default tables that should be present inside database. + pub enum Tables { + $( + $table, + )* + } + + impl Tables { + /// Array of all tables in database + pub const ALL: [Tables; NUM_TABLES] = [$(Tables::$table,)*]; + + /// The name of the given table in database + pub const fn name(&self) -> &str { + match self { + $(Tables::$table => { + $table::NAME + },)* + } + } + + /// The type of the given table in database + pub const fn table_type(&self) -> TableType { + match self { + $(Tables::$table => { + $type + },)* + } + } + } + + impl std::fmt::Display for Tables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } + } + + impl std::str::FromStr for Tables { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + $($table::NAME => { + return Ok(Tables::$table) + },)* + _ => { + return Err("Unknown table".to_string()) + } + } + } + } + }; +} + +/// Macro to declare key value table. +#[macro_export] +macro_rules! tables { + { $( $(#[$docs:meta])+ $table_name:ident: ($key:ty $(,$key_type2:ty)?) => $value:ty ),* } => { + $( + $(#[$docs])+ + /// + #[doc = concat!("Takes [`", stringify!($key), "`] as a key and returns [`", stringify!($value), "`].")] + #[derive(Debug)] + pub struct $table_name; + + impl Table for $table_name { + const NAME: &'static str = stringify!($table_name); + type Key = $key; + type Value = $value; + } + + $( + dupsort!($table_name, $key_type2); + )? + + impl std::fmt::Display for $table_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", stringify!($table_name)) + } + } + )* + }; +} + +/// Macro to declare duplicate key value table. +#[macro_export] +macro_rules! dupsort { + ($table_name:ident, $subkey:ty) => { + impl DupSort for $table_name { + type SubKey = $subkey; + } + }; +} + +define_tables_enum! {[ + (Headers, TableType::Table), + (BlockBodyIndices, TableType::Table), + (Transactions, TableType::Table), + (Receipts, TableType::Table), + (StateUpdates, TableType::Table), + (CompiledContractClasses, TableType::Table), + (SierraClasses, TableType::Table), + (ContractInfo, TableType::Table), + (ContractStorage, TableType::DupSort) +]} + +tables! { + /// Store canonical block headers + Headers: (u64) => Header, + /// Store block headers + BlockBodyIndices: (u64) => StoredBlockBodyIndices, + /// Store canonical transactions + TxHashNumber: (TxHash) => TxNumber, + /// Store canonical transactions + Transactions: (TxNumber) => Transaction, + /// Store transaction receipts + Receipts: (TxNumber) => Receipt, + /// Store block state updates + StateUpdates: (u64) => StateUpdate, + /// Store compiled classes + CompiledClassHashes: (ClassHash) => CompiledClassHash, + /// Store compiled contract classes according to its compiled class hash + CompiledContractClasses: (CompiledClassHash) => u64, + /// Store Sierra classes + SierraClasses: (ClassHash) => SierraClass, + /// Store contract information + ContractInfo: (ContractAddress) => GenericContractInfo, + /// Store contract storage + ContractStorage: (ContractAddress, StorageKey) => StorageValue +} diff --git a/crates/katana/storage/db/src/mdbx/tx.rs b/crates/katana/storage/db/src/mdbx/tx.rs new file mode 100644 index 0000000000..d3f685a523 --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/tx.rs @@ -0,0 +1,126 @@ +//! Transaction wrapper for libmdbx-sys. + +use std::str::FromStr; +use std::sync::Arc; + +use libmdbx::ffi::DBI; +use libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, RW}; +use parking_lot::RwLock; + +use super::cursor::Cursor; +use super::tables::{DupSort, Table, Tables, NUM_TABLES}; +use crate::codecs::{Compress, Encode}; +use crate::error::DatabaseError; +use crate::utils::decode_one; + +/// Wrapper for a `libmdbx` transaction. +#[derive(Debug)] +pub struct Tx<'env, K: TransactionKind, E: EnvironmentKind> { + /// Libmdbx-sys transaction. + pub inner: libmdbx::Transaction<'env, K, E>, + /// Database table handle cache. + pub(crate) db_handles: Arc; NUM_TABLES]>>, +} + +impl Tx<'_, K, E> { + /// Gets a table database handle if it exists, otherwise creates it. + pub fn get_dbi(&self) -> Result { + let mut handles = self.db_handles.write(); + let table = Tables::from_str(T::NAME).expect("Requested table should be part of `Tables`."); + + let dbi_handle = handles.get_mut(table as usize).expect("should exist"); + if dbi_handle.is_none() { + *dbi_handle = + Some(self.inner.open_db(Some(T::NAME)).map_err(DatabaseError::OpenDb)?.dbi()); + } + + Ok(dbi_handle.expect("is some; qed")) + } + + fn get(&self, key: T::Key) -> Result::Value>, DatabaseError> { + self.inner + .get(self.get_dbi::()?, key.encode().as_ref()) + .map_err(DatabaseError::Read)? + .map(decode_one::) + .transpose() + } + + /// Commits the transaction. + fn commit(self) -> Result { + self.inner.commit().map_err(DatabaseError::Commit) + } + + /// Aborts the transaction. + fn abort(self) { + drop(self.inner) + } + + /// Returns number of entries in the table using cheap DB stats invocation. + fn entries(&self) -> Result { + Ok(self + .inner + .db_stat_with_dbi(self.get_dbi::()?) + .map_err(DatabaseError::Stat)? + .entries()) + } +} + +impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { + /// Creates new `Tx` object with a `RO` or `RW` transaction. + pub fn new(inner: Transaction<'env, K, E>) -> Self { + Self { inner, db_handles: Default::default() } + } + + /// Create db Cursor + pub fn new_cursor(&self) -> Result, DatabaseError> { + let inner = self + .inner + .cursor_with_dbi(self.get_dbi::()?) + .map_err(DatabaseError::CreateCursor)?; + + Ok(Cursor::new(inner)) + } + + // Iterate over read only values in database. + fn cursor_read(&self) -> Result, DatabaseError> { + self.new_cursor() + } + + /// Iterate over read only values in database. + fn cursor_dup_read(&self) -> Result, DatabaseError> { + self.new_cursor() + } +} + +impl Tx<'_, RW, E> { + fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + let key = key.encode(); + let value = value.compress(); + self.inner.put(self.get_dbi::()?, key, value, WriteFlags::UPSERT).unwrap(); + Ok(()) + } + + fn delete( + &self, + key: T::Key, + value: Option, + ) -> Result { + let value = value.map(Compress::compress); + let value = value.as_ref().map(|v| v.as_ref()); + self.inner.del(self.get_dbi::()?, key.encode(), value).map_err(DatabaseError::Delete) + } + + fn clear(&self) -> Result<(), DatabaseError> { + self.inner.clear_db(self.get_dbi::()?).map_err(DatabaseError::Clear) + } +} + +impl<'env, E: EnvironmentKind> Tx<'env, RW, E> { + fn cursor_write(&self) -> Result, DatabaseError> { + self.new_cursor() + } + + fn cursor_dup_write(&self) -> Result, DatabaseError> { + self.new_cursor() + } +} diff --git a/crates/katana/storage/db/src/utils.rs b/crates/katana/storage/db/src/utils.rs new file mode 100644 index 0000000000..fb491532a3 --- /dev/null +++ b/crates/katana/storage/db/src/utils.rs @@ -0,0 +1,76 @@ +use std::borrow::Cow; +use std::path::Path; + +use crate::codecs::{Decode, Decompress}; +use crate::error::DatabaseError; +use crate::mdbx::tables::Table; + +/// Returns the default page size that can be used in this OS. +pub(crate) fn default_page_size() -> usize { + let os_page_size = page_size::get(); + // source: https://gitflic.ru/project/erthink/libmdbx/blob?file=mdbx.h#line-num-821 + let libmdbx_max_page_size = 0x10000; + // May lead to errors if it's reduced further because of the potential size of the + // data. + let min_page_size = 4096; + os_page_size.clamp(min_page_size, libmdbx_max_page_size) +} + +/// Check if a db is empty. It does not provide any information on the +/// validity of the data in it. We consider a database as non empty when it's a non empty directory. +pub fn is_database_empty>(path: P) -> bool { + let path = path.as_ref(); + if !path.exists() { + true + } else if let Ok(dir) = path.read_dir() { + dir.count() == 0 + } else { + true + } +} + +/// A key-value pair for table `T`. +pub type KeyValue = (::Key, ::Value); + +/// Helper function to decode a `(key, value)` pair. +pub(crate) fn decoder<'a, T: Table>( + kv: (Cow<'a, [u8]>, Cow<'a, [u8]>), +) -> Result<(T::Key, T::Value), DatabaseError> +where + T::Key: Decode, + T::Value: Decompress, +{ + let key = match kv.0 { + Cow::Borrowed(k) => Decode::decode(k)?, + Cow::Owned(k) => Decode::decode(k)?, + }; + let value = match kv.1 { + Cow::Borrowed(v) => Decompress::decompress(v)?, + Cow::Owned(v) => Decompress::decompress(v)?, + }; + Ok((key, value)) +} + +/// Helper function to decode only a value from a `(key, value)` pair. +pub(crate) fn decode_value<'a, T>( + kv: (Cow<'a, [u8]>, Cow<'a, [u8]>), +) -> Result +where + T: Table, +{ + Ok(match kv.1 { + Cow::Borrowed(v) => Decompress::decompress(v)?, + Cow::Owned(v) => Decompress::decompress(v)?, + }) +} + +/// Helper function to decode a value. It can be a key or subkey. +pub(crate) fn decode_one(value: Cow<'_, [u8]>) -> Result +where + T: Table, +{ + Ok(match value { + Cow::Borrowed(v) => Decompress::decompress(v)?, + Cow::Owned(v) => Decompress::decompress(v)?, + }) +} From 265b925da4d85d5a219123f059c4579216fb0a3f Mon Sep 17 00:00:00 2001 From: Kariy Date: Sat, 11 Nov 2023 14:29:01 +0300 Subject: [PATCH 065/192] Update mdbx implementation --- crates/katana/storage/db/src/error.rs | 10 +- crates/katana/storage/db/src/lib.rs | 32 +++- crates/katana/storage/db/src/mdbx/cursor.rs | 159 +++----------------- crates/katana/storage/db/src/mdbx/mod.rs | 69 +++++---- crates/katana/storage/db/src/mdbx/tables.rs | 6 +- crates/katana/storage/db/src/mdbx/tx.rs | 93 ++++++------ 6 files changed, 143 insertions(+), 226 deletions(-) diff --git a/crates/katana/storage/db/src/error.rs b/crates/katana/storage/db/src/error.rs index b36c41b7a4..70bb255153 100644 --- a/crates/katana/storage/db/src/error.rs +++ b/crates/katana/storage/db/src/error.rs @@ -1,5 +1,8 @@ #[derive(Debug, thiserror::Error)] pub enum DatabaseError { + #[error("failed to open an environment: {0}")] + OpenEnv(libmdbx::Error), + #[error(transparent)] Codec(#[from] CodecError), @@ -24,8 +27,11 @@ pub enum DatabaseError { #[error("failed to create cursor: {0}")] CreateCursor(libmdbx::Error), - #[error("failed to create transaction: {0}")] - CreateTransaction(libmdbx::Error), + #[error("failed to create read-only transaction: {0}")] + CreateROTx(libmdbx::Error), + + #[error("failed to create a read-write transaction: {0}")] + CreateRWTx(libmdbx::Error), #[error("failed to delete entry: {0}")] Delete(libmdbx::Error), diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 7ce7fa7c86..a4a39ac56e 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -2,18 +2,38 @@ use std::path::Path; -use anyhow::Result; +use anyhow::Context; +use libmdbx::WriteMap; pub mod codecs; pub mod error; pub mod mdbx; pub mod utils; -/// Initialize the database at the given path -pub fn init_db>(path: P) -> Result<()> { - Ok(()) +use mdbx::{Env, EnvKind}; +use utils::is_database_empty; + +/// Initialize the database at the given path and returning a handle to the its +/// environment. +/// +/// This will create the default tables, if necessary. +pub fn init_db>(path: P) -> anyhow::Result> { + if is_database_empty(path.as_ref()) { + // TODO: create dir if it doesn't exist and insert db version file + std::fs::create_dir_all(path.as_ref()).with_context(|| { + format!("Creating database directory at path {}", path.as_ref().display()) + })?; + } else { + // TODO: check if db version file exists and if it's compatible + } + let env = open_db(path)?; + env.create_tables()?; + Ok(env) } -pub fn open_rw_db>(path: P) -> Result<()> { - Ok(()) +/// Open the database at the given `path` in read-write mode. +pub fn open_db>(path: P) -> anyhow::Result> { + Env::open(path.as_ref(), EnvKind::RW).with_context(|| { + format!("Opening database in read-write mode at path {}", path.as_ref().display()) + }) } diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index c2b1465356..d253de2f2b 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -2,32 +2,25 @@ use std::marker::PhantomData; -use libmdbx::{self, TransactionKind, WriteFlags, RO, RW}; +use libmdbx::{self, TransactionKind, WriteFlags, RW}; use super::tables::{DupSort, Table}; use crate::codecs::{Compress, Encode}; use crate::error::DatabaseError; use crate::utils::{decode_one, decode_value, KeyValue}; -/// Read only Cursor. -pub type CursorRO<'tx, T> = Cursor<'tx, RO, T>; -/// Read write cursor. -pub type CursorRW<'tx, T> = Cursor<'tx, RW, T>; - /// Cursor wrapper to access KV items. #[derive(Debug)] pub struct Cursor<'tx, K: TransactionKind, T: Table> { /// Inner `libmdbx` cursor. pub(crate) inner: libmdbx::Cursor<'tx, K>, - /// Cache buffer that receives compressed values. - buf: Vec, /// Phantom data to enforce encoding/decoding. _dbi: PhantomData, } impl<'tx, K: TransactionKind, T: Table> Cursor<'tx, K, T> { pub(crate) fn new(inner: libmdbx::Cursor<'tx, K>) -> Self { - Self { inner, buf: Vec::new(), _dbi: PhantomData } + Self { inner, _dbi: PhantomData } } } @@ -39,168 +32,64 @@ macro_rules! decode { }; } -impl Cursor<'_, K, T> { - // fn walk(&mut self, start_key: Option) -> Result, DatabaseError> - // where - // Self: Sized, - // { - // let start = if let Some(start_key) = start_key { - // self.inner - // .set_range(start_key.encode().as_ref()) - // .map_err(|e| DatabaseError::Read(e.into()))? - // .map(decoder::) - // } else { - // self.first().transpose() - // }; - - // Ok(Walker::new(self, start)) - // } - - // fn walk_range( - // &mut self, - // range: impl RangeBounds, - // ) -> Result, DatabaseError> - // where - // Self: Sized, - // { - // let start = match range.start_bound().cloned() { - // Bound::Included(key) => self.inner.set_range(key.encode().as_ref()), - // Bound::Excluded(_key) => { - // unreachable!("Rust doesn't allow for Bound::Excluded in starting bounds"); - // } - // Bound::Unbounded => self.inner.first(), - // } - // .map_err(|e| DatabaseError::Read(e.into()))? - // .map(decoder::); - - // Ok(RangeWalker::new(self, start, range.end_bound().cloned())) - // } - - // fn walk_back( - // &mut self, - // start_key: Option, - // ) -> Result, DatabaseError> - // where - // Self: Sized, - // { - // let start = if let Some(start_key) = start_key { - // decode!(self.inner.set_range(start_key.encode().as_ref())) - // } else { - // self.last() - // } - // .transpose(); - - // Ok(ReverseWalker::new(self, start)) - // } -} - impl Cursor<'_, K, T> { - fn first(&mut self) -> Result>, DatabaseError> { + pub fn first(&mut self) -> Result>, DatabaseError> { decode!(self.inner.first()) } - fn seek_exact(&mut self, key: ::Key) -> Result>, DatabaseError> { + pub fn seek_exact( + &mut self, + key: ::Key, + ) -> Result>, DatabaseError> { decode!(self.inner.set_key(key.encode().as_ref())) } - fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { + pub fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { decode!(self.inner.set_range(key.encode().as_ref())) } - fn next(&mut self) -> Result>, DatabaseError> { + pub fn next(&mut self) -> Result>, DatabaseError> { decode!(self.inner.next()) } - fn prev(&mut self) -> Result>, DatabaseError> { + pub fn prev(&mut self) -> Result>, DatabaseError> { decode!(self.inner.prev()) } - fn last(&mut self) -> Result>, DatabaseError> { + pub fn last(&mut self) -> Result>, DatabaseError> { decode!(self.inner.last()) } - fn current(&mut self) -> Result>, DatabaseError> { + pub fn current(&mut self) -> Result>, DatabaseError> { decode!(self.inner.get_current()) } /// Returns the next `(key, value)` pair of a DUPSORT table. - fn next_dup(&mut self) -> Result>, DatabaseError> { + pub fn next_dup(&mut self) -> Result>, DatabaseError> { decode!(self.inner.next_dup()) } /// Returns the next `(key, value)` pair skipping the duplicates. - fn next_no_dup(&mut self) -> Result>, DatabaseError> { + pub fn next_no_dup(&mut self) -> Result>, DatabaseError> { decode!(self.inner.next_nodup()) } /// Returns the next `value` of a duplicate `key`. - fn next_dup_val(&mut self) -> Result::Value>, DatabaseError> { - self.inner - .next_dup() - .map_err(|e| DatabaseError::Read(e.into()))? - .map(decode_value::) - .transpose() + pub fn next_dup_val(&mut self) -> Result::Value>, DatabaseError> { + self.inner.next_dup().map_err(DatabaseError::Read)?.map(decode_value::).transpose() } - fn seek_by_key_subkey( + pub fn seek_by_key_subkey( &mut self, key: ::Key, subkey: ::SubKey, ) -> Result::Value>, DatabaseError> { self.inner .get_both_range(key.encode().as_ref(), subkey.encode().as_ref()) - .map_err(|e| DatabaseError::Read(e.into()))? + .map_err(DatabaseError::Read)? .map(decode_one::) .transpose() } - - // /// Depending on its arguments, returns an iterator starting at: - // /// - Some(key), Some(subkey): a `key` item whose data is >= than `subkey` - // /// - Some(key), None: first item of a specified `key` - // /// - None, Some(subkey): like first case, but in the first key - // /// - None, None: first item in the table - // /// of a DUPSORT table. - // fn walk_dup( - // &mut self, - // key: Option, - // subkey: Option, - // ) -> Result, DatabaseError> { - // let start = match (key, subkey) { - // (Some(key), Some(subkey)) => { - // // encode key and decode it after. - // let key = key.encode().as_ref().to_vec(); - - // self.inner - // .get_both_range(key.as_ref(), subkey.encode().as_ref()) - // .map_err(|e| DatabaseError::Read(e.into()))? - // .map(|val| decoder::((Cow::Owned(key), val))) - // } - // (Some(key), None) => { - // let key = key.encode().as_ref().to_vec(); - - // self.inner - // .set(key.as_ref()) - // .map_err(|e| DatabaseError::Read(e.into()))? - // .map(|val| decoder::((Cow::Owned(key), val))) - // } - // (None, Some(subkey)) => { - // if let Some((key, _)) = self.first()? { - // let key = key.encode().as_ref().to_vec(); - - // self.inner - // .get_both_range(key.as_ref(), subkey.encode().as_ref()) - // .map_err(|e| DatabaseError::Read(e.into()))? - // .map(|val| decoder::((Cow::Owned(key), val))) - // } else { - // let err_code = MDBXError::to_err_code(&MDBXError::NotFound); - // Some(Err(DatabaseError::Read(err_code))) - // } - // } - // (None, None) => self.first().transpose(), - // }; - - // Ok(DupWalker::<'_, T, Self> { cursor: self, start }) - // } } impl Cursor<'_, RW, T> { @@ -211,7 +100,7 @@ impl Cursor<'_, RW, T> { /// it will append the value to the subkey, even if the subkeys are the same. So if you want /// to properly upsert, you'll need to `seek_exact` & `delete_current` if the key+subkey was /// found, before calling `upsert`. - fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + pub fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::UPSERT).map_err(|error| { @@ -219,7 +108,7 @@ impl Cursor<'_, RW, T> { }) } - fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + pub fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::NO_OVERWRITE).map_err(|error| { @@ -229,7 +118,7 @@ impl Cursor<'_, RW, T> { /// Appends the data to the end of the table. Consequently, the append operation /// will fail if the inserted key is less than the last table key - fn append(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + pub fn append(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND).map_err(|error| { @@ -237,17 +126,17 @@ impl Cursor<'_, RW, T> { }) } - fn delete_current(&mut self) -> Result<(), DatabaseError> { + pub fn delete_current(&mut self) -> Result<(), DatabaseError> { self.inner.del(WriteFlags::CURRENT).map_err(|e| DatabaseError::Delete(e.into())) } } impl Cursor<'_, RW, T> { - fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { + pub fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { self.inner.del(WriteFlags::NO_DUP_DATA).map_err(DatabaseError::Delete) } - fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { + pub fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND_DUP).map_err(|error| { diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index bb4183f6bd..70e760ceb5 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -41,42 +41,40 @@ impl Env { /// Opens the database at the specified path with the given `EnvKind`. /// /// It does not create the tables, for that call [`Env::create_tables`]. - pub fn open(path: &Path, kind: EnvKind) -> Env { + pub fn open(path: &Path, kind: EnvKind) -> Result, DatabaseError> { let mode = match kind { EnvKind::RO => Mode::ReadOnly, EnvKind::RW => Mode::ReadWrite { sync_mode: SyncMode::Durable }, }; - let mut inner_env = Environment::new(); - inner_env.set_max_dbs(Tables::ALL.len()); - inner_env.set_geometry(Geometry { - // Maximum database size of 1 terabytes - size: Some(0..(1 * TERABYTE)), - // We grow the database in increments of 4 gigabytes - growth_step: Some(4 * GIGABYTE as isize), - // The database never shrinks - shrink_threshold: None, - page_size: Some(PageSize::Set(utils::default_page_size())), - }); - inner_env.set_flags(EnvironmentFlags { - mode, - // We disable readahead because it improves performance for linear scans, but - // worsens it for random access (which is our access pattern outside of sync) - no_rdahead: true, - coalesce: true, - ..Default::default() - }); - // configure more readers - inner_env.set_max_readers(DEFAULT_MAX_READERS); - - let env = Env(inner_env.open(path).unwrap()); - - env + let mut builder = Environment::new(); + builder + .set_max_dbs(Tables::ALL.len()) + .set_geometry(Geometry { + // Maximum database size of 1 terabytes + size: Some(0..(1 * TERABYTE)), + // We grow the database in increments of 4 gigabytes + growth_step: Some(4 * GIGABYTE as isize), + // The database never shrinks + shrink_threshold: None, + page_size: Some(PageSize::Set(utils::default_page_size())), + }) + .set_flags(EnvironmentFlags { + mode, + // We disable readahead because it improves performance for linear scans, but + // worsens it for random access (which is our access pattern outside of sync) + no_rdahead: true, + coalesce: true, + ..Default::default() + }) + .set_max_readers(DEFAULT_MAX_READERS); + + Ok(Env(builder.open(path).map_err(DatabaseError::OpenEnv)?)) } - /// Creates all the defined tables, if necessary. + /// Creates all the defined tables in [`Tables`], if necessary. pub fn create_tables(&self) -> Result<(), DatabaseError> { - let tx = self.begin_rw_txn().map_err(|e| DatabaseError::CreateTransaction(e.into()))?; + let tx = self.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?; for table in Tables::ALL { let flags = match table.table_type() { @@ -84,23 +82,24 @@ impl Env { TableType::DupSort => DatabaseFlags::DUP_SORT, }; - tx.create_db(Some(table.name()), flags) - .map_err(|e| DatabaseError::CreateTable(e.into()))?; + tx.create_db(Some(table.name()), flags).map_err(DatabaseError::CreateTable)?; } - tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?; + tx.commit().map_err(DatabaseError::Commit)?; Ok(()) } } impl<'env, E: EnvironmentKind> Env { - fn tx(&'env self) -> Result, DatabaseError> { - Ok(Tx::new(self.0.begin_ro_txn().map_err(DatabaseError::CreateTransaction)?)) + /// Begin a read-only transaction. + pub fn tx(&'env self) -> Result, DatabaseError> { + Ok(Tx::new(self.0.begin_ro_txn().map_err(DatabaseError::CreateROTx)?)) } - fn tx_mut(&'env self) -> Result, DatabaseError> { - Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateTransaction)?)) + /// Begin a read-write transaction. + pub fn tx_mut(&'env self) -> Result, DatabaseError> { + Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?)) } } diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/mdbx/tables.rs index 85ecc60e74..1b2eaeb829 100644 --- a/crates/katana/storage/db/src/mdbx/tables.rs +++ b/crates/katana/storage/db/src/mdbx/tables.rs @@ -4,6 +4,7 @@ use katana_primitives::contract::{ StorageValue, }; use katana_primitives::transaction::{Receipt, Transaction, TxHash}; +use katana_primitives::FieldElement; use serde::{Deserialize, Serialize}; use super::models::{StoredBlockBodyIndices, TxNumber}; @@ -157,7 +158,10 @@ define_tables_enum! {[ tables! { /// Store canonical block headers Headers: (u64) => Header, - /// Store block headers + /// Stores block hashes according to its block number + BlockHashes: (u64) => FieldElement, + /// Block number to its body indices which stores the tx number of + /// the first tx in the block and the number of txs in the block. BlockBodyIndices: (u64) => StoredBlockBodyIndices, /// Store canonical transactions TxHashNumber: (TxHash) => TxNumber, diff --git a/crates/katana/storage/db/src/mdbx/tx.rs b/crates/katana/storage/db/src/mdbx/tx.rs index d3f685a523..11bf343209 100644 --- a/crates/katana/storage/db/src/mdbx/tx.rs +++ b/crates/katana/storage/db/src/mdbx/tx.rs @@ -1,7 +1,6 @@ //! Transaction wrapper for libmdbx-sys. use std::str::FromStr; -use std::sync::Arc; use libmdbx::ffi::DBI; use libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, RW}; @@ -19,10 +18,25 @@ pub struct Tx<'env, K: TransactionKind, E: EnvironmentKind> { /// Libmdbx-sys transaction. pub inner: libmdbx::Transaction<'env, K, E>, /// Database table handle cache. - pub(crate) db_handles: Arc; NUM_TABLES]>>, + pub(crate) db_handles: RwLock<[Option; NUM_TABLES]>, } -impl Tx<'_, K, E> { +impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { + /// Creates new `Tx` object with a `RO` or `RW` transaction. + pub fn new(inner: Transaction<'env, K, E>) -> Self { + Self { inner, db_handles: Default::default() } + } + + /// Create db Cursor + pub fn new_cursor(&self) -> Result, DatabaseError> { + let inner = self + .inner + .cursor_with_dbi(self.get_dbi::()?) + .map_err(DatabaseError::CreateCursor)?; + + Ok(Cursor::new(inner)) + } + /// Gets a table database handle if it exists, otherwise creates it. pub fn get_dbi(&self) -> Result { let mut handles = self.db_handles.write(); @@ -37,70 +51,45 @@ impl Tx<'_, K, E> { Ok(dbi_handle.expect("is some; qed")) } - fn get(&self, key: T::Key) -> Result::Value>, DatabaseError> { + /// Gets a value from a table using the given key. + pub fn get(&self, key: T::Key) -> Result::Value>, DatabaseError> { + let key = Encode::encode(key); self.inner - .get(self.get_dbi::()?, key.encode().as_ref()) + .get(self.get_dbi::()?, key.as_ref()) .map_err(DatabaseError::Read)? .map(decode_one::) .transpose() } - /// Commits the transaction. - fn commit(self) -> Result { - self.inner.commit().map_err(DatabaseError::Commit) - } - - /// Aborts the transaction. - fn abort(self) { - drop(self.inner) - } - /// Returns number of entries in the table using cheap DB stats invocation. - fn entries(&self) -> Result { + pub fn entries(&self) -> Result { Ok(self .inner .db_stat_with_dbi(self.get_dbi::()?) .map_err(DatabaseError::Stat)? .entries()) } -} -impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { - /// Creates new `Tx` object with a `RO` or `RW` transaction. - pub fn new(inner: Transaction<'env, K, E>) -> Self { - Self { inner, db_handles: Default::default() } - } - - /// Create db Cursor - pub fn new_cursor(&self) -> Result, DatabaseError> { - let inner = self - .inner - .cursor_with_dbi(self.get_dbi::()?) - .map_err(DatabaseError::CreateCursor)?; - - Ok(Cursor::new(inner)) - } - - // Iterate over read only values in database. - fn cursor_read(&self) -> Result, DatabaseError> { + // Creates a cursor to iterate over a table values. + pub fn cursor(&self) -> Result, DatabaseError> { self.new_cursor() } - /// Iterate over read only values in database. - fn cursor_dup_read(&self) -> Result, DatabaseError> { + // Creates a cursor to iterate over a `DUPSORT` table values. + pub fn cursor_dup(&self) -> Result, DatabaseError> { self.new_cursor() } } -impl Tx<'_, RW, E> { - fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { +impl<'env, E: EnvironmentKind> Tx<'env, RW, E> { + pub fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = key.encode(); let value = value.compress(); self.inner.put(self.get_dbi::()?, key, value, WriteFlags::UPSERT).unwrap(); Ok(()) } - fn delete( + pub fn delete( &self, key: T::Key, value: Option, @@ -110,17 +99,27 @@ impl Tx<'_, RW, E> { self.inner.del(self.get_dbi::()?, key.encode(), value).map_err(DatabaseError::Delete) } - fn clear(&self) -> Result<(), DatabaseError> { + pub fn clear(&self) -> Result<(), DatabaseError> { self.inner.clear_db(self.get_dbi::()?).map_err(DatabaseError::Clear) } -} -impl<'env, E: EnvironmentKind> Tx<'env, RW, E> { - fn cursor_write(&self) -> Result, DatabaseError> { - self.new_cursor() + /// Commits the transaction. + pub fn commit(self) -> Result { + self.inner.commit().map_err(DatabaseError::Commit) } - fn cursor_dup_write(&self) -> Result, DatabaseError> { - self.new_cursor() + /// Aborts the transaction. + pub fn abort(self) { + drop(self.inner) + } +} + +impl<'env, K, E> From> for Tx<'env, K, E> +where + K: TransactionKind, + E: EnvironmentKind, +{ + fn from(inner: libmdbx::Transaction<'env, K, E>) -> Self { + Tx::new(inner) } } From 9525272e95fa769663825503e04faaff442060e3 Mon Sep 17 00:00:00 2001 From: Kariy Date: Sat, 11 Nov 2023 14:30:59 +0300 Subject: [PATCH 066/192] use types from primitives --- crates/katana/storage/db/src/mdbx/tables.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/mdbx/tables.rs index 1b2eaeb829..b85f17e9c1 100644 --- a/crates/katana/storage/db/src/mdbx/tables.rs +++ b/crates/katana/storage/db/src/mdbx/tables.rs @@ -1,10 +1,9 @@ -use katana_primitives::block::{Header, StateUpdate}; +use katana_primitives::block::{BlockHash, BlockNumber, Header, StateUpdate}; use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; use katana_primitives::transaction::{Receipt, Transaction, TxHash}; -use katana_primitives::FieldElement; use serde::{Deserialize, Serialize}; use super::models::{StoredBlockBodyIndices, TxNumber}; @@ -157,12 +156,12 @@ define_tables_enum! {[ tables! { /// Store canonical block headers - Headers: (u64) => Header, + Headers: (BlockNumber) => Header, /// Stores block hashes according to its block number - BlockHashes: (u64) => FieldElement, + BlockHashes: (BlockNumber) => BlockHash, /// Block number to its body indices which stores the tx number of /// the first tx in the block and the number of txs in the block. - BlockBodyIndices: (u64) => StoredBlockBodyIndices, + BlockBodyIndices: (BlockNumber) => StoredBlockBodyIndices, /// Store canonical transactions TxHashNumber: (TxHash) => TxNumber, /// Store canonical transactions @@ -170,7 +169,7 @@ tables! { /// Store transaction receipts Receipts: (TxNumber) => Receipt, /// Store block state updates - StateUpdates: (u64) => StateUpdate, + StateUpdates: (BlockNumber) => StateUpdate, /// Store compiled classes CompiledClassHashes: (ClassHash) => CompiledClassHash, /// Store compiled contract classes according to its compiled class hash From 77eb72b34773fec5fc0c49dbc69259403e9a804b Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 16 Nov 2023 16:59:16 +0300 Subject: [PATCH 067/192] Add tables for state updates --- crates/katana/storage/db/src/mdbx/models.rs | 4 +-- crates/katana/storage/db/src/mdbx/tables.rs | 29 ++++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/katana/storage/db/src/mdbx/models.rs b/crates/katana/storage/db/src/mdbx/models.rs index e9953f5102..bca0d4164d 100644 --- a/crates/katana/storage/db/src/mdbx/models.rs +++ b/crates/katana/storage/db/src/mdbx/models.rs @@ -1,8 +1,6 @@ +use katana_primitives::transaction::TxNumber; use serde::{Deserialize, Serialize}; -/// The sequential number of all the transactions in the database. -pub type TxNumber = u64; - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StoredBlockBodyIndices { /// The offset in database of the first transaction in the block. diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/mdbx/tables.rs index b85f17e9c1..e17ef1eb59 100644 --- a/crates/katana/storage/db/src/mdbx/tables.rs +++ b/crates/katana/storage/db/src/mdbx/tables.rs @@ -1,12 +1,12 @@ -use katana_primitives::block::{BlockHash, BlockNumber, Header, StateUpdate}; +use katana_primitives::block::{BlockHash, BlockNumber, Header}; use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::transaction::{Receipt, Transaction, TxHash}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; -use super::models::{StoredBlockBodyIndices, TxNumber}; +use super::models::StoredBlockBodyIndices; use crate::codecs::{Compress, Decode, Decompress, Encode}; pub trait Key: Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone {} @@ -42,7 +42,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 9; +pub const NUM_TABLES: usize = 14; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -144,10 +144,15 @@ macro_rules! dupsort { define_tables_enum! {[ (Headers, TableType::Table), + (BlockHashes, TableType::Table), + (BlockNumbers, TableType::Table), (BlockBodyIndices, TableType::Table), + (TxHashNumber, TableType::Table), (Transactions, TableType::Table), (Receipts, TableType::Table), - (StateUpdates, TableType::Table), + (ClassDeclarations, TableType::Table), + (ContractDeployments, TableType::Table), + (CompiledClassHashes, TableType::Table), (CompiledContractClasses, TableType::Table), (SierraClasses, TableType::Table), (ContractInfo, TableType::Table), @@ -159,24 +164,28 @@ tables! { Headers: (BlockNumber) => Header, /// Stores block hashes according to its block number BlockHashes: (BlockNumber) => BlockHash, + /// Stores block numbers according to its block hash + BlockNumbers: (BlockHash) => BlockNumber, /// Block number to its body indices which stores the tx number of /// the first tx in the block and the number of txs in the block. BlockBodyIndices: (BlockNumber) => StoredBlockBodyIndices, - /// Store canonical transactions + /// Store canonical transactions hashes according to its tx number TxHashNumber: (TxHash) => TxNumber, /// Store canonical transactions Transactions: (TxNumber) => Transaction, /// Store transaction receipts Receipts: (TxNumber) => Receipt, - /// Store block state updates - StateUpdates: (BlockNumber) => StateUpdate, + /// Stores the list of class hashes according to the block number it was declared in. + ClassDeclarations: (BlockNumber) => Vec, + /// Store the list of contracts deployed in a block according to its block number. + ContractDeployments: (BlockNumber) => Vec, /// Store compiled classes CompiledClassHashes: (ClassHash) => CompiledClassHash, /// Store compiled contract classes according to its compiled class hash CompiledContractClasses: (CompiledClassHash) => u64, - /// Store Sierra classes + /// Store Sierra classes according to its class hash SierraClasses: (ClassHash) => SierraClass, - /// Store contract information + /// Store contract information according to its contract address ContractInfo: (ContractAddress) => GenericContractInfo, /// Store contract storage ContractStorage: (ContractAddress, StorageKey) => StorageValue From 22dd8e1b2c9c099d4ef1ea0fdcb266c7abe80eba Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 16 Nov 2023 16:59:53 +0300 Subject: [PATCH 068/192] Statisfy clippy --- crates/katana/storage/db/src/mdbx/cursor.rs | 85 ++++++++++++++------- crates/katana/storage/db/src/mdbx/mod.rs | 2 +- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index d253de2f2b..a534dcade1 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -32,51 +32,55 @@ macro_rules! decode { }; } +#[allow(clippy::should_implement_trait)] impl Cursor<'_, K, T> { pub fn first(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.first()) + decode!(libmdbx::Cursor::first(&mut self.inner)) } pub fn seek_exact( &mut self, key: ::Key, ) -> Result>, DatabaseError> { - decode!(self.inner.set_key(key.encode().as_ref())) + decode!(libmdbx::Cursor::set_key(&mut self.inner, key.encode().as_ref())) } pub fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { - decode!(self.inner.set_range(key.encode().as_ref())) + decode!(libmdbx::Cursor::set_range(&mut self.inner, key.encode().as_ref())) } pub fn next(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.next()) + decode!(libmdbx::Cursor::next(&mut self.inner)) } pub fn prev(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.prev()) + decode!(libmdbx::Cursor::prev(&mut self.inner)) } pub fn last(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.last()) + decode!(libmdbx::Cursor::last(&mut self.inner)) } pub fn current(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.get_current()) + decode!(libmdbx::Cursor::get_current(&mut self.inner)) } /// Returns the next `(key, value)` pair of a DUPSORT table. pub fn next_dup(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.next_dup()) + decode!(libmdbx::Cursor::next_dup(&mut self.inner)) } /// Returns the next `(key, value)` pair skipping the duplicates. pub fn next_no_dup(&mut self) -> Result>, DatabaseError> { - decode!(self.inner.next_nodup()) + decode!(libmdbx::Cursor::next_nodup(&mut self.inner)) } /// Returns the next `value` of a duplicate `key`. pub fn next_dup_val(&mut self) -> Result::Value>, DatabaseError> { - self.inner.next_dup().map_err(DatabaseError::Read)?.map(decode_value::).transpose() + libmdbx::Cursor::next_dup(&mut self.inner) + .map_err(DatabaseError::Read)? + .map(decode_value::) + .transpose() } pub fn seek_by_key_subkey( @@ -84,11 +88,14 @@ impl Cursor<'_, K, T> { key: ::Key, subkey: ::SubKey, ) -> Result::Value>, DatabaseError> { - self.inner - .get_both_range(key.encode().as_ref(), subkey.encode().as_ref()) - .map_err(DatabaseError::Read)? - .map(decode_one::) - .transpose() + libmdbx::Cursor::get_both_range( + &mut self.inner, + key.encode().as_ref(), + subkey.encode().as_ref(), + ) + .map_err(DatabaseError::Read)? + .map(decode_one::) + .transpose() } } @@ -103,16 +110,29 @@ impl Cursor<'_, RW, T> { pub fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); - self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::UPSERT).map_err(|error| { - DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } - }) + + libmdbx::Cursor::put(&mut self.inner, key.as_ref(), value.as_ref(), WriteFlags::UPSERT) + .map_err(|error| DatabaseError::Write { + error, + table: T::NAME, + key: Box::from(key.as_ref()), + }) } pub fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); - self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::NO_OVERWRITE).map_err(|error| { - DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } + + libmdbx::Cursor::put( + &mut self.inner, + key.as_ref(), + value.as_ref(), + WriteFlags::NO_OVERWRITE, + ) + .map_err(|error| DatabaseError::Write { + error, + table: T::NAME, + key: Box::from(key.as_ref()), }) } @@ -121,26 +141,35 @@ impl Cursor<'_, RW, T> { pub fn append(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); - self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND).map_err(|error| { - DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } - }) + + libmdbx::Cursor::put(&mut self.inner, key.as_ref(), value.as_ref(), WriteFlags::APPEND) + .map_err(|error| DatabaseError::Write { + error, + table: T::NAME, + key: Box::from(key.as_ref()), + }) } pub fn delete_current(&mut self) -> Result<(), DatabaseError> { - self.inner.del(WriteFlags::CURRENT).map_err(|e| DatabaseError::Delete(e.into())) + libmdbx::Cursor::del(&mut self.inner, WriteFlags::CURRENT).map_err(DatabaseError::Delete) } } impl Cursor<'_, RW, T> { pub fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { - self.inner.del(WriteFlags::NO_DUP_DATA).map_err(DatabaseError::Delete) + libmdbx::Cursor::del(&mut self.inner, WriteFlags::NO_DUP_DATA) + .map_err(DatabaseError::Delete) } pub fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); - self.inner.put(key.as_ref(), value.as_ref(), WriteFlags::APPEND_DUP).map_err(|error| { - DatabaseError::Write { error, table: T::NAME, key: Box::from(key.as_ref()) } - }) + + libmdbx::Cursor::put(&mut self.inner, key.as_ref(), value.as_ref(), WriteFlags::APPEND_DUP) + .map_err(|error| DatabaseError::Write { + error, + table: T::NAME, + key: Box::from(key.as_ref()), + }) } } diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 70e760ceb5..504af5d92a 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -52,7 +52,7 @@ impl Env { .set_max_dbs(Tables::ALL.len()) .set_geometry(Geometry { // Maximum database size of 1 terabytes - size: Some(0..(1 * TERABYTE)), + size: Some(0..(TERABYTE)), // We grow the database in increments of 4 gigabytes growth_step: Some(4 * GIGABYTE as isize), // The database never shrinks From 679c5c0b3b60a8fd1fbe713ef6ee31a38fb87894 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 20 Nov 2023 22:31:18 +0800 Subject: [PATCH 069/192] Update get page size function --- crates/katana/storage/db/src/utils.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/katana/storage/db/src/utils.rs b/crates/katana/storage/db/src/utils.rs index fb491532a3..67d25cfb70 100644 --- a/crates/katana/storage/db/src/utils.rs +++ b/crates/katana/storage/db/src/utils.rs @@ -5,15 +5,15 @@ use crate::codecs::{Decode, Decompress}; use crate::error::DatabaseError; use crate::mdbx::tables::Table; -/// Returns the default page size that can be used in this OS. +// May lead to errors if it's reduced further because of the potential size of the data. +const MIN_PAGE_SIZE: usize = 4096; +// source: https://gitflic.ru/project/erthink/libmdbx/blob?file=mdbx.h#line-num-821 +const LIBMDBX_MAX_PAGE_SIZE: usize = 65536; + +/// Returns the default page size (in bytes) that can be used in this OS. pub(crate) fn default_page_size() -> usize { let os_page_size = page_size::get(); - // source: https://gitflic.ru/project/erthink/libmdbx/blob?file=mdbx.h#line-num-821 - let libmdbx_max_page_size = 0x10000; - // May lead to errors if it's reduced further because of the potential size of the - // data. - let min_page_size = 4096; - os_page_size.clamp(min_page_size, libmdbx_max_page_size) + os_page_size.clamp(MIN_PAGE_SIZE, LIBMDBX_MAX_PAGE_SIZE) } /// Check if a db is empty. It does not provide any information on the From 2098f4d89af3144ffb156f482d61d5c0a12a16a4 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 06:38:15 +0800 Subject: [PATCH 070/192] Add storeable contract class types --- Cargo.lock | 4 + crates/katana/storage/db/Cargo.toml | 6 + crates/katana/storage/db/src/lib.rs | 1 + .../src/mdbx/{models.rs => models/block.rs} | 0 .../storage/db/src/mdbx/models/contract.rs | 124 +++++++ .../katana/storage/db/src/mdbx/models/mod.rs | 2 + crates/katana/storage/db/src/mdbx/tables.rs | 5 +- .../katana/storage/db/src/serde/blockifier.rs | 310 ++++++++++++++++++ crates/katana/storage/db/src/serde/mod.rs | 3 + 9 files changed, 453 insertions(+), 2 deletions(-) rename crates/katana/storage/db/src/mdbx/{models.rs => models/block.rs} (100%) create mode 100644 crates/katana/storage/db/src/mdbx/models/contract.rs create mode 100644 crates/katana/storage/db/src/mdbx/models/mod.rs create mode 100644 crates/katana/storage/db/src/serde/blockifier.rs create mode 100644 crates/katana/storage/db/src/serde/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5c13cf6a06..fbdaaa038b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5119,12 +5119,16 @@ version = "0.3.14" dependencies = [ "anyhow", "bincode 1.3.3", + "blockifier", + "cairo-vm", "flate2", "katana-primitives", "page_size", "parking_lot 0.12.1", "reth-libmdbx", "serde", + "serde_json", + "starknet_api", "thiserror", ] diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 1e9f84be70..02729302ac 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -16,3 +16,9 @@ page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true thiserror.workspace = true + +# blockifier types serde compatiblity +blockifier.workspace = true +cairo-vm.workspace = true +serde_json.workspace = true +starknet_api.workspace = true diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index a4a39ac56e..d2a3e0e415 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -8,6 +8,7 @@ use libmdbx::WriteMap; pub mod codecs; pub mod error; pub mod mdbx; +pub mod serde; pub mod utils; use mdbx::{Env, EnvKind}; diff --git a/crates/katana/storage/db/src/mdbx/models.rs b/crates/katana/storage/db/src/mdbx/models/block.rs similarity index 100% rename from crates/katana/storage/db/src/mdbx/models.rs rename to crates/katana/storage/db/src/mdbx/models/block.rs diff --git a/crates/katana/storage/db/src/mdbx/models/contract.rs b/crates/katana/storage/db/src/mdbx/models/contract.rs new file mode 100644 index 0000000000..12bfb0963f --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/models/contract.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use starknet_api::deprecated_contract_class::EntryPointType; + +use crate::serde::blockifier::{ + SerializableEntryPoint, SerializableEntryPointV1, SerializableProgram, +}; + +/// Storeable version of the [`ContractClass`](blockifier::execution::contract_class::ContractClass) +/// type from `blockifier`. +#[derive(Debug, Serialize, Deserialize)] +pub enum StoredCompiledContractClass { + V0(StoredCompiledContractClassV0), + V1(StoredCompiledContractClassV1), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StoredCompiledContractClassV0 { + pub program: SerializableProgram, + pub entry_points_by_type: HashMap>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StoredCompiledContractClassV1 { + pub program: SerializableProgram, + pub hints: HashMap>, + pub entry_points_by_type: HashMap>, +} + +impl TryFrom for blockifier::execution::contract_class::ContractClass { + type Error = anyhow::Error; + + fn try_from(value: StoredCompiledContractClass) -> Result { + Ok(match value { + StoredCompiledContractClass::V0(v0) => { + blockifier::execution::contract_class::ContractClass::V0( + blockifier::execution::contract_class::ContractClassV0(Arc::new( + blockifier::execution::contract_class::ContractClassV0Inner { + program: v0.program.into(), + entry_points_by_type: v0 + .entry_points_by_type + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + }, + )), + ) + } + StoredCompiledContractClass::V1(v1) => { + blockifier::execution::contract_class::ContractClass::V1( + blockifier::execution::contract_class::ContractClassV1(Arc::new( + blockifier::execution::contract_class::ContractClassV1Inner { + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::from_slice(&v).unwrap())) + .collect(), + program: v1.program.into(), + entry_points_by_type: v1 + .entry_points_by_type + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + }, + )), + ) + } + }) + } +} + +impl From for StoredCompiledContractClass { + fn from(value: blockifier::execution::contract_class::ContractClass) -> Self { + match value { + blockifier::execution::contract_class::ContractClass::V0(v0) => { + StoredCompiledContractClass::V0(StoredCompiledContractClassV0 { + program: v0.program.clone().into(), + entry_points_by_type: v0 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + }) + } + blockifier::execution::contract_class::ContractClass::V1(v1) => { + StoredCompiledContractClass::V1(StoredCompiledContractClassV1 { + program: v1.program.clone().into(), + entry_points_by_type: v1 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::to_vec(&v).unwrap())) + .collect(), + }) + } + } + } +} diff --git a/crates/katana/storage/db/src/mdbx/models/mod.rs b/crates/katana/storage/db/src/mdbx/models/mod.rs new file mode 100644 index 0000000000..e239af3beb --- /dev/null +++ b/crates/katana/storage/db/src/mdbx/models/mod.rs @@ -0,0 +1,2 @@ +pub mod block; +pub mod contract; diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/mdbx/tables.rs index e17ef1eb59..2020a2627b 100644 --- a/crates/katana/storage/db/src/mdbx/tables.rs +++ b/crates/katana/storage/db/src/mdbx/tables.rs @@ -6,7 +6,8 @@ use katana_primitives::contract::{ use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; -use super::models::StoredBlockBodyIndices; +use super::models::block::StoredBlockBodyIndices; +use super::models::contract::StoredCompiledContractClass; use crate::codecs::{Compress, Decode, Decompress, Encode}; pub trait Key: Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone {} @@ -182,7 +183,7 @@ tables! { /// Store compiled classes CompiledClassHashes: (ClassHash) => CompiledClassHash, /// Store compiled contract classes according to its compiled class hash - CompiledContractClasses: (CompiledClassHash) => u64, + CompiledContractClasses: (CompiledClassHash) => StoredCompiledContractClass, /// Store Sierra classes according to its class hash SierraClasses: (ClassHash) => SierraClass, /// Store contract information according to its contract address diff --git a/crates/katana/storage/db/src/serde/blockifier.rs b/crates/katana/storage/db/src/serde/blockifier.rs new file mode 100644 index 0000000000..21cf344d6e --- /dev/null +++ b/crates/katana/storage/db/src/serde/blockifier.rs @@ -0,0 +1,310 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use cairo_vm::felt::Felt252; +use cairo_vm::hint_processor::hint_processor_definition::HintReference; +use cairo_vm::serde::deserialize_program::{ + ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, + InstructionLocation, Member, OffsetValue, +}; +use cairo_vm::types::program::{Program, SharedProgramData}; +use cairo_vm::types::relocatable::MaybeRelocatable; +use serde::{Deserialize, Serialize}; +use starknet_api::core::EntryPointSelector; +use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPoint { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, +} + +impl From for SerializableEntryPoint { + fn from(value: EntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +impl From for EntryPoint { + fn from(value: SerializableEntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPointOffset(pub usize); + +impl From for SerializableEntryPointOffset { + fn from(value: EntryPointOffset) -> Self { + Self(value.0) + } +} + +impl From for EntryPointOffset { + fn from(value: SerializableEntryPointOffset) -> Self { + Self(value.0) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPointV1 { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, + pub builtins: Vec, +} + +impl From for blockifier::execution::contract_class::EntryPointV1 { + fn from(value: SerializableEntryPointV1) -> Self { + blockifier::execution::contract_class::EntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +impl From for SerializableEntryPointV1 { + fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { + SerializableEntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableProgram { + pub shared_program_data: SerializableSharedProgramData, + pub constants: HashMap, + pub builtins: Vec, +} + +impl From for SerializableProgram { + fn from(value: Program) -> Self { + Self { + shared_program_data: value.shared_program_data.as_ref().clone().into(), + constants: value.constants, + builtins: value.builtins, + } + } +} + +impl From for Program { + fn from(value: SerializableProgram) -> Self { + Self { + shared_program_data: Arc::new(value.shared_program_data.into()), + constants: value.constants, + builtins: value.builtins, + } + } +} + +// Fields of `SerializableProgramData` must not rely on `deserialize_any` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableSharedProgramData { + pub data: Vec, + pub hints: HashMap>, + pub main: Option, + pub start: Option, + pub end: Option, + pub error_message_attributes: Vec, + pub instruction_locations: Option>, + pub identifiers: HashMap, + pub reference_manager: Vec, +} + +impl From for SerializableSharedProgramData { + fn from(value: SharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +impl From for SharedProgramData { + fn from(value: SerializableSharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintParams { + pub code: String, + pub accessible_scopes: Vec, + pub flow_tracking_data: SerializableFlowTrackingData, +} + +impl From for SerializableHintParams { + fn from(value: HintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +impl From for HintParams { + fn from(value: SerializableHintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableIdentifier { + pub pc: Option, + pub type_: Option, + pub value: Option, + pub full_name: Option, + pub members: Option>, + pub cairo_type: Option, +} + +impl From for SerializableIdentifier { + fn from(value: Identifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +impl From for Identifier { + fn from(value: SerializableIdentifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintReference { + pub offset1: OffsetValue, + pub offset2: OffsetValue, + pub dereference: bool, + pub ap_tracking_data: Option, + pub cairo_type: Option, +} + +impl From for SerializableHintReference { + fn from(value: HintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +impl From for HintReference { + fn from(value: SerializableHintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableAttribute { + pub name: String, + pub start_pc: usize, + pub end_pc: usize, + pub value: String, + pub flow_tracking_data: Option, +} + +impl From for SerializableAttribute { + fn from(value: Attribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +impl From for Attribute { + fn from(value: SerializableAttribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableFlowTrackingData { + pub ap_tracking: ApTracking, + pub reference_ids: HashMap, +} + +impl From for SerializableFlowTrackingData { + fn from(value: FlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} + +impl From for FlowTrackingData { + fn from(value: SerializableFlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} diff --git a/crates/katana/storage/db/src/serde/mod.rs b/crates/katana/storage/db/src/serde/mod.rs new file mode 100644 index 0000000000..39f0a68488 --- /dev/null +++ b/crates/katana/storage/db/src/serde/mod.rs @@ -0,0 +1,3 @@ +//! Serde support for types that are not de/serializable out of the box. + +pub mod blockifier; From c7bcd4872d224ad9f9223a1e087b5756601804d2 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 09:40:17 +0800 Subject: [PATCH 071/192] Restructure module & remove serde types from db crate --- Cargo.lock | 4 - crates/katana/storage/db/Cargo.toml | 6 - crates/katana/storage/db/src/lib.rs | 3 +- crates/katana/storage/db/src/mdbx/cursor.rs | 2 +- crates/katana/storage/db/src/mdbx/mod.rs | 4 +- .../storage/db/src/mdbx/models/contract.rs | 124 ------- .../katana/storage/db/src/mdbx/models/mod.rs | 2 - crates/katana/storage/db/src/mdbx/tx.rs | 2 +- .../storage/db/src/{mdbx => }/models/block.rs | 0 crates/katana/storage/db/src/models/mod.rs | 1 + .../katana/storage/db/src/serde/blockifier.rs | 310 ------------------ crates/katana/storage/db/src/serde/mod.rs | 3 - .../storage/db/src/{mdbx => }/tables.rs | 6 +- crates/katana/storage/db/src/utils.rs | 2 +- 14 files changed, 10 insertions(+), 459 deletions(-) delete mode 100644 crates/katana/storage/db/src/mdbx/models/contract.rs delete mode 100644 crates/katana/storage/db/src/mdbx/models/mod.rs rename crates/katana/storage/db/src/{mdbx => }/models/block.rs (100%) create mode 100644 crates/katana/storage/db/src/models/mod.rs delete mode 100644 crates/katana/storage/db/src/serde/blockifier.rs delete mode 100644 crates/katana/storage/db/src/serde/mod.rs rename crates/katana/storage/db/src/{mdbx => }/tables.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index fbdaaa038b..5c13cf6a06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5119,16 +5119,12 @@ version = "0.3.14" dependencies = [ "anyhow", "bincode 1.3.3", - "blockifier", - "cairo-vm", "flate2", "katana-primitives", "page_size", "parking_lot 0.12.1", "reth-libmdbx", "serde", - "serde_json", - "starknet_api", "thiserror", ] diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 02729302ac..1e9f84be70 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -16,9 +16,3 @@ page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true thiserror.workspace = true - -# blockifier types serde compatiblity -blockifier.workspace = true -cairo-vm.workspace = true -serde_json.workspace = true -starknet_api.workspace = true diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index d2a3e0e415..8cc73f83d1 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -8,7 +8,8 @@ use libmdbx::WriteMap; pub mod codecs; pub mod error; pub mod mdbx; -pub mod serde; +pub mod models; +pub mod tables; pub mod utils; use mdbx::{Env, EnvKind}; diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index a534dcade1..fce48985fd 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -4,9 +4,9 @@ use std::marker::PhantomData; use libmdbx::{self, TransactionKind, WriteFlags, RW}; -use super::tables::{DupSort, Table}; use crate::codecs::{Compress, Encode}; use crate::error::DatabaseError; +use crate::tables::{DupSort, Table}; use crate::utils::{decode_one, decode_value, KeyValue}; /// Cursor wrapper to access KV items. diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 504af5d92a..7703528e0d 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -1,8 +1,6 @@ //! MDBX backend for the database. pub mod cursor; -pub mod models; -pub mod tables; pub mod tx; use std::ops::Deref; @@ -13,9 +11,9 @@ use libmdbx::{ SyncMode, RO, RW, }; -use self::tables::{TableType, Tables}; use self::tx::Tx; use crate::error::DatabaseError; +use crate::tables::{TableType, Tables}; use crate::utils; const GIGABYTE: usize = 1024 * 1024 * 1024; diff --git a/crates/katana/storage/db/src/mdbx/models/contract.rs b/crates/katana/storage/db/src/mdbx/models/contract.rs deleted file mode 100644 index 12bfb0963f..0000000000 --- a/crates/katana/storage/db/src/mdbx/models/contract.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use serde::{Deserialize, Serialize}; -use starknet_api::deprecated_contract_class::EntryPointType; - -use crate::serde::blockifier::{ - SerializableEntryPoint, SerializableEntryPointV1, SerializableProgram, -}; - -/// Storeable version of the [`ContractClass`](blockifier::execution::contract_class::ContractClass) -/// type from `blockifier`. -#[derive(Debug, Serialize, Deserialize)] -pub enum StoredCompiledContractClass { - V0(StoredCompiledContractClassV0), - V1(StoredCompiledContractClassV1), -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct StoredCompiledContractClassV0 { - pub program: SerializableProgram, - pub entry_points_by_type: HashMap>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct StoredCompiledContractClassV1 { - pub program: SerializableProgram, - pub hints: HashMap>, - pub entry_points_by_type: HashMap>, -} - -impl TryFrom for blockifier::execution::contract_class::ContractClass { - type Error = anyhow::Error; - - fn try_from(value: StoredCompiledContractClass) -> Result { - Ok(match value { - StoredCompiledContractClass::V0(v0) => { - blockifier::execution::contract_class::ContractClass::V0( - blockifier::execution::contract_class::ContractClassV0(Arc::new( - blockifier::execution::contract_class::ContractClassV0Inner { - program: v0.program.into(), - entry_points_by_type: v0 - .entry_points_by_type - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }, - )), - ) - } - StoredCompiledContractClass::V1(v1) => { - blockifier::execution::contract_class::ContractClass::V1( - blockifier::execution::contract_class::ContractClassV1(Arc::new( - blockifier::execution::contract_class::ContractClassV1Inner { - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::from_slice(&v).unwrap())) - .collect(), - program: v1.program.into(), - entry_points_by_type: v1 - .entry_points_by_type - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - }, - )), - ) - } - }) - } -} - -impl From for StoredCompiledContractClass { - fn from(value: blockifier::execution::contract_class::ContractClass) -> Self { - match value { - blockifier::execution::contract_class::ContractClass::V0(v0) => { - StoredCompiledContractClass::V0(StoredCompiledContractClassV0 { - program: v0.program.clone().into(), - entry_points_by_type: v0 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }) - } - blockifier::execution::contract_class::ContractClass::V1(v1) => { - StoredCompiledContractClass::V1(StoredCompiledContractClassV1 { - program: v1.program.clone().into(), - entry_points_by_type: v1 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::to_vec(&v).unwrap())) - .collect(), - }) - } - } - } -} diff --git a/crates/katana/storage/db/src/mdbx/models/mod.rs b/crates/katana/storage/db/src/mdbx/models/mod.rs deleted file mode 100644 index e239af3beb..0000000000 --- a/crates/katana/storage/db/src/mdbx/models/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod block; -pub mod contract; diff --git a/crates/katana/storage/db/src/mdbx/tx.rs b/crates/katana/storage/db/src/mdbx/tx.rs index 11bf343209..133f7bcfdb 100644 --- a/crates/katana/storage/db/src/mdbx/tx.rs +++ b/crates/katana/storage/db/src/mdbx/tx.rs @@ -7,9 +7,9 @@ use libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, RW}; use parking_lot::RwLock; use super::cursor::Cursor; -use super::tables::{DupSort, Table, Tables, NUM_TABLES}; use crate::codecs::{Compress, Encode}; use crate::error::DatabaseError; +use crate::tables::{DupSort, Table, Tables, NUM_TABLES}; use crate::utils::decode_one; /// Wrapper for a `libmdbx` transaction. diff --git a/crates/katana/storage/db/src/mdbx/models/block.rs b/crates/katana/storage/db/src/models/block.rs similarity index 100% rename from crates/katana/storage/db/src/mdbx/models/block.rs rename to crates/katana/storage/db/src/models/block.rs diff --git a/crates/katana/storage/db/src/models/mod.rs b/crates/katana/storage/db/src/models/mod.rs new file mode 100644 index 0000000000..a863eaad24 --- /dev/null +++ b/crates/katana/storage/db/src/models/mod.rs @@ -0,0 +1 @@ +pub mod block; diff --git a/crates/katana/storage/db/src/serde/blockifier.rs b/crates/katana/storage/db/src/serde/blockifier.rs deleted file mode 100644 index 21cf344d6e..0000000000 --- a/crates/katana/storage/db/src/serde/blockifier.rs +++ /dev/null @@ -1,310 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use cairo_vm::felt::Felt252; -use cairo_vm::hint_processor::hint_processor_definition::HintReference; -use cairo_vm::serde::deserialize_program::{ - ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, - InstructionLocation, Member, OffsetValue, -}; -use cairo_vm::types::program::{Program, SharedProgramData}; -use cairo_vm::types::relocatable::MaybeRelocatable; -use serde::{Deserialize, Serialize}; -use starknet_api::core::EntryPointSelector; -use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPoint { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, -} - -impl From for SerializableEntryPoint { - fn from(value: EntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -impl From for EntryPoint { - fn from(value: SerializableEntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPointOffset(pub usize); - -impl From for SerializableEntryPointOffset { - fn from(value: EntryPointOffset) -> Self { - Self(value.0) - } -} - -impl From for EntryPointOffset { - fn from(value: SerializableEntryPointOffset) -> Self { - Self(value.0) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPointV1 { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, - pub builtins: Vec, -} - -impl From for blockifier::execution::contract_class::EntryPointV1 { - fn from(value: SerializableEntryPointV1) -> Self { - blockifier::execution::contract_class::EntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -impl From for SerializableEntryPointV1 { - fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { - SerializableEntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableProgram { - pub shared_program_data: SerializableSharedProgramData, - pub constants: HashMap, - pub builtins: Vec, -} - -impl From for SerializableProgram { - fn from(value: Program) -> Self { - Self { - shared_program_data: value.shared_program_data.as_ref().clone().into(), - constants: value.constants, - builtins: value.builtins, - } - } -} - -impl From for Program { - fn from(value: SerializableProgram) -> Self { - Self { - shared_program_data: Arc::new(value.shared_program_data.into()), - constants: value.constants, - builtins: value.builtins, - } - } -} - -// Fields of `SerializableProgramData` must not rely on `deserialize_any` -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableSharedProgramData { - pub data: Vec, - pub hints: HashMap>, - pub main: Option, - pub start: Option, - pub end: Option, - pub error_message_attributes: Vec, - pub instruction_locations: Option>, - pub identifiers: HashMap, - pub reference_manager: Vec, -} - -impl From for SerializableSharedProgramData { - fn from(value: SharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -impl From for SharedProgramData { - fn from(value: SerializableSharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintParams { - pub code: String, - pub accessible_scopes: Vec, - pub flow_tracking_data: SerializableFlowTrackingData, -} - -impl From for SerializableHintParams { - fn from(value: HintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -impl From for HintParams { - fn from(value: SerializableHintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableIdentifier { - pub pc: Option, - pub type_: Option, - pub value: Option, - pub full_name: Option, - pub members: Option>, - pub cairo_type: Option, -} - -impl From for SerializableIdentifier { - fn from(value: Identifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} - -impl From for Identifier { - fn from(value: SerializableIdentifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintReference { - pub offset1: OffsetValue, - pub offset2: OffsetValue, - pub dereference: bool, - pub ap_tracking_data: Option, - pub cairo_type: Option, -} - -impl From for SerializableHintReference { - fn from(value: HintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } -} - -impl From for HintReference { - fn from(value: SerializableHintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableAttribute { - pub name: String, - pub start_pc: usize, - pub end_pc: usize, - pub value: String, - pub flow_tracking_data: Option, -} - -impl From for SerializableAttribute { - fn from(value: Attribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } - } -} - -impl From for Attribute { - fn from(value: SerializableAttribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableFlowTrackingData { - pub ap_tracking: ApTracking, - pub reference_ids: HashMap, -} - -impl From for SerializableFlowTrackingData { - fn from(value: FlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } - } -} - -impl From for FlowTrackingData { - fn from(value: SerializableFlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } - } -} diff --git a/crates/katana/storage/db/src/serde/mod.rs b/crates/katana/storage/db/src/serde/mod.rs deleted file mode 100644 index 39f0a68488..0000000000 --- a/crates/katana/storage/db/src/serde/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Serde support for types that are not de/serializable out of the box. - -pub mod blockifier; diff --git a/crates/katana/storage/db/src/mdbx/tables.rs b/crates/katana/storage/db/src/tables.rs similarity index 97% rename from crates/katana/storage/db/src/mdbx/tables.rs rename to crates/katana/storage/db/src/tables.rs index 2020a2627b..da15a4db4f 100644 --- a/crates/katana/storage/db/src/mdbx/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -3,12 +3,12 @@ use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; +use katana_primitives::serde::contract::SerializableContractClass; use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; -use super::models::block::StoredBlockBodyIndices; -use super::models::contract::StoredCompiledContractClass; use crate::codecs::{Compress, Decode, Decompress, Encode}; +use crate::models::block::StoredBlockBodyIndices; pub trait Key: Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone {} pub trait Value: Compress + Decompress {} @@ -183,7 +183,7 @@ tables! { /// Store compiled classes CompiledClassHashes: (ClassHash) => CompiledClassHash, /// Store compiled contract classes according to its compiled class hash - CompiledContractClasses: (CompiledClassHash) => StoredCompiledContractClass, + CompiledContractClasses: (CompiledClassHash) => SerializableContractClass, /// Store Sierra classes according to its class hash SierraClasses: (ClassHash) => SierraClass, /// Store contract information according to its contract address diff --git a/crates/katana/storage/db/src/utils.rs b/crates/katana/storage/db/src/utils.rs index 67d25cfb70..c00b10816f 100644 --- a/crates/katana/storage/db/src/utils.rs +++ b/crates/katana/storage/db/src/utils.rs @@ -3,7 +3,7 @@ use std::path::Path; use crate::codecs::{Decode, Decompress}; use crate::error::DatabaseError; -use crate::mdbx::tables::Table; +use crate::tables::Table; // May lead to errors if it's reduced further because of the potential size of the data. const MIN_PAGE_SIZE: usize = 4096; From aa1f2b78693fa8e1e5120f878656b786e30a1aac Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 09:42:44 +0800 Subject: [PATCH 072/192] Add serde counterparts --- Cargo.lock | 4 + crates/katana/primitives/Cargo.toml | 9 + crates/katana/primitives/src/block.rs | 8 +- crates/katana/primitives/src/contract.rs | 11 +- crates/katana/primitives/src/lib.rs | 3 + .../katana/primitives/src/serde/contract.rs | 413 ++++++++++++++++++ crates/katana/primitives/src/serde/mod.rs | 4 + crates/katana/primitives/src/transaction.rs | 31 +- 8 files changed, 465 insertions(+), 18 deletions(-) create mode 100644 crates/katana/primitives/src/serde/contract.rs create mode 100644 crates/katana/primitives/src/serde/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5c13cf6a06..0176018a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5132,8 +5132,12 @@ dependencies = [ name = "katana-primitives" version = "0.3.14" dependencies = [ + "blockifier", + "cairo-vm", "serde", + "serde_json", "starknet", + "starknet_api", ] [[package]] diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index d8029e7882..bbb5db9f7f 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -7,5 +7,14 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +blockifier.workspace = true +cairo-vm.workspace = true serde.workspace = true +serde_json.workspace = true starknet.workspace = true +starknet_api.workspace = true + +[features] +blockifier = [ ] +default = [ "blockifier", "serde" ] +serde = [ ] diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index e3d8c67b79..501bbce5d4 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,12 +1,11 @@ -use serde::{Deserialize, Serialize}; - use crate::contract::ContractAddress; use crate::FieldElement; /// Block state update type. pub type StateUpdate = starknet::core::types::StateUpdate; -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BlockHashOrNumber { Hash(BlockHash), Num(BlockNumber), @@ -18,7 +17,8 @@ pub type BlockNumber = u64; pub type BlockHash = FieldElement; /// Represents a block header. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Header { pub parent_hash: BlockHash, pub number: BlockNumber, diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 61289e2882..1626f8a68f 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -1,6 +1,5 @@ use std::fmt; -use serde::{Deserialize, Serialize}; use starknet::core::utils::normalize_address; use crate::FieldElement; @@ -21,7 +20,8 @@ pub type Nonce = FieldElement; pub type SierraClass = starknet::core::types::FlattenedSierraClass; /// Represents a contract address. -#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ContractAddress(FieldElement); impl ContractAddress { @@ -49,10 +49,15 @@ impl From for FieldElement { } /// Represents a generic contract instance information. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GenericContractInfo { /// The nonce of the contract. pub nonce: Nonce, /// The hash of the contract class. pub class_hash: ClassHash, } + +/// Represents a runnable Starknet contract class (meaning, the program is runnable by the VM). +#[cfg(feature = "blockifier")] +pub type CompiledContractClass = ::blockifier::execution::contract_class::ContractClass; diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index d96f0bab27..36a39b0272 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -2,4 +2,7 @@ pub mod block; pub mod contract; pub mod transaction; +#[cfg(feature = "serde")] +pub mod serde; + pub type FieldElement = starknet::core::types::FieldElement; diff --git a/crates/katana/primitives/src/serde/contract.rs b/crates/katana/primitives/src/serde/contract.rs new file mode 100644 index 0000000000..36e436d2bd --- /dev/null +++ b/crates/katana/primitives/src/serde/contract.rs @@ -0,0 +1,413 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use blockifier::execution::contract_class::{ + ContractClass, ContractClassV0, ContractClassV0Inner, ContractClassV1, ContractClassV1Inner, +}; +use cairo_vm::felt::Felt252; +use cairo_vm::hint_processor::hint_processor_definition::HintReference; +use cairo_vm::serde::deserialize_program::{ + ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, + InstructionLocation, Member, OffsetValue, +}; +use cairo_vm::types::program::{Program, SharedProgramData}; +use cairo_vm::types::relocatable::MaybeRelocatable; +use serde::{Deserialize, Serialize}; +use starknet_api::core::EntryPointSelector; +use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset, EntryPointType}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum SerializableContractClass { + V0(SerializableContractClassV0), + V1(SerializableContractClassV1), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SerializableContractClassV0 { + pub program: SerializableProgram, + pub entry_points_by_type: HashMap>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SerializableContractClassV1 { + pub program: SerializableProgram, + pub hints: HashMap>, + pub entry_points_by_type: HashMap>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPoint { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, +} + +impl From for SerializableEntryPoint { + fn from(value: EntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +impl From for EntryPoint { + fn from(value: SerializableEntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPointOffset(pub usize); + +impl From for SerializableEntryPointOffset { + fn from(value: EntryPointOffset) -> Self { + Self(value.0) + } +} + +impl From for EntryPointOffset { + fn from(value: SerializableEntryPointOffset) -> Self { + Self(value.0) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializableEntryPointV1 { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, + pub builtins: Vec, +} + +impl From for blockifier::execution::contract_class::EntryPointV1 { + fn from(value: SerializableEntryPointV1) -> Self { + blockifier::execution::contract_class::EntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +impl From for SerializableEntryPointV1 { + fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { + SerializableEntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableProgram { + pub shared_program_data: SerializableSharedProgramData, + pub constants: HashMap, + pub builtins: Vec, +} + +impl From for SerializableProgram { + fn from(value: Program) -> Self { + Self { + shared_program_data: value.shared_program_data.as_ref().clone().into(), + constants: value.constants, + builtins: value.builtins, + } + } +} + +impl From for Program { + fn from(value: SerializableProgram) -> Self { + Self { + shared_program_data: Arc::new(value.shared_program_data.into()), + constants: value.constants, + builtins: value.builtins, + } + } +} + +// Fields of `SerializableProgramData` must not rely on `deserialize_any` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableSharedProgramData { + pub data: Vec, + pub hints: HashMap>, + pub main: Option, + pub start: Option, + pub end: Option, + pub error_message_attributes: Vec, + pub instruction_locations: Option>, + pub identifiers: HashMap, + pub reference_manager: Vec, +} + +impl From for SerializableSharedProgramData { + fn from(value: SharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +impl From for SharedProgramData { + fn from(value: SerializableSharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintParams { + pub code: String, + pub accessible_scopes: Vec, + pub flow_tracking_data: SerializableFlowTrackingData, +} + +impl From for SerializableHintParams { + fn from(value: HintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +impl From for HintParams { + fn from(value: SerializableHintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableIdentifier { + pub pc: Option, + pub type_: Option, + pub value: Option, + pub full_name: Option, + pub members: Option>, + pub cairo_type: Option, +} + +impl From for SerializableIdentifier { + fn from(value: Identifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +impl From for Identifier { + fn from(value: SerializableIdentifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintReference { + pub offset1: OffsetValue, + pub offset2: OffsetValue, + pub dereference: bool, + pub ap_tracking_data: Option, + pub cairo_type: Option, +} + +impl From for SerializableHintReference { + fn from(value: HintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +impl From for HintReference { + fn from(value: SerializableHintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableAttribute { + pub name: String, + pub start_pc: usize, + pub end_pc: usize, + pub value: String, + pub flow_tracking_data: Option, +} + +impl From for SerializableAttribute { + fn from(value: Attribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +impl From for Attribute { + fn from(value: SerializableAttribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableFlowTrackingData { + pub ap_tracking: ApTracking, + pub reference_ids: HashMap, +} + +impl From for SerializableFlowTrackingData { + fn from(value: FlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} + +impl From for FlowTrackingData { + fn from(value: SerializableFlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} + +impl From for blockifier::execution::contract_class::ContractClass { + fn from(value: SerializableContractClass) -> Self { + match value { + SerializableContractClass::V0(v0) => { + ContractClass::V0(ContractClassV0(Arc::new(ContractClassV0Inner { + program: v0.program.into(), + entry_points_by_type: v0 + .entry_points_by_type + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + }))) + } + SerializableContractClass::V1(v1) => { + ContractClass::V1(ContractClassV1(Arc::new(ContractClassV1Inner { + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::from_slice(&v).expect("valid hint"))) + .collect(), + program: v1.program.into(), + entry_points_by_type: v1 + .entry_points_by_type + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + }))) + } + } + } +} + +impl From for SerializableContractClass { + fn from(value: ContractClass) -> Self { + match value { + ContractClass::V0(v0) => SerializableContractClass::V0(SerializableContractClassV0 { + program: v0.program.clone().into(), + entry_points_by_type: v0 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + }), + + ContractClass::V1(v1) => SerializableContractClass::V1(SerializableContractClassV1 { + program: v1.program.clone().into(), + entry_points_by_type: v1 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::to_vec(&v).expect("valid hint"))) + .collect(), + }), + } + } +} diff --git a/crates/katana/primitives/src/serde/mod.rs b/crates/katana/primitives/src/serde/mod.rs new file mode 100644 index 0000000000..ed14f5e51d --- /dev/null +++ b/crates/katana/primitives/src/serde/mod.rs @@ -0,0 +1,4 @@ +//! De/serializable counterparts for types that are not de/serializable by default. + +#[cfg(feature = "blockifier")] +pub mod contract; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 374eded996..0368548737 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use serde::{Deserialize, Serialize}; use starknet::core::types::{Event, MsgToL1}; use crate::contract::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; @@ -11,7 +10,8 @@ pub type TxHash = FieldElement; /// The sequential number for all the transactions.. pub type TxNumber = u64; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Transaction { Invoke(InvokeTx), Declare(DeclareTx), @@ -19,12 +19,14 @@ pub enum Transaction { DeployAccount(DeployAccountTx), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum InvokeTx { V1(InvokeTxV1), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvokeTxV1 { pub transaction_hash: TxHash, pub nonce: Nonce, @@ -34,13 +36,15 @@ pub struct InvokeTxV1 { pub sender_address: ContractAddress, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DeclareTx { V1(DeclareTxV1), V2(DeclareTxV2), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeclareTxV1 { pub transaction_hash: TxHash, pub max_fee: u128, @@ -50,7 +54,8 @@ pub struct DeclareTxV1 { pub sender_address: ContractAddress, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeclareTxV2 { pub transaction_hash: TxHash, pub max_fee: u128, @@ -61,7 +66,8 @@ pub struct DeclareTxV2 { pub compiled_class_hash: CompiledClassHash, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1HandlerTx { pub transaction_hash: TxHash, pub version: FieldElement, @@ -72,7 +78,8 @@ pub struct L1HandlerTx { pub entry_point_selector: FieldElement, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeployAccountTx { pub transaction_hash: TxHash, pub max_fee: u128, @@ -85,7 +92,8 @@ pub struct DeployAccountTx { } /// A transaction finality status. -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum FinalityStatus { AcceptedOnL2, AcceptedOnL1, @@ -94,7 +102,8 @@ pub enum FinalityStatus { pub type ExecutionResources = HashMap; /// The receipt of a transaction containing the outputs of its execution. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Receipt { pub actual_fee: u128, pub events: Vec, From b3d47755677245bf8490745a70f02fa9430c0f91 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 16:10:49 +0800 Subject: [PATCH 073/192] Add full block type --- crates/katana/primitives/src/block.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 501bbce5d4..a2e33500d9 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,4 +1,5 @@ use crate::contract::ContractAddress; +use crate::transaction::Transaction; use crate::FieldElement; /// Block state update type. @@ -28,6 +29,14 @@ pub struct Header { pub sequencer_address: ContractAddress, } +/// Represents a Starknet full block. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Block { + header: Header, + body: Vec, +} + impl From for BlockHashOrNumber { fn from(number: BlockNumber) -> Self { Self::Num(number) From 4f008712137329bc5aaf31210f57c8f7db51c7e9 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 16:11:15 +0800 Subject: [PATCH 074/192] Add table for storing tx hash based on the number --- crates/katana/storage/db/src/tables.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index da15a4db4f..aabb2e2094 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -43,7 +43,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 14; +pub const NUM_TABLES: usize = 15; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -148,7 +148,8 @@ define_tables_enum! {[ (BlockHashes, TableType::Table), (BlockNumbers, TableType::Table), (BlockBodyIndices, TableType::Table), - (TxHashNumber, TableType::Table), + (TxNumbers, TableType::Table), + (TxHashes, TableType::Table), (Transactions, TableType::Table), (Receipts, TableType::Table), (ClassDeclarations, TableType::Table), @@ -170,8 +171,10 @@ tables! { /// Block number to its body indices which stores the tx number of /// the first tx in the block and the number of txs in the block. BlockBodyIndices: (BlockNumber) => StoredBlockBodyIndices, - /// Store canonical transactions hashes according to its tx number - TxHashNumber: (TxHash) => TxNumber, + /// Transaction number based on its hash + TxNumbers: (TxHash) => TxNumber, + /// Transaction hash based on its number + TxHashes: (TxNumber) => TxHash, /// Store canonical transactions Transactions: (TxNumber) => Transaction, /// Store transaction receipts From be66b7eab77b7ad50a5e4c70c5441f4ff4682974 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 21 Nov 2023 20:35:34 +0800 Subject: [PATCH 075/192] Initial storage provider design --- Cargo.lock | 9 + Cargo.toml | 1 + crates/katana/storage/provider/Cargo.toml | 16 ++ crates/katana/storage/provider/src/lib.rs | 207 ++++++++++++++++++ .../provider/src/providers/in_memory/mod.rs | 27 +++ .../storage/provider/src/providers/mod.rs | 2 + .../storage/provider/src/traits/block.rs | 64 ++++++ .../storage/provider/src/traits/contract.rs | 7 + .../katana/storage/provider/src/traits/mod.rs | 5 + .../storage/provider/src/traits/state.rs | 49 +++++ .../provider/src/traits/state_update.rs | 7 + .../provider/src/traits/transaction.rs | 36 +++ 12 files changed, 430 insertions(+) create mode 100644 crates/katana/storage/provider/Cargo.toml create mode 100644 crates/katana/storage/provider/src/lib.rs create mode 100644 crates/katana/storage/provider/src/providers/in_memory/mod.rs create mode 100644 crates/katana/storage/provider/src/providers/mod.rs create mode 100644 crates/katana/storage/provider/src/traits/block.rs create mode 100644 crates/katana/storage/provider/src/traits/contract.rs create mode 100644 crates/katana/storage/provider/src/traits/mod.rs create mode 100644 crates/katana/storage/provider/src/traits/state.rs create mode 100644 crates/katana/storage/provider/src/traits/state_update.rs create mode 100644 crates/katana/storage/provider/src/traits/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 0176018a60..af8516e21d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5140,6 +5140,15 @@ dependencies = [ "starknet_api", ] +[[package]] +name = "katana-provider" +version = "0.3.14" +dependencies = [ + "anyhow", + "katana-db", + "katana-primitives", +] + [[package]] name = "katana-rpc" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 9d3ad8c520..de606502c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/katana/primitives", "crates/katana/rpc", "crates/katana/storage/db", + "crates/katana/storage/provider", "crates/sozo", "crates/torii/client", "crates/torii/server", diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml new file mode 100644 index 0000000000..b75a455bcb --- /dev/null +++ b/crates/katana/storage/provider/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "Katana storage provider" +edition.workspace = true +name = "katana-provider" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +katana-db = { path = "../db" } +katana-primitives = { path = "../../primitives" } + +[features] +default = [ "in_memory" ] +in_memory = [ ] diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs new file mode 100644 index 0000000000..04b16f2e80 --- /dev/null +++ b/crates/katana/storage/provider/src/lib.rs @@ -0,0 +1,207 @@ +use std::ops::{Range, RangeInclusive}; + +use anyhow::Result; +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, +}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + SierraClass, StorageKey, StorageValue, +}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; + +pub mod providers; +pub mod traits; + +use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; +use crate::traits::contract::ContractProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider, StateProviderExt}; +use crate::traits::state_update::StateUpdateProvider; +use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; + +/// A blockchain provider that can be used to access the storage. +/// +/// Serves as the main entrypoint for interacting with the storage storage. Every read/write +/// operation is done through this provider. +pub struct BlockchainProvider { + provider: Db, +} + +impl BlockchainProvider { + pub fn new(provider: Db) -> Self { + Self { provider } + } +} + +impl BlockProvider for BlockchainProvider +where + Db: BlockProvider, +{ + fn block(&self, id: BlockHashOrNumber) -> Result> { + self.provider.block(id) + } + + fn blocks_in_range(&self, range: RangeInclusive) -> Result> { + self.provider.blocks_in_range(range) + } +} + +impl HeaderProvider for BlockchainProvider +where + Db: HeaderProvider, +{ + fn header(&self, id: BlockHashOrNumber) -> Result> { + self.provider.header(id) + } +} + +impl BlockNumberProvider for BlockchainProvider +where + Db: BlockNumberProvider, +{ + fn latest_number(&self) -> Result { + self.provider.latest_number() + } + + fn block_number_by_hash(&self, hash: BlockHash) -> Result> { + self.provider.block_number_by_hash(hash) + } +} + +impl BlockHashProvider for BlockchainProvider +where + Db: BlockHashProvider, +{ + fn latest_hash(&self) -> Result { + self.provider.latest_hash() + } + + fn block_hash_by_num(&self, num: BlockNumber) -> Result> { + self.provider.block_hash_by_num(num) + } +} + +impl TransactionProvider for BlockchainProvider +where + Db: TransactionProvider, +{ + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + self.provider.transaction_by_hash(hash) + } + + fn transaction_by_block_and_idx( + &self, + block_id: BlockHashOrNumber, + idx: u64, + ) -> Result> { + self.provider.transaction_by_block_and_idx(block_id, idx) + } + + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { + self.provider.transactions_by_block(block_id) + } +} + +impl TransactionsProviderExt for BlockchainProvider +where + Db: TransactionsProviderExt, +{ + fn transaction_hashes_by_range(&self, range: Range) -> Result> { + self.provider.transaction_hashes_by_range(range) + } +} + +impl ReceiptProvider for BlockchainProvider +where + Db: ReceiptProvider, +{ + fn receipt_by_hash(&self, hash: TxHash) -> Result> { + self.provider.receipt_by_hash(hash) + } + + fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + self.provider.receipts_by_block(block_id) + } +} + +impl StateProvider for BlockchainProvider +where + Db: StateProvider, +{ + fn class(&self, hash: ClassHash) -> Result> { + self.provider.class(hash) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + self.provider.class_hash_of_contract(address) + } + + fn nonce( + &self, + address: ContractAddress, + ) -> Result> { + self.provider.nonce(address) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + self.provider.storage(address, storage_key) + } + + fn compiled_contract_class(&self, hash: ClassHash) -> Result> { + self.provider.compiled_contract_class(hash) + } +} + +impl StateProviderExt for BlockchainProvider +where + Db: StateProviderExt, +{ + fn sierra_class(&self, hash: ClassHash) -> Result> { + self.provider.sierra_class(hash) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + self.provider.compiled_class_hash_of_class_hash(hash) + } +} + +impl StateFactoryProvider for BlockchainProvider +where + Db: StateFactoryProvider, +{ + fn latest(&self) -> Result> { + self.provider.latest() + } + + fn historical(&self, block_id: BlockHashOrNumber) -> Result>> { + self.provider.historical(block_id) + } +} + +impl StateUpdateProvider for BlockchainProvider +where + Db: StateUpdateProvider, +{ + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + self.provider.state_update(block_id) + } +} + +impl ContractProvider for BlockchainProvider +where + Db: ContractProvider, +{ + fn contract(&self, address: ContractAddress) -> Result> { + self.provider.contract(address) + } +} diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs new file mode 100644 index 0000000000..1ee294fdc1 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; + +use katana_primitives::block::{Block, StateUpdate}; +use katana_primitives::transaction::Transaction; +use katana_primitives::FieldElement; + +#[derive(Debug, Default)] +pub struct InMemoryProvider { + /// Mapping from block hash -> block + pub blocks: HashMap, + /// Mapping from block number -> block hash + pub hashes: HashMap, + /// Mapping from block number -> state update + pub state_update: HashMap, + /// The latest block hash + pub latest_hash: FieldElement, + /// The latest block number + pub latest_number: u64, + /// Mapping of all known transactions from its transaction hash + pub transactions: HashMap, +} + +impl InMemoryProvider { + pub fn new() -> Self { + Self::default() + } +} diff --git a/crates/katana/storage/provider/src/providers/mod.rs b/crates/katana/storage/provider/src/providers/mod.rs new file mode 100644 index 0000000000..028e4780b0 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "in_memory")] +pub mod in_memory; diff --git a/crates/katana/storage/provider/src/traits/block.rs b/crates/katana/storage/provider/src/traits/block.rs new file mode 100644 index 0000000000..fcfb1c6029 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/block.rs @@ -0,0 +1,64 @@ +use std::ops::RangeInclusive; + +use anyhow::Result; +use katana_primitives::block::{Block, BlockHash, BlockHashOrNumber, BlockNumber, Header}; + +use super::transaction::TransactionProvider; + +pub trait BlockHashProvider { + /// Retrieves the latest block hash. + /// + /// There should always be at least one block (genesis) in the chain. + fn latest_hash(&self) -> Result; + + /// Retrieves the block hash given its id. + fn block_hash_by_num(&self, num: BlockNumber) -> Result>; +} + +pub trait BlockNumberProvider { + /// Retrieves the latest block number. + /// + /// There should always be at least one block (genesis) in the chain. + fn latest_number(&self) -> Result; + + /// Retrieves the block number given its id. + fn block_number_by_hash(&self, hash: BlockHash) -> Result>; +} + +pub trait HeaderProvider { + /// Retrieves the latest header by its block id. + fn header(&self, id: BlockHashOrNumber) -> Result>; + + fn header_by_hash(&self, hash: BlockHash) -> Result> { + self.header(hash.into()) + } + + fn header_by_number(&self, number: BlockNumber) -> Result> { + self.header(number.into()) + } +} + +pub trait BlockProvider: + BlockHashProvider + BlockNumberProvider + HeaderProvider + TransactionProvider +{ + /// Returns a block by its id. + fn block(&self, id: BlockHashOrNumber) -> Result>; + + /// Returns all available blocks in the given range. + fn blocks_in_range(&self, range: RangeInclusive) -> Result>; + + /// Returns the block based on its hash. + fn block_by_hash(&self, hash: BlockHash) -> Result> { + self.block(hash.into()) + } + + /// Returns the block based on its number. + fn block_by_number(&self, number: BlockNumber) -> Result> { + self.block(number.into()) + } +} + +pub trait BlockExecutionWriter { + /// Store an executed block along with its output to the storage. + fn store_block(&mut self, block: Block) -> Result<()>; +} diff --git a/crates/katana/storage/provider/src/traits/contract.rs b/crates/katana/storage/provider/src/traits/contract.rs new file mode 100644 index 0000000000..8ea44326ea --- /dev/null +++ b/crates/katana/storage/provider/src/traits/contract.rs @@ -0,0 +1,7 @@ +use anyhow::Result; +use katana_primitives::contract::{ContractAddress, GenericContractInfo}; + +pub trait ContractProvider { + /// Returns the contract information given its address. + fn contract(&self, address: ContractAddress) -> Result>; +} diff --git a/crates/katana/storage/provider/src/traits/mod.rs b/crates/katana/storage/provider/src/traits/mod.rs new file mode 100644 index 0000000000..2598632fb1 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/mod.rs @@ -0,0 +1,5 @@ +pub mod block; +pub mod contract; +pub mod state; +pub mod state_update; +pub mod transaction; diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs new file mode 100644 index 0000000000..85f6fb9aaa --- /dev/null +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, + StorageKey, StorageValue, +}; + +pub trait StateProvider: StateProviderExt { + /// Returns the compiled class definition of a contract class given its class hash. + fn class(&self, hash: ClassHash) -> Result>; + + /// Returns the nonce of a contract. + fn nonce(&self, address: ContractAddress) -> Result>; + + /// Returns the value of a contract storage. + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result>; + + /// Returns the class hash of a contract. + fn class_hash_of_contract(&self, address: ContractAddress) -> Result>; + + /// Returns the compiled contract class for the given class hash. + fn compiled_contract_class(&self, hash: ClassHash) -> Result>; +} + +/// An extension of the `StateProvider` trait which provides additional methods. +pub trait StateProviderExt { + /// Retrieves the Sierra class definition of a contract class given its class hash. + fn sierra_class(&self, hash: ClassHash) -> Result>; + + /// Returns the compiled class hash for the given class hash. + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result>; +} + +/// A state factory provider is a provider which can create state providers for +/// states at a particular block. +pub trait StateFactoryProvider { + /// Returns a state provider for retrieving the latest state. + fn latest(&self) -> Result>; + + /// Returns a state provider for retrieving historical state at the given block. + fn historical(&self, block_id: BlockHashOrNumber) -> Result>>; +} diff --git a/crates/katana/storage/provider/src/traits/state_update.rs b/crates/katana/storage/provider/src/traits/state_update.rs new file mode 100644 index 0000000000..04f4eaf772 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/state_update.rs @@ -0,0 +1,7 @@ +use anyhow::Result; +use katana_primitives::block::{BlockHashOrNumber, StateUpdate}; + +pub trait StateUpdateProvider { + /// Returns the state update for the given block. + fn state_update(&self, block_id: BlockHashOrNumber) -> Result>; +} diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs new file mode 100644 index 0000000000..f24a021d75 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -0,0 +1,36 @@ +use std::ops::Range; + +use anyhow::Result; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; + +pub trait TransactionProvider { + /// Returns a transaction given its hash. + fn transaction_by_hash(&self, hash: TxHash) -> Result>; + + /// Returns all the transactions for a given block. + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>>; + + /// Returns the transaction at the given block and its exact index in the block. + fn transaction_by_block_and_idx( + &self, + block_id: BlockHashOrNumber, + idx: u64, + ) -> Result>; +} + +pub trait TransactionsProviderExt { + /// Retrieves the tx hashes for the given range of tx numbers. + fn transaction_hashes_by_range(&self, range: Range) -> Result>; +} + +pub trait ReceiptProvider { + /// Returns the transaction receipt given a transaction hash. + fn receipt_by_hash(&self, hash: TxHash) -> Result>; + + /// Returns all the receipts for a given block. + fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>>; +} From d957623457e7913b64bbad1ce579aafd9b38d1b3 Mon Sep 17 00:00:00 2001 From: Kariy Date: Wed, 22 Nov 2023 07:14:16 +0800 Subject: [PATCH 076/192] Add `blockifier` glue code for primitive types --- crates/katana/primitives/Cargo.toml | 4 +- crates/katana/primitives/src/contract.rs | 2 +- .../primitives/src/conversion/blockifier.rs | 145 ++++++++++++++++++ .../katana/primitives/src/conversion/mod.rs | 2 + crates/katana/primitives/src/lib.rs | 2 + .../src/serde/{contract.rs => blockifier.rs} | 0 crates/katana/primitives/src/serde/mod.rs | 4 +- crates/katana/primitives/src/transaction.rs | 40 ++++- crates/katana/storage/db/src/tables.rs | 2 +- 9 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 crates/katana/primitives/src/conversion/blockifier.rs create mode 100644 crates/katana/primitives/src/conversion/mod.rs rename crates/katana/primitives/src/serde/{contract.rs => blockifier.rs} (100%) diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index bbb5db9f7f..69dc8e49c7 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -15,6 +15,8 @@ starknet.workspace = true starknet_api.workspace = true [features] +default = [ "blockifier", "conversion", "serde" ] + blockifier = [ ] -default = [ "blockifier", "serde" ] +conversion = [ ] serde = [ ] diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 1626f8a68f..68407c856b 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -22,7 +22,7 @@ pub type SierraClass = starknet::core::types::FlattenedSierraClass; /// Represents a contract address. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ContractAddress(FieldElement); +pub struct ContractAddress(pub FieldElement); impl ContractAddress { pub fn new(address: FieldElement) -> Self { diff --git a/crates/katana/primitives/src/conversion/blockifier.rs b/crates/katana/primitives/src/conversion/blockifier.rs new file mode 100644 index 0000000000..04f73c7ef5 --- /dev/null +++ b/crates/katana/primitives/src/conversion/blockifier.rs @@ -0,0 +1,145 @@ +//! Translation layer for converting the primitive types to the execution engine types. + +use std::sync::Arc; + +use starknet_api::core::{ + ClassHash, CompiledClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey, +}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::patricia_key; +use starknet_api::transaction::{ + Calldata, ContractAddressSalt, DeclareTransaction, DeclareTransactionV0V1, + DeclareTransactionV2, DeployAccountTransaction, Fee, InvokeTransaction, InvokeTransactionV1, + L1HandlerTransaction, TransactionHash, TransactionSignature, TransactionVersion, +}; + +mod primitives { + pub use crate::contract::{ContractAddress, Nonce}; + pub use crate::transaction::{ + DeclareTx, DeclareTxV1, DeclareTxV2, DeclareTxWithCompiledClass, DeployAccountTx, + DeployAccountTxWithContractAddress, InvokeTx, InvokeTxV1, L1HandlerTx, + }; + pub use crate::FieldElement; +} + +impl From for ContractAddress { + fn from(address: primitives::ContractAddress) -> Self { + Self(patricia_key!(address.0)) + } +} + +impl From for InvokeTransaction { + fn from(tx: primitives::InvokeTx) -> Self { + match tx { + primitives::InvokeTx::V1(tx) => InvokeTransaction::V1(tx.into()), + } + } +} + +impl From for InvokeTransactionV1 { + fn from(tx: primitives::InvokeTxV1) -> Self { + Self { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + calldata: calldata_from_felts_vec(tx.calldata), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + } + } +} + +impl From for DeclareTransaction { + fn from(tx: primitives::DeclareTx) -> Self { + match tx { + primitives::DeclareTx::V1(tx) => DeclareTransaction::V1(tx.into()), + primitives::DeclareTx::V2(tx) => DeclareTransaction::V2(tx.into()), + } + } +} + +impl From for DeclareTransactionV0V1 { + fn from(tx: primitives::DeclareTxV1) -> Self { + Self { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + class_hash: ClassHash(tx.class_hash.into()), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + } + } +} + +impl From for DeclareTransactionV2 { + fn from(tx: primitives::DeclareTxV2) -> Self { + Self { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + class_hash: ClassHash(tx.class_hash.into()), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.into()), + } + } +} + +impl From for DeployAccountTransaction { + fn from(tx: primitives::DeployAccountTx) -> Self { + Self { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + class_hash: ClassHash(tx.class_hash.into()), + version: TransactionVersion(tx.version.into()), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + contract_address_salt: ContractAddressSalt(tx.contract_address_salt.into()), + constructor_calldata: calldata_from_felts_vec(tx.constructor_calldata), + } + } +} + +impl From for L1HandlerTransaction { + fn from(tx: primitives::L1HandlerTx) -> Self { + Self { + nonce: Nonce(tx.nonce.into()), + contract_address: tx.contract_address.into(), + version: TransactionVersion(tx.version.into()), + calldata: calldata_from_felts_vec(tx.calldata), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + entry_point_selector: EntryPointSelector(tx.entry_point_selector.into()), + } + } +} + +impl From + for blockifier::transaction::transactions::DeployAccountTransaction +{ + fn from(tx: primitives::DeployAccountTxWithContractAddress) -> Self { + Self { tx: tx.0.into(), contract_address: tx.1.into() } + } +} + +impl From + for blockifier::transaction::transactions::DeclareTransaction +{ + fn from(tx: primitives::DeclareTxWithCompiledClass) -> Self { + Self::new(tx.0.into(), tx.1).expect("tx & class must be compatible") + } +} + +#[inline] +fn felt_to_starkfelt_vec(felts: Vec) -> Vec { + felts.into_iter().map(|f| f.into()).collect() +} + +#[inline] +fn calldata_from_felts_vec(felts: Vec) -> Calldata { + Calldata(Arc::new(felt_to_starkfelt_vec(felts))) +} + +#[inline] +fn signature_from_felts_vec(felts: Vec) -> TransactionSignature { + TransactionSignature(felt_to_starkfelt_vec(felts)) +} diff --git a/crates/katana/primitives/src/conversion/mod.rs b/crates/katana/primitives/src/conversion/mod.rs new file mode 100644 index 0000000000..013169b02e --- /dev/null +++ b/crates/katana/primitives/src/conversion/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "blockifier")] +pub mod blockifier; diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 36a39b0272..804a759900 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -2,6 +2,8 @@ pub mod block; pub mod contract; pub mod transaction; +#[cfg(feature = "conversion")] +pub mod conversion; #[cfg(feature = "serde")] pub mod serde; diff --git a/crates/katana/primitives/src/serde/contract.rs b/crates/katana/primitives/src/serde/blockifier.rs similarity index 100% rename from crates/katana/primitives/src/serde/contract.rs rename to crates/katana/primitives/src/serde/blockifier.rs diff --git a/crates/katana/primitives/src/serde/mod.rs b/crates/katana/primitives/src/serde/mod.rs index ed14f5e51d..537efa7ca8 100644 --- a/crates/katana/primitives/src/serde/mod.rs +++ b/crates/katana/primitives/src/serde/mod.rs @@ -1,4 +1,4 @@ -//! De/serializable counterparts for types that are not de/serializable by default. +//! De/serializable counterparts for types that are not de/serializable out of the box. #[cfg(feature = "blockifier")] -pub mod contract; +pub mod blockifier; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 0368548737..9e658d48e1 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use starknet::core::types::{Event, MsgToL1}; -use crate::contract::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use crate::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, +}; use crate::FieldElement; /// The hash of a transaction. @@ -10,6 +12,15 @@ pub type TxHash = FieldElement; /// The sequential number for all the transactions.. pub type TxNumber = u64; +/// Represents a transaction that can be executed. +#[derive(Debug, Clone)] +pub enum ExecutionTx { + Invoke(InvokeTx), + L1Handler(L1HandlerTx), + Declare(DeclareTxWithCompiledClass), + DeployAccount(DeployAccountTxWithContractAddress), +} + #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Transaction { @@ -36,6 +47,12 @@ pub struct InvokeTxV1 { pub sender_address: ContractAddress, } +#[derive(Debug, Clone)] +pub struct DeclareTxWithCompiledClass(pub DeclareTx, pub CompiledContractClass); + +#[derive(Debug, Clone)] +pub struct DeployAccountTxWithContractAddress(pub DeployAccountTx, pub ContractAddress); + #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DeclareTx { @@ -104,7 +121,17 @@ pub type ExecutionResources = HashMap; /// The receipt of a transaction containing the outputs of its execution. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Receipt { +pub enum Receipt { + Invoke(CommonReceipt), + Declare(CommonReceipt), + L1Handler(CommonReceipt), + DeployAccount(DeployAccountReceipt), +} + +/// Commong transaction receipt fields. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CommonReceipt { pub actual_fee: u128, pub events: Vec, pub messages_sent: Vec, @@ -112,3 +139,12 @@ pub struct Receipt { pub actual_resources: ExecutionResources, pub contract_address: Option, } + +/// Commong transaction receipt fields. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeployAccountReceipt { + #[cfg_attr(feature = "serde", serde(flatten))] + pub common: CommonReceipt, + pub contract_adddress: ContractAddress, +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index aabb2e2094..48ebdc9e6c 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -3,7 +3,7 @@ use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::serde::contract::SerializableContractClass; +use katana_primitives::serde::blockifier::SerializableContractClass; use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; From 04dff642bcab82e59c17b611dd77e3f21ad106db Mon Sep 17 00:00:00 2001 From: Kariy Date: Wed, 22 Nov 2023 11:16:16 +0800 Subject: [PATCH 077/192] Add rpc types primitives conversion --- Cargo.lock | 3 + crates/katana/primitives/Cargo.toml | 10 +- .../katana/primitives/src/conversion/mod.rs | 2 + .../katana/primitives/src/conversion/rpc.rs | 276 ++++++++++++++++++ crates/katana/primitives/src/lib.rs | 2 +- crates/katana/primitives/src/transaction.rs | 1 - crates/katana/primitives/src/utils/mod.rs | 1 + .../primitives/src/utils/transaction.rs | 160 ++++++++++ 8 files changed, 450 insertions(+), 5 deletions(-) create mode 100644 crates/katana/primitives/src/conversion/rpc.rs create mode 100644 crates/katana/primitives/src/utils/mod.rs create mode 100644 crates/katana/primitives/src/utils/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index af8516e21d..fab4b09f27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5132,8 +5132,11 @@ dependencies = [ name = "katana-primitives" version = "0.3.14" dependencies = [ + "anyhow", "blockifier", + "cairo-lang-starknet", "cairo-vm", + "flate2", "serde", "serde_json", "starknet", diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 69dc8e49c7..c0481255c1 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -7,16 +7,20 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -blockifier.workspace = true +anyhow.workspace = true cairo-vm.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true + +blockifier.workspace = true +cairo-lang-starknet.workspace = true +flate2.workspace = true starknet_api.workspace = true [features] -default = [ "blockifier", "conversion", "serde" ] +default = [ "blockifier", "rpc", "serde" ] blockifier = [ ] -conversion = [ ] +rpc = [ ] serde = [ ] diff --git a/crates/katana/primitives/src/conversion/mod.rs b/crates/katana/primitives/src/conversion/mod.rs index 013169b02e..00281f3728 100644 --- a/crates/katana/primitives/src/conversion/mod.rs +++ b/crates/katana/primitives/src/conversion/mod.rs @@ -1,2 +1,4 @@ #[cfg(feature = "blockifier")] pub mod blockifier; +#[cfg(feature = "rpc")] +pub mod rpc; diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs new file mode 100644 index 0000000000..50aa4d9d0d --- /dev/null +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -0,0 +1,276 @@ +use std::collections::HashMap; +use std::io::{Read, Write}; + +use anyhow::{anyhow, Result}; +use blockifier::execution::contract_class::ContractClassV0; +use cairo_lang_starknet::casm_contract_class::CasmContractClass; +use cairo_vm::serde::deserialize_program::ProgramJson; +use serde_json::json; +pub use starknet::core::types::contract::legacy::{LegacyContractClass, LegacyProgram}; +pub use starknet::core::types::contract::CompiledClass; +use starknet::core::types::{ + BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, + BroadcastedInvokeTransaction, CompressedLegacyContractClass, ContractClass, + LegacyContractEntryPoint, LegacyEntryPointsByType, +}; +use starknet::core::utils::get_contract_address; +use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; + +use self::primitives::{ContractAddress, InvokeTxV1}; +use crate::contract::{CompiledContractClass, SierraClass}; +use crate::utils::transaction::{ + compute_declare_v1_transaction_hash, compute_declare_v2_transaction_hash, + compute_deploy_account_v1_transaction_hash, compute_invoke_v1_transaction_hash, +}; +use crate::FieldElement; + +mod primitives { + pub use crate::contract::{CompiledContractClass, ContractAddress, Nonce}; + pub use crate::transaction::{ + DeclareTx, DeclareTxV1, DeclareTxV2, DeclareTxWithCompiledClass, DeployAccountTx, + DeployAccountTxWithContractAddress, InvokeTx, InvokeTxV1, L1HandlerTx, Transaction, + }; + pub use crate::FieldElement; +} + +// Transactions + +impl primitives::InvokeTx { + pub fn from_broadcasted_rpc(tx: BroadcastedInvokeTransaction, chain_id: FieldElement) -> Self { + let transaction_hash = compute_invoke_v1_transaction_hash( + tx.sender_address, + &tx.calldata, + tx.max_fee, + chain_id, + tx.nonce, + tx.is_query, + ); + + primitives::InvokeTx::V1(InvokeTxV1 { + transaction_hash, + nonce: tx.nonce, + calldata: tx.calldata, + signature: tx.signature, + sender_address: tx.sender_address.into(), + max_fee: tx.max_fee.try_into().expect("max_fee is too large"), + }) + } +} + +impl primitives::DeployAccountTx { + pub fn from_broadcasted_rpc( + tx: BroadcastedDeployAccountTransaction, + chain_id: FieldElement, + ) -> (Self, ContractAddress) { + let contract_address = get_contract_address( + tx.contract_address_salt, + tx.class_hash, + &tx.constructor_calldata, + FieldElement::ZERO, + ); + + let transaction_hash = compute_deploy_account_v1_transaction_hash( + contract_address, + &tx.constructor_calldata, + tx.class_hash, + tx.contract_address_salt, + tx.max_fee, + chain_id, + tx.nonce, + tx.is_query, + ); + + ( + Self { + transaction_hash, + nonce: tx.nonce, + signature: tx.signature, + class_hash: tx.class_hash, + version: FieldElement::ONE, + constructor_calldata: tx.constructor_calldata, + contract_address_salt: tx.contract_address_salt, + max_fee: tx.max_fee.try_into().expect("max_fee is too large"), + }, + contract_address.into(), + ) + } +} + +impl primitives::DeclareTx { + pub fn from_broadcasted_rpc( + tx: BroadcastedDeclareTransaction, + chain_id: FieldElement, + ) -> (Self, primitives::CompiledContractClass) { + match tx { + BroadcastedDeclareTransaction::V1(tx) => { + let (class_hash, contract_class) = + legacy_rpc_to_inner_class(&tx.contract_class).expect("valid contract class"); + + let transaction_hash = compute_declare_v1_transaction_hash( + tx.sender_address, + class_hash, + tx.max_fee, + chain_id, + tx.nonce, + tx.is_query, + ); + + ( + primitives::DeclareTx::V1(primitives::DeclareTxV1 { + class_hash, + nonce: tx.nonce, + transaction_hash, + signature: tx.signature, + sender_address: tx.sender_address.into(), + max_fee: tx.max_fee.try_into().expect("max_fee is too large"), + }), + contract_class, + ) + } + + BroadcastedDeclareTransaction::V2(tx) => { + let (class_hash, contract_class) = + rpc_to_inner_class(&tx.contract_class).expect("valid contract class"); + + let transaction_hash = compute_declare_v2_transaction_hash( + tx.sender_address, + class_hash, + tx.max_fee, + chain_id, + tx.nonce, + tx.compiled_class_hash, + tx.is_query, + ); + + ( + primitives::DeclareTx::V2(primitives::DeclareTxV2 { + class_hash, + nonce: tx.nonce, + transaction_hash, + signature: tx.signature, + sender_address: tx.sender_address.into(), + compiled_class_hash: tx.compiled_class_hash, + max_fee: tx.max_fee.try_into().expect("max_fee is too large"), + }), + contract_class, + ) + } + } + } +} + +// Contract class + +pub fn legacy_inner_to_rpc_class(legacy_contract_class: ContractClassV0) -> Result { + let entry_points_by_type = + to_rpc_legacy_entry_points_by_type(&legacy_contract_class.entry_points_by_type)?; + + let program = { + let program: ProgramJson = legacy_contract_class.program.clone().into(); + compress(&serde_json::to_vec(&program)?)? + }; + + Ok(ContractClass::Legacy(CompressedLegacyContractClass { + program, + abi: None, + entry_points_by_type, + })) +} + +pub fn rpc_to_inner_class( + contract_class: &SierraClass, +) -> Result<(FieldElement, CompiledContractClass)> { + let class_hash = contract_class.class_hash(); + let contract_class = rpc_to_cairo_contract_class(contract_class)?; + let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; + Ok((class_hash, CompiledContractClass::V1(casm_contract.try_into()?))) +} + +/// Converts `starknet-rs` RPC [SierraClass] type to Cairo's +/// [ContractClass](cairo_lang_starknet::contract_class::ContractClass) type. +pub fn rpc_to_cairo_contract_class( + contract_class: &SierraClass, +) -> Result { + let value = serde_json::to_value(contract_class)?; + + Ok(cairo_lang_starknet::contract_class::ContractClass { + abi: serde_json::from_value(value["abi"].clone()).ok(), + sierra_program: serde_json::from_value(value["sierra_program"].clone())?, + entry_points_by_type: serde_json::from_value(value["entry_points_by_type"].clone())?, + contract_class_version: serde_json::from_value(value["contract_class_version"].clone())?, + sierra_program_debug_info: serde_json::from_value( + value["sierra_program_debug_info"].clone(), + ) + .ok(), + }) +} + +/// Compute the compiled class hash from the given [FlattenedSierraClass]. +pub fn compiled_class_hash_from_flattened_sierra_class( + contract_class: &SierraClass, +) -> Result { + let contract_class = rpc_to_cairo_contract_class(contract_class)?; + let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; + let res = serde_json::to_string_pretty(&casm_contract)?; + let compiled_class: CompiledClass = serde_json::from_str(&res)?; + Ok(compiled_class.class_hash()?) +} + +pub fn legacy_rpc_to_inner_class( + compressed_legacy_contract: &CompressedLegacyContractClass, +) -> Result<(FieldElement, CompiledContractClass)> { + let legacy_program_json = decompress(&compressed_legacy_contract.program)?; + let legacy_program: LegacyProgram = serde_json::from_str(&legacy_program_json)?; + + let flattened = json!({ + "program": legacy_program, + "abi": compressed_legacy_contract.abi, + "entry_points_by_type": compressed_legacy_contract.entry_points_by_type, + }); + + let legacy_contract_class: LegacyContractClass = serde_json::from_value(flattened.clone())?; + let class_hash = legacy_contract_class.class_hash()?; + let contract_class: ContractClassV0 = serde_json::from_value(flattened)?; + + Ok((class_hash, CompiledContractClass::V0(contract_class))) +} + +/// Returns a [LegacyEntryPointsByType](rpc::LegacyEntryPointsByType) (RPC type) from a +/// [EntryPointType] (blockifier type) +fn to_rpc_legacy_entry_points_by_type( + entries: &HashMap>, +) -> Result { + fn collect_entry_points( + entries: &HashMap>, + entry_point_type: &EntryPointType, + ) -> Result> { + Ok(entries + .get(entry_point_type) + .ok_or(anyhow!("Missing {entry_point_type:?} entry point",))? + .iter() + .map(|e| LegacyContractEntryPoint { + offset: e.offset.0 as u64, + selector: FieldElement::from(e.selector.0), + }) + .collect::>()) + } + + let constructor = collect_entry_points(entries, &EntryPointType::Constructor)?; + let external = collect_entry_points(entries, &EntryPointType::External)?; + let l1_handler = collect_entry_points(entries, &EntryPointType::L1Handler)?; + + Ok(LegacyEntryPointsByType { constructor, external, l1_handler }) +} + +fn compress(data: &[u8]) -> Result, std::io::Error> { + let mut gzip_encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); + Write::write_all(&mut gzip_encoder, data)?; + gzip_encoder.finish() +} + +fn decompress(data: &[u8]) -> Result { + let mut decoder = flate2::read::GzDecoder::new(data); + let mut decoded = String::new(); + Read::read_to_string(&mut decoder, &mut decoded)?; + Ok(decoded) +} diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 804a759900..3a752e0249 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -2,9 +2,9 @@ pub mod block; pub mod contract; pub mod transaction; -#[cfg(feature = "conversion")] pub mod conversion; #[cfg(feature = "serde")] pub mod serde; +pub mod utils; pub type FieldElement = starknet::core::types::FieldElement; diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 9e658d48e1..faac27467c 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -89,7 +89,6 @@ pub struct L1HandlerTx { pub transaction_hash: TxHash, pub version: FieldElement, pub nonce: Nonce, - pub paid_l1_fee: u128, pub calldata: Vec, pub contract_address: ContractAddress, pub entry_point_selector: FieldElement, diff --git a/crates/katana/primitives/src/utils/mod.rs b/crates/katana/primitives/src/utils/mod.rs new file mode 100644 index 0000000000..37f08066e0 --- /dev/null +++ b/crates/katana/primitives/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod transaction; diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs new file mode 100644 index 0000000000..0b48e9a71d --- /dev/null +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -0,0 +1,160 @@ +use starknet::core::crypto::compute_hash_on_elements; + +use crate::FieldElement; + +/// 2^ 128 +const QUERY_VERSION_OFFSET: FieldElement = FieldElement::from_mont([ + 18446744073700081665, + 17407, + 18446744073709551584, + 576460752142434320, +]); + +/// Cairo string for "invoke" +const PREFIX_INVOKE: FieldElement = FieldElement::from_mont([ + 18443034532770911073, + 18446744073709551615, + 18446744073709551615, + 513398556346534256, +]); + +/// Cairo string for "declare" +const PREFIX_DECLARE: FieldElement = FieldElement::from_mont([ + 17542456862011667323, + 18446744073709551615, + 18446744073709551615, + 191557713328401194, +]); + +/// Cairo string for "deploy_account" +const PREFIX_DEPLOY_ACCOUNT: FieldElement = FieldElement::from_mont([ + 3350261884043292318, + 18443211694809419988, + 18446744073709551615, + 461298303000467581, +]); + +/// Cairo string for "l1_handler" +const PREFIX_L1_HANDLER: FieldElement = FieldElement::from_mont([ + 1365666230910873368, + 18446744073708665300, + 18446744073709551615, + 157895833347907735, +]); + +/// Compute the hash of a V1 DeployAccount transaction. +#[allow(clippy::too_many_arguments)] +pub fn compute_deploy_account_v1_transaction_hash( + contract_address: FieldElement, + constructor_calldata: &[FieldElement], + class_hash: FieldElement, + salt: FieldElement, + max_fee: FieldElement, + chain_id: FieldElement, + nonce: FieldElement, + is_query: bool, +) -> FieldElement { + let calldata_to_hash = [&[class_hash, salt], constructor_calldata].concat(); + + compute_hash_on_elements(&[ + PREFIX_DEPLOY_ACCOUNT, + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ + contract_address, + FieldElement::ZERO, // entry_point_selector + compute_hash_on_elements(&calldata_to_hash), + max_fee, + chain_id, + nonce, + ]) +} + +/// Compute the hash of a V1 Declare transaction. +pub fn compute_declare_v1_transaction_hash( + sender_address: FieldElement, + class_hash: FieldElement, + max_fee: FieldElement, + chain_id: FieldElement, + nonce: FieldElement, + is_query: bool, +) -> FieldElement { + compute_hash_on_elements(&[ + PREFIX_DECLARE, + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ + sender_address, + FieldElement::ZERO, // entry_point_selector + compute_hash_on_elements(&[class_hash]), + max_fee, + chain_id, + nonce, + ]) +} + +/// Compute the hash of a V2 Declare transaction. +pub fn compute_declare_v2_transaction_hash( + sender_address: FieldElement, + class_hash: FieldElement, + max_fee: FieldElement, + chain_id: FieldElement, + nonce: FieldElement, + compiled_class_hash: FieldElement, + is_query: bool, +) -> FieldElement { + compute_hash_on_elements(&[ + PREFIX_DECLARE, + if is_query { QUERY_VERSION_OFFSET + FieldElement::TWO } else { FieldElement::TWO }, /* version */ + sender_address, + FieldElement::ZERO, // entry_point_selector + compute_hash_on_elements(&[class_hash]), + max_fee, + chain_id, + nonce, + compiled_class_hash, + ]) +} + +/// Compute the hash of a V1 Invoke transaction. +pub fn compute_invoke_v1_transaction_hash( + sender_address: FieldElement, + calldata: &[FieldElement], + max_fee: FieldElement, + chain_id: FieldElement, + nonce: FieldElement, + is_query: bool, +) -> FieldElement { + compute_hash_on_elements(&[ + PREFIX_INVOKE, + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ + sender_address, + FieldElement::ZERO, // entry_point_selector + compute_hash_on_elements(calldata), + max_fee, + chain_id, + nonce, + ]) +} + +/// Computes the hash of a L1 handler transaction +/// from the fields involved in the computation, +/// as felts values. +pub fn compute_l1_handler_transaction_hash( + version: FieldElement, + contract_address: FieldElement, + entry_point_selector: FieldElement, + calldata: &[FieldElement], + chain_id: FieldElement, + nonce: FieldElement, +) -> FieldElement { + // No fee on L2 for L1 handler transaction. + let fee = FieldElement::ZERO; + + compute_hash_on_elements(&[ + PREFIX_L1_HANDLER, + version, + contract_address, + entry_point_selector, + compute_hash_on_elements(calldata), + fee, + chain_id, + nonce, + ]) +} From 74d1a913d9bcc431e8c2760729c2f6b731118ff1 Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 23 Nov 2023 12:12:10 +0800 Subject: [PATCH 078/192] Implement in memory storage provider --- Cargo.lock | 3 + crates/katana/primitives/src/block.rs | 4 +- crates/katana/storage/provider/Cargo.toml | 8 +- crates/katana/storage/provider/src/lib.rs | 4 - .../provider/src/providers/in_memory/mod.rs | 270 ++++++++++++- .../provider/src/providers/in_memory/state.rs | 373 ++++++++++++++++++ .../storage/provider/src/traits/state.rs | 9 +- 7 files changed, 643 insertions(+), 28 deletions(-) create mode 100644 crates/katana/storage/provider/src/providers/in_memory/state.rs diff --git a/Cargo.lock b/Cargo.lock index fab4b09f27..ac7b7f9e85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5148,8 +5148,11 @@ name = "katana-provider" version = "0.3.14" dependencies = [ "anyhow", + "auto_impl", "katana-db", "katana-primitives", + "parking_lot 0.12.1", + "starknet", ] [[package]] diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index a2e33500d9..560e9710a2 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -33,8 +33,8 @@ pub struct Header { #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Block { - header: Header, - body: Vec, + pub header: Header, + pub body: Vec, } impl From for BlockHashOrNumber { diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index b75a455bcb..7bf3aa866b 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -7,10 +7,16 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow.workspace = true katana-db = { path = "../db" } katana-primitives = { path = "../../primitives" } +anyhow.workspace = true +auto_impl = "1.1.0" +parking_lot.workspace = true + [features] default = [ "in_memory" ] in_memory = [ ] + +[dev-dependencies] +starknet.workspace = true diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index 04b16f2e80..2513fc723b 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -153,10 +153,6 @@ where ) -> Result> { self.provider.storage(address, storage_key) } - - fn compiled_contract_class(&self, hash: ClassHash) -> Result> { - self.provider.compiled_contract_class(hash) - } } impl StateProviderExt for BlockchainProvider diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 1ee294fdc1..38e667dc96 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -1,23 +1,45 @@ +pub mod state; + use std::collections::HashMap; +use std::ops::RangeInclusive; +use std::sync::Arc; + +use anyhow::Result; +use katana_db::models::block::StoredBlockBodyIndices; +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, +}; +use katana_primitives::contract::{ContractAddress, GenericContractInfo}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use parking_lot::RwLock; -use katana_primitives::block::{Block, StateUpdate}; -use katana_primitives::transaction::Transaction; -use katana_primitives::FieldElement; +use self::state::{HistoricalStates, InMemoryState, LatestStateProvider, SnapshotStateProvider}; +use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; +use crate::traits::contract::ContractProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::state_update::StateUpdateProvider; +use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; -#[derive(Debug, Default)] +#[derive(Default)] pub struct InMemoryProvider { - /// Mapping from block hash -> block - pub blocks: HashMap, - /// Mapping from block number -> block hash - pub hashes: HashMap, - /// Mapping from block number -> state update - pub state_update: HashMap, - /// The latest block hash - pub latest_hash: FieldElement, - /// The latest block number - pub latest_number: u64, - /// Mapping of all known transactions from its transaction hash - pub transactions: HashMap, + pub block_headers: HashMap, + pub block_hashes: HashMap, + pub block_numbers: HashMap, + pub block_body_indices: HashMap, + + pub latest_block_number: BlockNumber, + pub latest_block_hash: BlockHash, + + pub state_update: HashMap, + + pub transactions: Vec, + pub transaction_numbers: HashMap, + pub transaction_hashes: HashMap, + pub receipts: Vec, + + pub state: Arc, + + pub historical_states: RwLock, } impl InMemoryProvider { @@ -25,3 +47,219 @@ impl InMemoryProvider { Self::default() } } + +impl BlockHashProvider for InMemoryProvider { + fn latest_hash(&self) -> Result { + Ok(self.latest_block_hash) + } + + fn block_hash_by_num(&self, num: BlockNumber) -> Result> { + Ok(self.block_hashes.get(&num).cloned()) + } +} + +impl BlockNumberProvider for InMemoryProvider { + fn latest_number(&self) -> Result { + Ok(self.latest_block_number) + } + + fn block_number_by_hash(&self, hash: BlockHash) -> Result> { + Ok(self.block_numbers.get(&hash).cloned()) + } +} + +impl HeaderProvider for InMemoryProvider { + fn header(&self, id: katana_primitives::block::BlockHashOrNumber) -> Result> { + match id { + katana_primitives::block::BlockHashOrNumber::Num(num) => { + Ok(self.block_headers.get(&num).cloned()) + } + + katana_primitives::block::BlockHashOrNumber::Hash(hash) => { + let header @ Some(_) = self + .block_numbers + .get(&hash) + .and_then(|num| self.block_headers.get(num).cloned()) + else { + return Ok(None); + }; + Ok(header) + } + } + } +} + +impl BlockProvider for InMemoryProvider { + fn block(&self, id: BlockHashOrNumber) -> Result> { + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + }; + + let Some(header) = block_num.and_then(|num| self.block_headers.get(&num).cloned()) else { + return Ok(None); + }; + + let body = self.transactions_by_block(id)?.unwrap_or_default(); + + Ok(Some(Block { header, body })) + } + + fn blocks_in_range(&self, range: RangeInclusive) -> Result> { + let mut blocks = Vec::new(); + for num in range { + if let Some(block) = self.block(BlockHashOrNumber::Num(num))? { + blocks.push(block); + } + } + Ok(blocks) + } +} + +impl TransactionProvider for InMemoryProvider { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + Ok(self + .transaction_numbers + .get(&hash) + .and_then(|num| self.transactions.get(*num as usize).cloned())) + } + + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + let count = *tx_count as usize; + + Ok(Some(self.transactions[offset..offset + count].to_vec())) + } + + fn transaction_by_block_and_idx( + &self, + block_id: BlockHashOrNumber, + idx: u64, + ) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + + if idx >= *tx_count { + return Ok(None); + } + + Ok(Some(self.transactions[offset + idx as usize].clone())) + } +} + +impl TransactionsProviderExt for InMemoryProvider { + fn transaction_hashes_by_range(&self, range: std::ops::Range) -> Result> { + let mut hashes = Vec::new(); + for num in range { + if let Some(hash) = self.transaction_hashes.get(&num).cloned() { + hashes.push(hash); + } + } + Ok(hashes) + } +} + +impl ReceiptProvider for InMemoryProvider { + fn receipt_by_hash(&self, hash: TxHash) -> Result> { + let receipt = self + .transaction_numbers + .get(&hash) + .and_then(|num| self.receipts.get(*num as usize).cloned()); + Ok(receipt) + } + + fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + let count = *tx_count as usize; + + Ok(Some(self.receipts[offset..offset + count].to_vec())) + } +} + +impl ContractProvider for InMemoryProvider { + fn contract(&self, address: ContractAddress) -> Result> { + let contract = self.state.contract_state.read().get(&address).cloned(); + Ok(contract) + } +} + +impl StateUpdateProvider for InMemoryProvider { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + }; + + let state_update = block_num.and_then(|num| self.state_update.get(&num).cloned()); + Ok(state_update) + } +} + +impl StateFactoryProvider for InMemoryProvider { + fn latest(&self) -> Result> { + Ok(Box::new(LatestStateProvider(Arc::clone(&self.state)))) + } + + fn historical(&self, block_id: BlockHashOrNumber) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash)?, + }; + + let provider @ Some(_) = + block_num.and_then(|num| { + self.historical_states.read().get(&num).cloned().map(|provider| { + Box::new(SnapshotStateProvider(provider)) as Box + }) + }) + else { + return Ok(None); + }; + + Ok(provider) + } +} + +#[cfg(test)] +mod tests { + use super::InMemoryProvider; + + pub(super) fn create_mock_provider() -> InMemoryProvider { + InMemoryProvider { ..Default::default() } + } +} diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs new file mode 100644 index 0000000000..1e84d2c1c7 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -0,0 +1,373 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::Arc; + +use anyhow::Result; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + Nonce, SierraClass, StorageKey, StorageValue, +}; +use parking_lot::RwLock; + +use crate::traits::state::{StateProvider, StateProviderExt}; + +type ContractStorageMap = HashMap<(ContractAddress, StorageKey), StorageValue>; +type ContractStateMap = HashMap; + +type SierraClassesMap = HashMap; +type CompiledClassesMap = HashMap; +type CompiledClassHashesMap = HashMap; + +pub struct StateSnapshot { + pub contract_state: ContractStateMap, + pub storage: ContractStorageMap, + pub compiled_class_hashes: CompiledClassHashesMap, + pub shared_sierra_classes: Arc>, + pub shared_compiled_classes: Arc>, +} + +#[derive(Default)] +pub struct InMemoryState { + pub contract_state: RwLock, + pub storage: RwLock, + pub compiled_class_hashes: RwLock, + pub shared_sierra_classes: Arc>, + pub shared_compiled_classes: Arc>, +} + +impl InMemoryState { + pub(crate) fn create_snapshot(&self) -> StateSnapshot { + StateSnapshot { + storage: self.storage.read().clone(), + contract_state: self.contract_state.read().clone(), + compiled_class_hashes: self.compiled_class_hashes.read().clone(), + shared_sierra_classes: self.shared_sierra_classes.clone(), + shared_compiled_classes: self.shared_compiled_classes.clone(), + } + } +} + +const DEFAULT_HISTORY_LIMIT: usize = 500; +const MIN_HISTORY_LIMIT: usize = 10; + +/// Represents the complete state of a single block. +/// +/// It should store at N - 1 states, where N is the latest block number. +pub struct HistoricalStates { + /// The states at a certain block based on the block number + states: HashMap>, + /// How many states to store at most + in_memory_limit: usize, + /// minimum amount of states we keep in memory + min_in_memory_limit: usize, + /// all states present, used to enforce `in_memory_limit` + present: VecDeque, +} + +impl HistoricalStates { + pub fn new(limit: usize) -> Self { + Self { + in_memory_limit: limit, + states: Default::default(), + present: Default::default(), + min_in_memory_limit: limit.min(MIN_HISTORY_LIMIT), + } + } + + /// Returns the state for the given `block_hash` if present + pub fn get(&self, block_num: &BlockNumber) -> Option<&Arc> { + self.states.get(block_num) + } + + /// Inserts a new (block_hash -> state) pair + /// + /// When the configured limit for the number of states that can be stored in memory is reached, + /// the oldest state is removed. + /// + /// Since we keep a snapshot of the entire state as history, the size of the state will increase + /// with the transactions processed. To counter this, we gradually decrease the cache limit with + /// the number of states/blocks until we reached the `min_limit`. + pub fn insert(&mut self, block_num: BlockNumber, state: StateSnapshot) { + if self.present.len() >= self.in_memory_limit { + // once we hit the max limit we gradually decrease it + self.in_memory_limit = + self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit); + } + + self.enforce_limits(); + self.states.insert(block_num, Arc::new(state)); + self.present.push_back(block_num); + } + + /// Enforces configured limits + fn enforce_limits(&mut self) { + // enforce memory limits + while self.present.len() >= self.in_memory_limit { + // evict the oldest block in memory + if let Some(block_num) = self.present.pop_front() { + self.states.remove(&block_num); + } + } + } +} + +impl Default for HistoricalStates { + fn default() -> Self { + // enough in memory to store `DEFAULT_HISTORY_LIMIT` blocks in memory + Self::new(DEFAULT_HISTORY_LIMIT) + } +} + +pub struct LatestStateProvider(pub(super) Arc); + +impl StateProvider for LatestStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + let nonce = self.0.contract_state.read().get(&address).map(|info| info.nonce); + Ok(nonce) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + let value = self.0.storage.read().get(&(address, storage_key)).cloned(); + Ok(value) + } + + fn class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_compiled_classes.read().get(&hash).cloned(); + Ok(class) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + let class_hash = self.0.contract_state.read().get(&address).map(|info| info.class_hash); + Ok(class_hash) + } +} + +impl StateProviderExt for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_sierra_classes.read().get(&hash).cloned(); + Ok(class) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + let hash = self.0.compiled_class_hashes.read().get(&hash).cloned(); + Ok(hash) + } +} + +pub struct SnapshotStateProvider(pub(super) Arc); + +impl StateProvider for SnapshotStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + let nonce = self.0.contract_state.get(&address).map(|info| info.nonce); + Ok(nonce) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + let value = self.0.storage.get(&(address, storage_key)).cloned(); + Ok(value) + } + + fn class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_compiled_classes.read().get(&hash).cloned(); + Ok(class) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + let class_hash = self.0.contract_state.get(&address).map(|info| info.class_hash); + Ok(class_hash) + } +} + +impl StateProviderExt for SnapshotStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_sierra_classes.read().get(&hash).cloned(); + Ok(class) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + let hash = self.0.compiled_class_hashes.get(&hash).cloned(); + Ok(hash) + } +} + +#[cfg(test)] +mod tests { + use katana_primitives::block::BlockHashOrNumber; + use katana_primitives::contract::StorageKey; + use starknet::macros::felt; + + use super::*; + use crate::providers::in_memory::tests::create_mock_provider; + use crate::traits::state::StateFactoryProvider; + + const STORAGE_KEY: StorageKey = felt!("0x1"); + + const ADDR_1: ContractAddress = ContractAddress(felt!("0xADD1")); + const ADDR_1_STORAGE_VALUE_AT_1: StorageKey = felt!("0x8080"); + const ADDR_1_STORAGE_VALUE_AT_2: StorageKey = felt!("0x1212"); + const ADDR_1_CLASS_HASH_AT_1: ClassHash = felt!("0x4337"); + const ADDR_1_CLASS_HASH_AT_2: ClassHash = felt!("0x7334"); + const ADDR_1_NONCE_AT_1: Nonce = felt!("0x1"); + const ADDR_1_NONCE_AT_2: Nonce = felt!("0x2"); + + const ADDR_2: ContractAddress = ContractAddress(felt!("0xADD2")); + const ADDR_2_STORAGE_VALUE_AT_1: StorageKey = felt!("0x9090"); + const ADDR_2_STORAGE_VALUE_AT_2: StorageKey = felt!("1313"); + const ADDR_2_CLASS_HASH_AT_1: ClassHash = felt!("0x1559"); + const ADDR_2_CLASS_HASH_AT_2: ClassHash = felt!("0x9551"); + const ADDR_2_NONCE_AT_1: Nonce = felt!("0x1"); + const ADDR_2_NONCE_AT_2: Nonce = felt!("0x2"); + + fn create_mock_state() -> InMemoryState { + let storage = HashMap::from([ + ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_1), + ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_1), + ]); + + let contract_state = HashMap::from([ + ( + ADDR_1, + GenericContractInfo { nonce: felt!("0x1"), class_hash: ADDR_1_CLASS_HASH_AT_1 }, + ), + ( + ADDR_2, + GenericContractInfo { nonce: felt!("0x1"), class_hash: ADDR_2_CLASS_HASH_AT_1 }, + ), + ]); + + InMemoryState { + storage: storage.into(), + contract_state: contract_state.into(), + ..Default::default() + } + } + + #[test] + fn latest_state_provider() { + let state = create_mock_state(); + + let mut provider = create_mock_provider(); + provider.state = Arc::new(state); + + let latest_state_provider = StateFactoryProvider::latest(&provider).unwrap(); + + assert_eq!(latest_state_provider.nonce(ADDR_1).unwrap(), Some(felt!("0x1"))); + assert_eq!( + latest_state_provider.storage(ADDR_1, STORAGE_KEY).unwrap(), + Some(ADDR_1_STORAGE_VALUE_AT_1) + ); + assert_eq!( + latest_state_provider.class_hash_of_contract(ADDR_1).unwrap(), + Some(felt!("0x4337")) + ); + + assert_eq!(latest_state_provider.nonce(ADDR_2).unwrap(), Some(felt!("0x1"))); + assert_eq!( + latest_state_provider.storage(ADDR_2, STORAGE_KEY).unwrap(), + Some(ADDR_2_STORAGE_VALUE_AT_1) + ); + assert_eq!( + latest_state_provider.class_hash_of_contract(ADDR_2).unwrap(), + Some(felt!("0x1559")) + ); + } + + #[test] + fn historical_state_provider() { + // setup + + let state = create_mock_state(); + let snapshot = state.create_snapshot(); + + state.storage.write().extend([ + ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_2), + ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_2), + ]); + + state.contract_state.write().extend([ + ( + ADDR_1, + GenericContractInfo { + nonce: ADDR_1_NONCE_AT_2, + class_hash: ADDR_1_CLASS_HASH_AT_2, + }, + ), + ( + ADDR_2, + GenericContractInfo { + nonce: ADDR_2_NONCE_AT_2, + class_hash: ADDR_2_CLASS_HASH_AT_2, + }, + ), + ]); + + let mut provider = create_mock_provider(); + provider.state = Arc::new(state); + provider.historical_states.write().insert(1, snapshot); + + // check latest state + + let latest_state_provider = StateFactoryProvider::latest(&provider).unwrap(); + + assert_eq!(latest_state_provider.nonce(ADDR_1).unwrap(), Some(ADDR_1_NONCE_AT_2)); + assert_eq!( + latest_state_provider.storage(ADDR_1, STORAGE_KEY).unwrap(), + Some(ADDR_1_STORAGE_VALUE_AT_2) + ); + assert_eq!( + latest_state_provider.class_hash_of_contract(ADDR_1).unwrap(), + Some(ADDR_1_CLASS_HASH_AT_2) + ); + + assert_eq!(latest_state_provider.nonce(ADDR_2).unwrap(), Some(ADDR_2_NONCE_AT_2)); + assert_eq!( + latest_state_provider.storage(ADDR_2, STORAGE_KEY).unwrap(), + Some(ADDR_2_STORAGE_VALUE_AT_2) + ); + assert_eq!( + latest_state_provider.class_hash_of_contract(ADDR_2).unwrap(), + Some(ADDR_2_CLASS_HASH_AT_2) + ); + + // check historical state + + let historical_state_provider = + StateFactoryProvider::historical(&provider, BlockHashOrNumber::Num(1)) + .unwrap() + .unwrap(); + + assert_eq!(historical_state_provider.nonce(ADDR_1).unwrap(), Some(ADDR_1_NONCE_AT_1)); + assert_eq!( + historical_state_provider.storage(ADDR_1, STORAGE_KEY).unwrap(), + Some(ADDR_1_STORAGE_VALUE_AT_1) + ); + assert_eq!( + historical_state_provider.class_hash_of_contract(ADDR_1).unwrap(), + Some(ADDR_1_CLASS_HASH_AT_1) + ); + + assert_eq!(historical_state_provider.nonce(ADDR_2).unwrap(), Some(ADDR_2_NONCE_AT_1)); + assert_eq!( + historical_state_provider.storage(ADDR_2, STORAGE_KEY).unwrap(), + Some(ADDR_2_STORAGE_VALUE_AT_1) + ); + assert_eq!( + historical_state_provider.class_hash_of_contract(ADDR_2).unwrap(), + Some(ADDR_2_CLASS_HASH_AT_1) + ); + } +} diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 85f6fb9aaa..03c7746fc6 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -5,7 +5,8 @@ use katana_primitives::contract::{ StorageKey, StorageValue, }; -pub trait StateProvider: StateProviderExt { +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateProvider { /// Returns the compiled class definition of a contract class given its class hash. fn class(&self, hash: ClassHash) -> Result>; @@ -21,13 +22,11 @@ pub trait StateProvider: StateProviderExt { /// Returns the class hash of a contract. fn class_hash_of_contract(&self, address: ContractAddress) -> Result>; - - /// Returns the compiled contract class for the given class hash. - fn compiled_contract_class(&self, hash: ClassHash) -> Result>; } /// An extension of the `StateProvider` trait which provides additional methods. -pub trait StateProviderExt { +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateProviderExt: StateProvider { /// Retrieves the Sierra class definition of a contract class given its class hash. fn sierra_class(&self, hash: ClassHash) -> Result>; From 4bcb86edef2d4eb98a85c77fe00214cf08450130 Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 23 Nov 2023 12:27:19 +0800 Subject: [PATCH 079/192] refactor(katana-provider): mark traits as send + sync --- crates/katana/storage/provider/src/traits/block.rs | 10 +++++----- crates/katana/storage/provider/src/traits/contract.rs | 2 +- crates/katana/storage/provider/src/traits/state.rs | 4 ++-- .../katana/storage/provider/src/traits/state_update.rs | 2 +- .../katana/storage/provider/src/traits/transaction.rs | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/katana/storage/provider/src/traits/block.rs b/crates/katana/storage/provider/src/traits/block.rs index fcfb1c6029..8a299c0257 100644 --- a/crates/katana/storage/provider/src/traits/block.rs +++ b/crates/katana/storage/provider/src/traits/block.rs @@ -5,7 +5,7 @@ use katana_primitives::block::{Block, BlockHash, BlockHashOrNumber, BlockNumber, use super::transaction::TransactionProvider; -pub trait BlockHashProvider { +pub trait BlockHashProvider: Send + Sync { /// Retrieves the latest block hash. /// /// There should always be at least one block (genesis) in the chain. @@ -15,7 +15,7 @@ pub trait BlockHashProvider { fn block_hash_by_num(&self, num: BlockNumber) -> Result>; } -pub trait BlockNumberProvider { +pub trait BlockNumberProvider: Send + Sync { /// Retrieves the latest block number. /// /// There should always be at least one block (genesis) in the chain. @@ -25,7 +25,7 @@ pub trait BlockNumberProvider { fn block_number_by_hash(&self, hash: BlockHash) -> Result>; } -pub trait HeaderProvider { +pub trait HeaderProvider: Send + Sync { /// Retrieves the latest header by its block id. fn header(&self, id: BlockHashOrNumber) -> Result>; @@ -39,7 +39,7 @@ pub trait HeaderProvider { } pub trait BlockProvider: - BlockHashProvider + BlockNumberProvider + HeaderProvider + TransactionProvider + BlockHashProvider + BlockNumberProvider + HeaderProvider + TransactionProvider + Send + Sync { /// Returns a block by its id. fn block(&self, id: BlockHashOrNumber) -> Result>; @@ -58,7 +58,7 @@ pub trait BlockProvider: } } -pub trait BlockExecutionWriter { +pub trait BlockExecutionWriter: Send + Sync { /// Store an executed block along with its output to the storage. fn store_block(&mut self, block: Block) -> Result<()>; } diff --git a/crates/katana/storage/provider/src/traits/contract.rs b/crates/katana/storage/provider/src/traits/contract.rs index 8ea44326ea..6fad0bf64d 100644 --- a/crates/katana/storage/provider/src/traits/contract.rs +++ b/crates/katana/storage/provider/src/traits/contract.rs @@ -1,7 +1,7 @@ use anyhow::Result; use katana_primitives::contract::{ContractAddress, GenericContractInfo}; -pub trait ContractProvider { +pub trait ContractProvider: Send + Sync { /// Returns the contract information given its address. fn contract(&self, address: ContractAddress) -> Result>; } diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 03c7746fc6..3769bdcb90 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -6,7 +6,7 @@ use katana_primitives::contract::{ }; #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateProvider { +pub trait StateProvider: Send + Sync { /// Returns the compiled class definition of a contract class given its class hash. fn class(&self, hash: ClassHash) -> Result>; @@ -26,7 +26,7 @@ pub trait StateProvider { /// An extension of the `StateProvider` trait which provides additional methods. #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateProviderExt: StateProvider { +pub trait StateProviderExt: StateProvider + Send + Sync { /// Retrieves the Sierra class definition of a contract class given its class hash. fn sierra_class(&self, hash: ClassHash) -> Result>; diff --git a/crates/katana/storage/provider/src/traits/state_update.rs b/crates/katana/storage/provider/src/traits/state_update.rs index 04f4eaf772..86fe0d709f 100644 --- a/crates/katana/storage/provider/src/traits/state_update.rs +++ b/crates/katana/storage/provider/src/traits/state_update.rs @@ -1,7 +1,7 @@ use anyhow::Result; use katana_primitives::block::{BlockHashOrNumber, StateUpdate}; -pub trait StateUpdateProvider { +pub trait StateUpdateProvider: Send + Sync { /// Returns the state update for the given block. fn state_update(&self, block_id: BlockHashOrNumber) -> Result>; } diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index f24a021d75..5936b16bf2 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -4,7 +4,7 @@ use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; -pub trait TransactionProvider { +pub trait TransactionProvider: Send + Sync { /// Returns a transaction given its hash. fn transaction_by_hash(&self, hash: TxHash) -> Result>; @@ -22,12 +22,12 @@ pub trait TransactionProvider { ) -> Result>; } -pub trait TransactionsProviderExt { +pub trait TransactionsProviderExt: TransactionProvider + Send + Sync { /// Retrieves the tx hashes for the given range of tx numbers. fn transaction_hashes_by_range(&self, range: Range) -> Result>; } -pub trait ReceiptProvider { +pub trait ReceiptProvider: Send + Sync { /// Returns the transaction receipt given a transaction hash. fn receipt_by_hash(&self, hash: TxHash) -> Result>; From 01e1a4d863a2705974a4feae8692599ed5e0a045 Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 23 Nov 2023 12:33:35 +0800 Subject: [PATCH 080/192] Pin `reth-libmdbx` to exact version --- crates/katana/storage/db/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 1e9f84be70..f3604a95bc 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -11,7 +11,7 @@ anyhow.workspace = true bincode = "1.3.3" flate2.workspace = true katana-primitives = { path = "../../primitives" } -libmdbx = { git = "https://github.com/paradigmxyz/reth.git", version = "0.1.0-alpha.10", package = "reth-libmdbx" } +libmdbx = { git = "https://github.com/paradigmxyz/reth.git", version = "=0.1.0-alpha.10", package = "reth-libmdbx" } page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true From 1acc742d851c6b1c368588ae5fbd9e7540df2193 Mon Sep 17 00:00:00 2001 From: Kariy Date: Thu, 23 Nov 2023 21:18:26 +0800 Subject: [PATCH 081/192] refactor(katana-primitives): revert receipt types --- crates/katana/primitives/src/transaction.rs | 27 ++++++--------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index faac27467c..a3dd9df5dc 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -120,30 +120,17 @@ pub type ExecutionResources = HashMap; /// The receipt of a transaction containing the outputs of its execution. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Receipt { - Invoke(CommonReceipt), - Declare(CommonReceipt), - L1Handler(CommonReceipt), - DeployAccount(DeployAccountReceipt), -} - -/// Commong transaction receipt fields. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct CommonReceipt { +pub struct Receipt { + /// Actual fee paid for the transaction. pub actual_fee: u128, + /// Events emitted by contracts. pub events: Vec, + /// Messages sent to L1. pub messages_sent: Vec, + /// Revert error message if the transaction execution failed. pub revert_error: Option, + /// The execution resources used by the transaction. pub actual_resources: ExecutionResources, + /// Contract address if the transaction deployed a contract. (only for deploy account tx) pub contract_address: Option, } - -/// Commong transaction receipt fields. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeployAccountReceipt { - #[cfg_attr(feature = "serde", serde(flatten))] - pub common: CommonReceipt, - pub contract_adddress: ContractAddress, -} From 4724ea77ff3f328acd4c9d178a342f0402a30d5e Mon Sep 17 00:00:00 2001 From: Kariy Date: Fri, 24 Nov 2023 02:22:52 +0800 Subject: [PATCH 082/192] Initial forked storage provider --- Cargo.lock | 4 + crates/katana/storage/provider/Cargo.toml | 10 +- .../provider/src/providers/fork/backend.rs | 370 ++++++++++++++++++ .../storage/provider/src/providers/fork/db.rs | 285 ++++++++++++++ .../provider/src/providers/fork/mod.rs | 2 + .../storage/provider/src/providers/mod.rs | 2 + 6 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 crates/katana/storage/provider/src/providers/fork/backend.rs create mode 100644 crates/katana/storage/provider/src/providers/fork/db.rs create mode 100644 crates/katana/storage/provider/src/providers/fork/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ac7b7f9e85..45b4b7c333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5149,10 +5149,14 @@ version = "0.3.14" dependencies = [ "anyhow", "auto_impl", + "futures", "katana-db", "katana-primitives", "parking_lot 0.12.1", "starknet", + "thiserror", + "tokio", + "tracing", ] [[package]] diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index 7bf3aa866b..5352f6e516 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -13,9 +13,17 @@ katana-primitives = { path = "../../primitives" } anyhow.workspace = true auto_impl = "1.1.0" parking_lot.workspace = true +thiserror.workspace = true +tracing.workspace = true + +# fork provider deps +futures.workspace = true +starknet.workspace = true +tokio.workspace = true [features] -default = [ "in_memory" ] +default = [ "fork", "in_memory" ] +fork = [ ] in_memory = [ ] [dev-dependencies] diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs new file mode 100644 index 0000000000..63296e1b72 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -0,0 +1,370 @@ +use std::collections::VecDeque; +use std::pin::Pin; +use std::sync::mpsc::{channel as oneshot, Sender as OneshotSender}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::thread; + +use anyhow::Result; +use futures::channel::mpsc::{channel, Receiver, Sender, TrySendError}; +use futures::future::BoxFuture; +use futures::stream::Stream; +use futures::{Future, FutureExt}; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, ContractAddress, Nonce, StorageKey, StorageValue, +}; +use katana_primitives::conversion::rpc::{ + compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, +}; +use katana_primitives::FieldElement; +use parking_lot::Mutex; +use starknet::core::types::BlockId; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider, ProviderError}; +use tracing::trace; + +use crate::traits::state::{StateProvider, StateProviderExt}; + +type GetNonceResult = Result; +type GetStorageResult = Result; +type GetClassHashAtResult = Result; +type GetClassAtResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum ForkedBackendError { + #[error(transparent)] + Send(TrySendError), + #[error("Compute class hash error: {0}")] + ComputeClassHashError(String), + #[error(transparent)] + Provider(ProviderError), +} + +pub enum BackendRequest { + GetClassAt(ClassHash, OneshotSender), + GetNonce(ContractAddress, OneshotSender), + GetClassHashAt(ContractAddress, OneshotSender), + GetStorage(ContractAddress, StorageKey, OneshotSender), +} + +type BackendRequestFuture = BoxFuture<'static, ()>; + +/// A thread-safe handler for the shared forked backend. This handler is responsible for receiving +/// requests from all instances of the [ForkedBackend], process them, and returns the results back +/// to the request sender. +pub struct ForkedBackend { + provider: Arc>, + /// Requests that are currently being poll. + pending_requests: Vec, + /// Requests that are queued to be polled. + queued_requests: VecDeque, + /// A channel for receiving requests from the [ForkedBackend]'s. + incoming: Receiver, + /// Pinned block id for all requests. + block: BlockId, +} + +impl ForkedBackend { + /// This function is responsible for transforming the incoming request + /// into a future that will be polled until completion by the `BackendHandler`. + /// + /// Each request is accompanied by the sender-half of a oneshot channel that will be used + /// to send the result back to the [ForkedBackend] which sent the requests. + fn handle_requests(&mut self, request: BackendRequest) { + let block = self.block; + let provider = self.provider.clone(); + + match request { + BackendRequest::GetNonce(contract_address, sender) => { + let fut = Box::pin(async move { + let res = provider + .get_nonce(block, Into::::into(contract_address)) + .await + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send nonce result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetStorage(contract_address, key, sender) => { + let fut = Box::pin(async move { + let res = provider + .get_storage_at(Into::::into(contract_address), key, block) + .await + .map(|f| f.into()) + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send storage result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetClassHashAt(contract_address, sender) => { + let fut = Box::pin(async move { + let res = provider + .get_class_hash_at(block, Into::::into(contract_address)) + .await + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send class hash result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetClassAt(class_hash, sender) => { + let fut = Box::pin(async move { + let res = provider + .get_class(block, class_hash) + .await + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send class result") + }); + + self.pending_requests.push(fut); + } + } + } +} + +impl Future for ForkedBackend { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pin = self.get_mut(); + loop { + // convert all queued requests into futures to be polled + while let Some(req) = pin.queued_requests.pop_front() { + pin.handle_requests(req); + } + + loop { + match Pin::new(&mut pin.incoming).poll_next(cx) { + Poll::Ready(Some(req)) => { + pin.queued_requests.push_back(req); + } + // Resolve if stream is exhausted. + Poll::Ready(None) => { + return Poll::Ready(()); + } + Poll::Pending => { + break; + } + } + } + + // poll all pending requests + for n in (0..pin.pending_requests.len()).rev() { + let mut fut = pin.pending_requests.swap_remove(n); + // poll the future and if the future is still pending, push it back to the + // pending requests so that it will be polled again + if fut.poll_unpin(cx).is_pending() { + pin.pending_requests.push(fut); + } + } + + // if no queued requests, then yield + if pin.queued_requests.is_empty() { + return Poll::Pending; + } + } + } +} + +/// Handler for the [`ForkedBackend`]. +#[derive(Debug)] +pub struct ForkedBackendHandler(Mutex>); + +impl Clone for ForkedBackendHandler { + fn clone(&self) -> Self { + Self(Mutex::new(self.0.lock().clone())) + } +} + +impl ForkedBackendHandler { + pub fn new_with_backend_thread( + provider: Arc>, + block_id: BlockHashOrNumber, + ) -> Self { + let (backend, handler) = Self::new(provider, block_id); + + thread::Builder::new() + .spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to create fork backend thread tokio runtime") + .block_on(handler); + }) + .expect("failed to spawn fork backend thread"); + + trace!(target: "forked_backend", "fork backend thread spawned"); + + backend + } + + fn new( + provider: Arc>, + block_id: BlockHashOrNumber, + ) -> (Self, ForkedBackend) { + let block = match block_id { + BlockHashOrNumber::Hash(hash) => BlockId::Hash(hash), + BlockHashOrNumber::Num(number) => BlockId::Number(number), + }; + + let (sender, rx) = channel(1); + let backend = ForkedBackend { + incoming: rx, + provider, + block, + queued_requests: VecDeque::new(), + pending_requests: Vec::new(), + }; + + (Self(Mutex::new(sender)), backend) + } + + pub fn do_get_nonce( + &self, + contract_address: ContractAddress, + ) -> Result { + trace!(target: "forked_backend", "request nonce for contract address {contract_address}"); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetNonce(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive nonce result") + }) + } + + pub fn do_get_storage( + &self, + contract_address: ContractAddress, + key: StorageKey, + ) -> Result { + trace!(target: "forked_backend", "request storage for address {contract_address} at key {key:#x}" ); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetStorage(contract_address, key, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive storage result") + }) + } + + pub fn do_get_class_hash_at( + &self, + contract_address: ContractAddress, + ) -> Result { + trace!(target: "forked_backend", "request class hash at address {contract_address}"); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class hash result") + }) + } + + pub fn do_get_class_at( + &self, + class_hash: ClassHash, + ) -> Result { + trace!(target: "forked_backend", "request class at hash {class_hash:#x}"); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetClassAt(class_hash, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class result") + }) + } + + pub fn do_get_compiled_class_hash( + &self, + class_hash: ClassHash, + ) -> Result { + trace!(target: "forked_backend", "request compiled class hash at class {class_hash:#x}"); + let class = self.do_get_class_at(class_hash)?; + // if its a legacy class, then we just return back the class hash + // else if sierra class, then we have to compile it and compute the compiled class hash. + match class { + starknet::core::types::ContractClass::Legacy(_) => Ok(class_hash), + + starknet::core::types::ContractClass::Sierra(sierra_class) => { + tokio::task::block_in_place(|| { + compiled_class_hash_from_flattened_sierra_class(&sierra_class) + }) + .map_err(|e| ForkedBackendError::ComputeClassHashError(e.to_string())) + } + } + } +} + +// impl StateProvider for ForkedBackendHandler { +// fn nonce(&self, address: ContractAddress) -> Result> { +// let nonce = self.do_get_nonce(address).unwrap(); +// Ok(Some(nonce)) +// } + +// fn class( +// &self, +// hash: ClassHash, +// ) -> Result> { +// let class = self.do_get_class_at(hash).unwrap(); +// match class { +// starknet::core::types::ContractClass::Legacy(legacy_class) => { +// Ok(Some(legacy_rpc_to_inner_class(&legacy_class).map(|(_, class)| class)?)) +// } + +// starknet::core::types::ContractClass::Sierra(sierra_class) => { +// Ok(Some(rpc_to_inner_class(&sierra_class).map(|(_, class)| class)?)) +// } +// } +// } + +// fn storage( +// &self, +// address: ContractAddress, +// storage_key: StorageKey, +// ) -> Result> { +// let storage = self.do_get_storage(address, storage_key).unwrap(); +// Ok(Some(storage)) +// } + +// fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { +// let class_hash = self.do_get_class_hash_at(address).unwrap(); +// Ok(Some(class_hash)) +// } +// } + +// impl StateProviderExt for ForkedBackendHandler { +// fn sierra_class( +// &self, +// hash: ClassHash, +// ) -> Result> { +// let class = self.do_get_class_at(hash).unwrap(); +// match class { +// starknet::core::types::ContractClass::Sierra(sierra_class) => Ok(Some(sierra_class)), +// starknet::core::types::ContractClass::Legacy(_) => Ok(None), +// } +// } + +// fn compiled_class_hash_of_class_hash( +// &self, +// hash: ClassHash, +// ) -> Result> { +// let hash = self.do_get_compiled_class_hash(hash).unwrap(); +// Ok(Some(hash)) +// } +// } diff --git a/crates/katana/storage/provider/src/providers/fork/db.rs b/crates/katana/storage/provider/src/providers/fork/db.rs new file mode 100644 index 0000000000..f8033b5584 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/fork/db.rs @@ -0,0 +1,285 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use katana_primitives::block::BlockHashOrNumber; +// use blockifier::execution::contract_class::ContractClass; +// use blockifier::state::cached_state::CommitmentStateDiff; +// use blockifier::state::state_api::{State, StateReader, StateResult}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; + +use super::backend::ForkedBackendHandler; +use crate::providers::in_memory::InMemoryProvider; +// use crate::db::cached::{AsCachedDb, CachedDb, MaybeAsCachedDb}; +// use crate::db::serde::state::{ +// SerializableClassRecord, SerializableState, SerializableStorageRecord, +// }; +// use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; + +/// A state database implementation that forks from a network. +/// +/// It will try to find the requested data in the cache, and if it's not there, it will fetch it +/// from the forked network. The fetched data will be stored in the cache so that the next time the +/// same data is requested, it will be fetched from the cache instead of fetching it from the forked +/// network again. +/// +/// The forked database provider should be locked to a particular block. +pub struct ForkedProvider { + /// Cache + storage: InMemoryProvider, + /// Shared cache of the forked database. This will be shared across all instances of the + /// `ForkedDb` when it is cloned into a [StateRefDb] using the [AsStateRefDb] trait. + /// + /// So if one instance fetches data from the forked network, the + /// other instances will be able to use the cached data instead of fetching it again. + backend: Arc, +} + +impl ForkedProvider { + /// Construct a new `ForkedDb` from a `Provider` of the network to fork from at a particular + /// `block`. + pub fn new(provider: Arc>, block: BlockHashOrNumber) -> Self { + Self { + storage: InMemoryProvider::new(), + backend: Arc::new(ForkedBackendHandler::new_with_backend_thread(provider, block)), + } + } +} + +// impl State for ForkedProvider { +// fn set_storage_at( +// &mut self, +// contract_address: ContractAddress, +// key: StorageKey, +// value: StarkFelt, +// ) { +// self.db.set_storage_at(contract_address, key, value); +// } + +// fn set_class_hash_at( +// &mut self, +// contract_address: ContractAddress, +// class_hash: ClassHash, +// ) -> StateResult<()> { +// self.db.set_class_hash_at(contract_address, class_hash) +// } + +// fn set_compiled_class_hash( +// &mut self, +// class_hash: ClassHash, +// compiled_class_hash: CompiledClassHash, +// ) -> StateResult<()> { +// self.db.set_compiled_class_hash(class_hash, compiled_class_hash) +// } + +// fn to_state_diff(&self) -> CommitmentStateDiff { +// self.db.to_state_diff() +// } + +// fn set_contract_class( +// &mut self, +// class_hash: &ClassHash, +// contract_class: ContractClass, +// ) -> StateResult<()> { +// self.db.set_contract_class(class_hash, contract_class) +// } + +// fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { +// self.db.increment_nonce(contract_address) +// } +// } + +// impl StateReader for ForkedProvider { +// fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { +// self.db.get_nonce_at(contract_address) +// } + +// fn get_storage_at( +// &mut self, +// contract_address: ContractAddress, +// key: StorageKey, +// ) -> StateResult { +// self.db.get_storage_at(contract_address, key) +// } + +// fn get_class_hash_at( +// &mut self, +// contract_address: ContractAddress, +// ) -> StateResult { +// self.db.get_class_hash_at(contract_address) +// } + +// fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> +// StateResult { self.db.get_compiled_class_hash(class_hash) +// } + +// fn get_compiled_contract_class( +// &mut self, +// class_hash: &ClassHash, +// ) -> StateResult { +// self.db.get_compiled_contract_class(class_hash) +// } +// } + +// impl StateExtRef for ForkedProvider { +// fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { +// self.db.get_sierra_class(class_hash) +// } +// } + +// impl StateExt for ForkedProvider { +// fn set_sierra_class( +// &mut self, +// class_hash: ClassHash, +// sierra_class: FlattenedSierraClass, +// ) -> StateResult<()> { +// self.db.set_sierra_class(class_hash, sierra_class) +// } +// } + +// impl AsStateRefDb for ForkedProvider { +// fn as_ref_db(&self) -> StateRefDb { +// StateRefDb::new(self.clone()) +// } +// } + +// impl MaybeAsCachedDb for ForkedProvider { +// fn maybe_as_cached_db(&self) -> Option { +// Some(CachedDb { +// db: (), +// classes: self.db.classes.clone(), +// storage: self.db.storage.clone(), +// contracts: self.db.contracts.clone(), +// sierra_classes: self.db.sierra_classes.clone(), +// }) +// } +// } + +// impl Database for ForkedProvider { +// fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { +// self.db.storage.entry(addr).or_default().nonce = nonce; +// } + +// fn dump_state(&self) -> anyhow::Result { +// let mut serializable = SerializableState::default(); + +// self.db.storage.iter().for_each(|(addr, storage)| { +// let mut record = SerializableStorageRecord { +// storage: BTreeMap::new(), +// nonce: storage.nonce.0.into(), +// }; + +// storage.storage.iter().for_each(|(key, value)| { +// record.storage.insert((*key.0.key()).into(), (*value).into()); +// }); + +// serializable.storage.insert((*addr.0.key()).into(), record); +// }); + +// self.db.classes.iter().for_each(|(class_hash, class_record)| { +// serializable.classes.insert( +// class_hash.0.into(), +// SerializableClassRecord { +// class: class_record.class.clone().into(), +// compiled_hash: class_record.compiled_hash.0.into(), +// }, +// ); +// }); + +// self.db.contracts.iter().for_each(|(address, class_hash)| { +// serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); +// }); + +// self.db.sierra_classes.iter().for_each(|(class_hash, class)| { +// serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); +// }); + +// Ok(serializable) +// } +// } + +// #[cfg(test)] +// mod tests { +// use starknet::core::types::BlockTag; +// use starknet::providers::jsonrpc::HttpTransport; +// use starknet::providers::JsonRpcClient; +// use starknet_api::core::PatriciaKey; +// use starknet_api::hash::StarkHash; +// use starknet_api::{patricia_key, stark_felt}; +// use url::Url; + +// use super::*; +// use crate::constants::UDC_CONTRACT; + +// const FORKED_ENDPOINT: &str = +// "https://starknet-goerli.infura.io/v3/369ce5ac40614952af936e4d64e40474"; + +// #[tokio::test] +// async fn fetch_from_cache_if_exist() { +// let address = ContractAddress(patricia_key!(0x1u32)); +// let class_hash = ClassHash(stark_felt!(0x88u32)); + +// let expected_nonce = Nonce(stark_felt!(44u32)); +// let expected_storage_key = StorageKey(patricia_key!(0x2u32)); +// let expected_storage_value = stark_felt!(55u32); +// let expected_compiled_class_hash = CompiledClassHash(class_hash.0); +// let expected_contract_class = (*UDC_CONTRACT).clone(); + +// let provider = +// JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); let mut +// cache = CachedDb::new(SharedBackend::new_with_backend_thread( Arc::new(provider), +// BlockId::Tag(BlockTag::Latest), +// )); + +// cache.storage.entry(address).or_default().nonce = expected_nonce; +// cache.set_storage_at(address, expected_storage_key, expected_storage_value); +// cache.set_contract_class(&class_hash, expected_contract_class.clone()).unwrap(); +// cache.set_compiled_class_hash(class_hash, expected_compiled_class_hash).unwrap(); + +// let mut db = ForkedProvider::new_from_backend(cache); + +// let nonce = db.get_nonce_at(address).unwrap(); +// let storage_value = db.get_storage_at(address, expected_storage_key).unwrap(); +// let contract_class = db.get_compiled_contract_class(&class_hash).unwrap(); +// let compiled_class_hash = db.get_compiled_class_hash(class_hash).unwrap(); + +// assert_eq!(nonce, expected_nonce); +// assert_eq!(storage_value, expected_storage_value); +// assert_eq!(contract_class, expected_contract_class); +// assert_eq!(compiled_class_hash, expected_compiled_class_hash) +// } + +// #[tokio::test(flavor = "multi_thread")] +// #[ignore] +// async fn fetch_from_provider_if_not_in_cache() { +// let provider = +// JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); let mut db +// = ForkedProvider::new(Arc::new(provider), BlockId::Tag(BlockTag::Latest)); + +// let address = ContractAddress(patricia_key!( +// "0x02b92ec12cA1e308f320e99364d4dd8fcc9efDAc574F836C8908de937C289974" +// )); +// let storage_key = StorageKey(patricia_key!( +// "0x3b459c3fadecdb1a501f2fdeec06fd735cb2d93ea59779177a0981660a85352" +// )); + +// let class_hash = db.get_class_hash_at(address).unwrap(); +// let class = db.get_compiled_contract_class(&class_hash).unwrap(); +// let storage_value = db.get_storage_at(address, storage_key).unwrap(); + +// let expected_class_hash = ClassHash(stark_felt!( +// "0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003" +// )); + +// assert_eq!(class_hash, expected_class_hash); + +// let class_hash_in_cache = *db.db.contracts.get(&address).unwrap(); +// let class_in_cache = db.db.classes.get(&class_hash).unwrap().class.clone(); +// let storage_value_in_cache = +// *db.db.storage.get(&address).unwrap().storage.get(&storage_key).unwrap(); + +// assert_eq!(class_in_cache, class, "class must be stored in cache"); +// assert_eq!(class_hash_in_cache, expected_class_hash, "class hash must be stored in +// cache"); assert_eq!(storage_value_in_cache, storage_value, "storage value must be stored +// in cache"); } +// } diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs new file mode 100644 index 0000000000..b1a4440c59 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -0,0 +1,2 @@ +pub mod backend; +pub mod db; diff --git a/crates/katana/storage/provider/src/providers/mod.rs b/crates/katana/storage/provider/src/providers/mod.rs index 028e4780b0..58d9a050ca 100644 --- a/crates/katana/storage/provider/src/providers/mod.rs +++ b/crates/katana/storage/provider/src/providers/mod.rs @@ -1,2 +1,4 @@ +#[cfg(feature = "fork")] +pub mod fork; #[cfg(feature = "in_memory")] pub mod in_memory; From 8296e82d826480d5da887ee6453769f326690b87 Mon Sep 17 00:00:00 2001 From: Kariy Date: Sat, 25 Nov 2023 14:44:48 +0800 Subject: [PATCH 083/192] refactor(katana-provider): update in-memory and forked provider --- crates/katana/primitives/src/contract.rs | 2 +- .../katana/primitives/src/conversion/rpc.rs | 22 +- .../provider/src/providers/fork/backend.rs | 209 ++++++++----- .../storage/provider/src/providers/fork/db.rs | 285 ------------------ .../provider/src/providers/fork/mod.rs | 257 +++++++++++++++- .../provider/src/providers/fork/state.rs | 169 +++++++++++ .../provider/src/providers/in_memory/cache.rs | 110 +++++++ .../provider/src/providers/in_memory/mod.rs | 103 +++---- .../provider/src/providers/in_memory/state.rs | 118 ++++---- 9 files changed, 786 insertions(+), 489 deletions(-) delete mode 100644 crates/katana/storage/provider/src/providers/fork/db.rs create mode 100644 crates/katana/storage/provider/src/providers/fork/state.rs create mode 100644 crates/katana/storage/provider/src/providers/in_memory/cache.rs diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 68407c856b..de5ab62c3f 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -49,7 +49,7 @@ impl From for FieldElement { } /// Represents a generic contract instance information. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GenericContractInfo { /// The nonce of the contract. diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs index 50aa4d9d0d..650e703633 100644 --- a/crates/katana/primitives/src/conversion/rpc.rs +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -17,7 +17,7 @@ use starknet::core::utils::get_contract_address; use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; use self::primitives::{ContractAddress, InvokeTxV1}; -use crate::contract::{CompiledContractClass, SierraClass}; +use crate::contract::{ClassHash, CompiledClassHash, CompiledContractClass, SierraClass}; use crate::utils::transaction::{ compute_declare_v1_transaction_hash, compute_declare_v2_transaction_hash, compute_deploy_account_v1_transaction_hash, compute_invoke_v1_transaction_hash, @@ -129,7 +129,7 @@ impl primitives::DeclareTx { } BroadcastedDeclareTransaction::V2(tx) => { - let (class_hash, contract_class) = + let (class_hash, _, contract_class) = rpc_to_inner_class(&tx.contract_class).expect("valid contract class"); let transaction_hash = compute_declare_v2_transaction_hash( @@ -179,11 +179,21 @@ pub fn legacy_inner_to_rpc_class(legacy_contract_class: ContractClassV0) -> Resu pub fn rpc_to_inner_class( contract_class: &SierraClass, -) -> Result<(FieldElement, CompiledContractClass)> { +) -> Result<(ClassHash, CompiledClassHash, CompiledContractClass)> { let class_hash = contract_class.class_hash(); + let contract_class = rpc_to_cairo_contract_class(contract_class)?; let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; - Ok((class_hash, CompiledContractClass::V1(casm_contract.try_into()?))) + + // compute compiled class hash + let res = serde_json::to_string(&casm_contract)?; + let compiled_class: CompiledClass = serde_json::from_str(&res)?; + + Ok(( + class_hash, + compiled_class.class_hash()?, + CompiledContractClass::V1(casm_contract.try_into()?), + )) } /// Converts `starknet-rs` RPC [SierraClass] type to Cairo's @@ -211,14 +221,14 @@ pub fn compiled_class_hash_from_flattened_sierra_class( ) -> Result { let contract_class = rpc_to_cairo_contract_class(contract_class)?; let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; - let res = serde_json::to_string_pretty(&casm_contract)?; + let res = serde_json::to_string(&casm_contract)?; let compiled_class: CompiledClass = serde_json::from_str(&res)?; Ok(compiled_class.class_hash()?) } pub fn legacy_rpc_to_inner_class( compressed_legacy_contract: &CompressedLegacyContractClass, -) -> Result<(FieldElement, CompiledContractClass)> { +) -> Result<(ClassHash, CompiledContractClass)> { let legacy_program_json = decompress(&compressed_legacy_contract.program)?; let legacy_program: LegacyProgram = serde_json::from_str(&legacy_program_json)?; diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 63296e1b72..739b634a7e 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -12,18 +12,20 @@ use futures::stream::Stream; use futures::{Future, FutureExt}; use katana_primitives::block::BlockHashOrNumber; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, ContractAddress, Nonce, StorageKey, StorageValue, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, + StorageKey, StorageValue, }; use katana_primitives::conversion::rpc::{ compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, }; use katana_primitives::FieldElement; use parking_lot::Mutex; -use starknet::core::types::BlockId; +use starknet::core::types::{BlockId, ContractClass}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider, ProviderError}; use tracing::trace; +use crate::providers::in_memory::cache::CacheStateDb; use crate::traits::state::{StateProvider, StateProviderExt}; type GetNonceResult = Result; @@ -50,10 +52,11 @@ pub enum BackendRequest { type BackendRequestFuture = BoxFuture<'static, ()>; -/// A thread-safe handler for the shared forked backend. This handler is responsible for receiving -/// requests from all instances of the [ForkedBackend], process them, and returns the results back -/// to the request sender. -pub struct ForkedBackend { +/// The backend for the forked provider. It processes all requests from the [ForkedBackend]'s +/// and sends the results back to it. +/// +/// It is responsible it fetching the data from the forked provider. +pub struct Backend { provider: Arc>, /// Requests that are currently being poll. pending_requests: Vec, @@ -65,7 +68,7 @@ pub struct ForkedBackend { block: BlockId, } -impl ForkedBackend { +impl Backend { /// This function is responsible for transforming the incoming request /// into a future that will be polled until completion by the `BackendHandler`. /// @@ -94,7 +97,6 @@ impl ForkedBackend { let res = provider .get_storage_at(Into::::into(contract_address), key, block) .await - .map(|f| f.into()) .map_err(ForkedBackendError::Provider); sender.send(res).expect("failed to send storage result") @@ -132,7 +134,7 @@ impl ForkedBackend { } } -impl Future for ForkedBackend { +impl Future for Backend { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -176,49 +178,49 @@ impl Future for ForkedBackend { } } -/// Handler for the [`ForkedBackend`]. -#[derive(Debug)] -pub struct ForkedBackendHandler(Mutex>); +/// A thread safe handler to the [`ForkedBackend`]. This is the primary interface for sending +/// request to the backend thread to fetch data from the forked provider. +pub struct ForkedBackend(Mutex>); -impl Clone for ForkedBackendHandler { +impl Clone for ForkedBackend { fn clone(&self) -> Self { Self(Mutex::new(self.0.lock().clone())) } } -impl ForkedBackendHandler { +impl ForkedBackend { pub fn new_with_backend_thread( provider: Arc>, block_id: BlockHashOrNumber, ) -> Self { - let (backend, handler) = Self::new(provider, block_id); + let (handler, backend) = Self::new(provider, block_id); thread::Builder::new() .spawn(move || { tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .expect("failed to create fork backend thread tokio runtime") - .block_on(handler); + .expect("failed to create tokio runtime") + .block_on(backend); }) .expect("failed to spawn fork backend thread"); trace!(target: "forked_backend", "fork backend thread spawned"); - backend + handler } fn new( provider: Arc>, block_id: BlockHashOrNumber, - ) -> (Self, ForkedBackend) { + ) -> (Self, Backend) { let block = match block_id { BlockHashOrNumber::Hash(hash) => BlockId::Hash(hash), BlockHashOrNumber::Num(number) => BlockId::Number(number), }; let (sender, rx) = channel(1); - let backend = ForkedBackend { + let backend = Backend { incoming: rx, provider, block, @@ -311,60 +313,113 @@ impl ForkedBackendHandler { } } -// impl StateProvider for ForkedBackendHandler { -// fn nonce(&self, address: ContractAddress) -> Result> { -// let nonce = self.do_get_nonce(address).unwrap(); -// Ok(Some(nonce)) -// } - -// fn class( -// &self, -// hash: ClassHash, -// ) -> Result> { -// let class = self.do_get_class_at(hash).unwrap(); -// match class { -// starknet::core::types::ContractClass::Legacy(legacy_class) => { -// Ok(Some(legacy_rpc_to_inner_class(&legacy_class).map(|(_, class)| class)?)) -// } - -// starknet::core::types::ContractClass::Sierra(sierra_class) => { -// Ok(Some(rpc_to_inner_class(&sierra_class).map(|(_, class)| class)?)) -// } -// } -// } - -// fn storage( -// &self, -// address: ContractAddress, -// storage_key: StorageKey, -// ) -> Result> { -// let storage = self.do_get_storage(address, storage_key).unwrap(); -// Ok(Some(storage)) -// } - -// fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { -// let class_hash = self.do_get_class_hash_at(address).unwrap(); -// Ok(Some(class_hash)) -// } -// } - -// impl StateProviderExt for ForkedBackendHandler { -// fn sierra_class( -// &self, -// hash: ClassHash, -// ) -> Result> { -// let class = self.do_get_class_at(hash).unwrap(); -// match class { -// starknet::core::types::ContractClass::Sierra(sierra_class) => Ok(Some(sierra_class)), -// starknet::core::types::ContractClass::Legacy(_) => Ok(None), -// } -// } - -// fn compiled_class_hash_of_class_hash( -// &self, -// hash: ClassHash, -// ) -> Result> { -// let hash = self.do_get_compiled_class_hash(hash).unwrap(); -// Ok(Some(hash)) -// } -// } +/// A shared cache that stores data fetched from the forked network. +/// +/// Check in cache first, if not found, then fetch from the forked provider and store it in the +/// cache to avoid fetching it again. This is shared across multiple instances of +/// [`ForkedStateDb`](super::state::ForkedStateDb). +#[derive(Clone)] +pub struct SharedStateProvider(Arc>); + +impl SharedStateProvider { + pub(crate) fn new_with_backend(backend: ForkedBackend) -> Self { + Self(Arc::new(CacheStateDb::new(backend))) + } +} + +impl StateProvider for SharedStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + if let Some(nonce) = self.0.contract_state.read().get(&address).map(|c| c.nonce) { + return Ok(Some(nonce)); + } + + let nonce = self.0.do_get_nonce(address).unwrap(); + self.0.contract_state.write().entry(address).or_default().nonce = nonce; + + Ok(Some(nonce)) + } + + fn class(&self, hash: ClassHash) -> Result> { + if let Some(class) = + self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned() + { + return Ok(Some(class)); + } + + let class = self.0.do_get_class_at(hash).unwrap(); + let (class_hash, compiled_class_hash, casm, sierra) = match class { + ContractClass::Legacy(class) => { + let (_, compiled_class) = legacy_rpc_to_inner_class(&class)?; + (hash, hash, compiled_class, None) + } + ContractClass::Sierra(sierra_class) => { + let (_, compiled_class_hash, compiled_class) = rpc_to_inner_class(&sierra_class)?; + (hash, compiled_class_hash, compiled_class, Some(sierra_class)) + } + }; + + self.0.compiled_class_hashes.write().insert(class_hash, compiled_class_hash); + + self.0 + .shared_contract_classes + .compiled_classes + .write() + .entry(class_hash) + .or_insert(casm.clone()); + + if let Some(sierra) = sierra { + self.0 + .shared_contract_classes + .sierra_classes + .write() + .entry(class_hash) + .or_insert(sierra); + } + + Ok(Some(casm)) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + if let Some(value) = self.0.storage.read().get(&(address, storage_key)).cloned() { + return Ok(Some(value)); + } + + let value = self.0.do_get_storage(address, storage_key).unwrap(); + self.0.storage.write().entry((address, storage_key)).or_insert(value); + + Ok(Some(value)) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + if let Some(hash) = self.0.contract_state.read().get(&address).map(|c| c.class_hash) { + return Ok(Some(hash)); + } + + let class_hash = self.0.do_get_class_hash_at(address).unwrap(); + self.0.contract_state.write().entry(address).or_default().class_hash = class_hash; + + Ok(Some(class_hash)) + } +} + +impl StateProviderExt for SharedStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.do_get_class_at(hash).unwrap(); + match class { + starknet::core::types::ContractClass::Legacy(_) => Ok(None), + starknet::core::types::ContractClass::Sierra(sierra_class) => Ok(Some(sierra_class)), + } + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + let hash = self.0.do_get_compiled_class_hash(hash).unwrap(); + Ok(Some(hash)) + } +} diff --git a/crates/katana/storage/provider/src/providers/fork/db.rs b/crates/katana/storage/provider/src/providers/fork/db.rs deleted file mode 100644 index f8033b5584..0000000000 --- a/crates/katana/storage/provider/src/providers/fork/db.rs +++ /dev/null @@ -1,285 +0,0 @@ -use std::collections::BTreeMap; -use std::sync::Arc; - -use katana_primitives::block::BlockHashOrNumber; -// use blockifier::execution::contract_class::ContractClass; -// use blockifier::state::cached_state::CommitmentStateDiff; -// use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::JsonRpcClient; - -use super::backend::ForkedBackendHandler; -use crate::providers::in_memory::InMemoryProvider; -// use crate::db::cached::{AsCachedDb, CachedDb, MaybeAsCachedDb}; -// use crate::db::serde::state::{ -// SerializableClassRecord, SerializableState, SerializableStorageRecord, -// }; -// use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; - -/// A state database implementation that forks from a network. -/// -/// It will try to find the requested data in the cache, and if it's not there, it will fetch it -/// from the forked network. The fetched data will be stored in the cache so that the next time the -/// same data is requested, it will be fetched from the cache instead of fetching it from the forked -/// network again. -/// -/// The forked database provider should be locked to a particular block. -pub struct ForkedProvider { - /// Cache - storage: InMemoryProvider, - /// Shared cache of the forked database. This will be shared across all instances of the - /// `ForkedDb` when it is cloned into a [StateRefDb] using the [AsStateRefDb] trait. - /// - /// So if one instance fetches data from the forked network, the - /// other instances will be able to use the cached data instead of fetching it again. - backend: Arc, -} - -impl ForkedProvider { - /// Construct a new `ForkedDb` from a `Provider` of the network to fork from at a particular - /// `block`. - pub fn new(provider: Arc>, block: BlockHashOrNumber) -> Self { - Self { - storage: InMemoryProvider::new(), - backend: Arc::new(ForkedBackendHandler::new_with_backend_thread(provider, block)), - } - } -} - -// impl State for ForkedProvider { -// fn set_storage_at( -// &mut self, -// contract_address: ContractAddress, -// key: StorageKey, -// value: StarkFelt, -// ) { -// self.db.set_storage_at(contract_address, key, value); -// } - -// fn set_class_hash_at( -// &mut self, -// contract_address: ContractAddress, -// class_hash: ClassHash, -// ) -> StateResult<()> { -// self.db.set_class_hash_at(contract_address, class_hash) -// } - -// fn set_compiled_class_hash( -// &mut self, -// class_hash: ClassHash, -// compiled_class_hash: CompiledClassHash, -// ) -> StateResult<()> { -// self.db.set_compiled_class_hash(class_hash, compiled_class_hash) -// } - -// fn to_state_diff(&self) -> CommitmentStateDiff { -// self.db.to_state_diff() -// } - -// fn set_contract_class( -// &mut self, -// class_hash: &ClassHash, -// contract_class: ContractClass, -// ) -> StateResult<()> { -// self.db.set_contract_class(class_hash, contract_class) -// } - -// fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { -// self.db.increment_nonce(contract_address) -// } -// } - -// impl StateReader for ForkedProvider { -// fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { -// self.db.get_nonce_at(contract_address) -// } - -// fn get_storage_at( -// &mut self, -// contract_address: ContractAddress, -// key: StorageKey, -// ) -> StateResult { -// self.db.get_storage_at(contract_address, key) -// } - -// fn get_class_hash_at( -// &mut self, -// contract_address: ContractAddress, -// ) -> StateResult { -// self.db.get_class_hash_at(contract_address) -// } - -// fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> -// StateResult { self.db.get_compiled_class_hash(class_hash) -// } - -// fn get_compiled_contract_class( -// &mut self, -// class_hash: &ClassHash, -// ) -> StateResult { -// self.db.get_compiled_contract_class(class_hash) -// } -// } - -// impl StateExtRef for ForkedProvider { -// fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { -// self.db.get_sierra_class(class_hash) -// } -// } - -// impl StateExt for ForkedProvider { -// fn set_sierra_class( -// &mut self, -// class_hash: ClassHash, -// sierra_class: FlattenedSierraClass, -// ) -> StateResult<()> { -// self.db.set_sierra_class(class_hash, sierra_class) -// } -// } - -// impl AsStateRefDb for ForkedProvider { -// fn as_ref_db(&self) -> StateRefDb { -// StateRefDb::new(self.clone()) -// } -// } - -// impl MaybeAsCachedDb for ForkedProvider { -// fn maybe_as_cached_db(&self) -> Option { -// Some(CachedDb { -// db: (), -// classes: self.db.classes.clone(), -// storage: self.db.storage.clone(), -// contracts: self.db.contracts.clone(), -// sierra_classes: self.db.sierra_classes.clone(), -// }) -// } -// } - -// impl Database for ForkedProvider { -// fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { -// self.db.storage.entry(addr).or_default().nonce = nonce; -// } - -// fn dump_state(&self) -> anyhow::Result { -// let mut serializable = SerializableState::default(); - -// self.db.storage.iter().for_each(|(addr, storage)| { -// let mut record = SerializableStorageRecord { -// storage: BTreeMap::new(), -// nonce: storage.nonce.0.into(), -// }; - -// storage.storage.iter().for_each(|(key, value)| { -// record.storage.insert((*key.0.key()).into(), (*value).into()); -// }); - -// serializable.storage.insert((*addr.0.key()).into(), record); -// }); - -// self.db.classes.iter().for_each(|(class_hash, class_record)| { -// serializable.classes.insert( -// class_hash.0.into(), -// SerializableClassRecord { -// class: class_record.class.clone().into(), -// compiled_hash: class_record.compiled_hash.0.into(), -// }, -// ); -// }); - -// self.db.contracts.iter().for_each(|(address, class_hash)| { -// serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); -// }); - -// self.db.sierra_classes.iter().for_each(|(class_hash, class)| { -// serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); -// }); - -// Ok(serializable) -// } -// } - -// #[cfg(test)] -// mod tests { -// use starknet::core::types::BlockTag; -// use starknet::providers::jsonrpc::HttpTransport; -// use starknet::providers::JsonRpcClient; -// use starknet_api::core::PatriciaKey; -// use starknet_api::hash::StarkHash; -// use starknet_api::{patricia_key, stark_felt}; -// use url::Url; - -// use super::*; -// use crate::constants::UDC_CONTRACT; - -// const FORKED_ENDPOINT: &str = -// "https://starknet-goerli.infura.io/v3/369ce5ac40614952af936e4d64e40474"; - -// #[tokio::test] -// async fn fetch_from_cache_if_exist() { -// let address = ContractAddress(patricia_key!(0x1u32)); -// let class_hash = ClassHash(stark_felt!(0x88u32)); - -// let expected_nonce = Nonce(stark_felt!(44u32)); -// let expected_storage_key = StorageKey(patricia_key!(0x2u32)); -// let expected_storage_value = stark_felt!(55u32); -// let expected_compiled_class_hash = CompiledClassHash(class_hash.0); -// let expected_contract_class = (*UDC_CONTRACT).clone(); - -// let provider = -// JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); let mut -// cache = CachedDb::new(SharedBackend::new_with_backend_thread( Arc::new(provider), -// BlockId::Tag(BlockTag::Latest), -// )); - -// cache.storage.entry(address).or_default().nonce = expected_nonce; -// cache.set_storage_at(address, expected_storage_key, expected_storage_value); -// cache.set_contract_class(&class_hash, expected_contract_class.clone()).unwrap(); -// cache.set_compiled_class_hash(class_hash, expected_compiled_class_hash).unwrap(); - -// let mut db = ForkedProvider::new_from_backend(cache); - -// let nonce = db.get_nonce_at(address).unwrap(); -// let storage_value = db.get_storage_at(address, expected_storage_key).unwrap(); -// let contract_class = db.get_compiled_contract_class(&class_hash).unwrap(); -// let compiled_class_hash = db.get_compiled_class_hash(class_hash).unwrap(); - -// assert_eq!(nonce, expected_nonce); -// assert_eq!(storage_value, expected_storage_value); -// assert_eq!(contract_class, expected_contract_class); -// assert_eq!(compiled_class_hash, expected_compiled_class_hash) -// } - -// #[tokio::test(flavor = "multi_thread")] -// #[ignore] -// async fn fetch_from_provider_if_not_in_cache() { -// let provider = -// JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); let mut db -// = ForkedProvider::new(Arc::new(provider), BlockId::Tag(BlockTag::Latest)); - -// let address = ContractAddress(patricia_key!( -// "0x02b92ec12cA1e308f320e99364d4dd8fcc9efDAc574F836C8908de937C289974" -// )); -// let storage_key = StorageKey(patricia_key!( -// "0x3b459c3fadecdb1a501f2fdeec06fd735cb2d93ea59779177a0981660a85352" -// )); - -// let class_hash = db.get_class_hash_at(address).unwrap(); -// let class = db.get_compiled_contract_class(&class_hash).unwrap(); -// let storage_value = db.get_storage_at(address, storage_key).unwrap(); - -// let expected_class_hash = ClassHash(stark_felt!( -// "0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003" -// )); - -// assert_eq!(class_hash, expected_class_hash); - -// let class_hash_in_cache = *db.db.contracts.get(&address).unwrap(); -// let class_in_cache = db.db.classes.get(&class_hash).unwrap().class.clone(); -// let storage_value_in_cache = -// *db.db.storage.get(&address).unwrap().storage.get(&storage_key).unwrap(); - -// assert_eq!(class_in_cache, class, "class must be stored in cache"); -// assert_eq!(class_hash_in_cache, expected_class_hash, "class hash must be stored in -// cache"); assert_eq!(storage_value_in_cache, storage_value, "storage value must be stored -// in cache"); } -// } diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index b1a4440c59..e5307e7e70 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -1,2 +1,257 @@ pub mod backend; -pub mod db; +pub mod state; + +use std::ops::RangeInclusive; +use std::sync::Arc; + +use anyhow::Result; +use katana_db::models::block::StoredBlockBodyIndices; +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, +}; +use katana_primitives::contract::{ContractAddress, GenericContractInfo}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use parking_lot::RwLock; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; + +use self::backend::{ForkedBackend, SharedStateProvider}; +use self::state::ForkedStateDb; +use super::in_memory::cache::{CacheDb, CacheStateDb}; +use super::in_memory::state::HistoricalStates; +use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; +use crate::traits::contract::ContractProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::state_update::StateUpdateProvider; +use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; + +pub struct ForkedProvider { + // TODO: insert `ForkedBackend` into `CacheDb` + storage: CacheDb<()>, + state: Arc, + historical_states: RwLock, +} + +impl ForkedProvider { + pub fn new(provider: Arc>, block_id: BlockHashOrNumber) -> Self { + let backend = ForkedBackend::new_with_backend_thread(provider, block_id); + let shared_provider = SharedStateProvider::new_with_backend(backend); + + let storage = CacheDb::new(()); + let state = Arc::new(CacheStateDb::new(shared_provider)); + let historical_states = RwLock::new(HistoricalStates::default()); + + Self { storage, state, historical_states } + } +} + +impl BlockHashProvider for ForkedProvider { + fn latest_hash(&self) -> Result { + Ok(self.storage.latest_block_hash) + } + + fn block_hash_by_num(&self, num: BlockNumber) -> Result> { + Ok(self.storage.block_hashes.get(&num).cloned()) + } +} + +impl BlockNumberProvider for ForkedProvider { + fn latest_number(&self) -> Result { + Ok(self.storage.latest_block_number) + } + + fn block_number_by_hash(&self, hash: BlockHash) -> Result> { + Ok(self.storage.block_numbers.get(&hash).cloned()) + } +} + +impl HeaderProvider for ForkedProvider { + fn header(&self, id: katana_primitives::block::BlockHashOrNumber) -> Result> { + match id { + katana_primitives::block::BlockHashOrNumber::Num(num) => { + Ok(self.storage.block_headers.get(&num).cloned()) + } + + katana_primitives::block::BlockHashOrNumber::Hash(hash) => { + let header @ Some(_) = self + .storage + .block_numbers + .get(&hash) + .and_then(|num| self.storage.block_headers.get(num).cloned()) + else { + return Ok(None); + }; + Ok(header) + } + } + } +} + +impl BlockProvider for ForkedProvider { + fn block(&self, id: BlockHashOrNumber) -> Result> { + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + }; + + let Some(header) = block_num.and_then(|num| self.storage.block_headers.get(&num).cloned()) + else { + return Ok(None); + }; + + let body = self.transactions_by_block(id)?.unwrap_or_default(); + + Ok(Some(Block { header, body })) + } + + fn blocks_in_range(&self, range: RangeInclusive) -> Result> { + let mut blocks = Vec::new(); + for num in range { + if let Some(block) = self.block(BlockHashOrNumber::Num(num))? { + blocks.push(block); + } + } + Ok(blocks) + } +} + +impl TransactionProvider for ForkedProvider { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + Ok(self + .storage + .transaction_numbers + .get(&hash) + .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) + } + + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + let count = *tx_count as usize; + + Ok(Some(self.storage.transactions[offset..offset + count].to_vec())) + } + + fn transaction_by_block_and_idx( + &self, + block_id: BlockHashOrNumber, + idx: u64, + ) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + + if idx >= *tx_count { + return Ok(None); + } + + Ok(Some(self.storage.transactions[offset + idx as usize].clone())) + } +} + +impl TransactionsProviderExt for ForkedProvider { + fn transaction_hashes_by_range(&self, range: std::ops::Range) -> Result> { + let mut hashes = Vec::new(); + for num in range { + if let Some(hash) = self.storage.transaction_hashes.get(&num).cloned() { + hashes.push(hash); + } + } + Ok(hashes) + } +} + +impl ReceiptProvider for ForkedProvider { + fn receipt_by_hash(&self, hash: TxHash) -> Result> { + let receipt = self + .storage + .transaction_numbers + .get(&hash) + .and_then(|num| self.storage.receipts.get(*num as usize).cloned()); + Ok(receipt) + } + + fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + }; + + let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + else { + return Ok(None); + }; + + let offset = *tx_offset as usize; + let count = *tx_count as usize; + + Ok(Some(self.storage.receipts[offset..offset + count].to_vec())) + } +} + +impl ContractProvider for ForkedProvider { + fn contract(&self, address: ContractAddress) -> Result> { + let contract = self.state.contract_state.read().get(&address).cloned(); + Ok(contract) + } +} + +impl StateUpdateProvider for ForkedProvider { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + }; + + let state_update = block_num.and_then(|num| self.storage.state_update.get(&num).cloned()); + Ok(state_update) + } +} + +impl StateFactoryProvider for ForkedProvider { + fn latest(&self) -> Result> { + Ok(Box::new(self::state::LatestStateProvider(Arc::clone(&self.state)))) + } + + fn historical(&self, block_id: BlockHashOrNumber) -> Result>> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash)?, + }; + + let provider @ Some(_) = block_num.and_then(|num| { + self.historical_states + .read() + .get(&num) + .cloned() + .map(|provider| Box::new(provider) as Box) + }) else { + return Ok(None); + }; + + Ok(provider) + } +} diff --git a/crates/katana/storage/provider/src/providers/fork/state.rs b/crates/katana/storage/provider/src/providers/fork/state.rs new file mode 100644 index 0000000000..c124adbd35 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/fork/state.rs @@ -0,0 +1,169 @@ +use std::sync::Arc; + +use anyhow::Result; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, + StorageKey, StorageValue, +}; + +use super::backend::SharedStateProvider; +use crate::providers::in_memory::cache::CacheStateDb; +use crate::providers::in_memory::state::StateSnapshot; +use crate::traits::state::{StateProvider, StateProviderExt}; + +pub type ForkedStateDb = CacheStateDb; +pub type ForkedSnapshot = StateSnapshot; + +impl ForkedStateDb { + pub(crate) fn create_snapshot(&self) -> ForkedSnapshot { + ForkedSnapshot { + inner: self.create_snapshot_without_classes(), + classes: Arc::clone(&self.shared_contract_classes), + } + } +} + +impl StateProvider for ForkedStateDb { + fn class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.shared_contract_classes.compiled_classes.read().get(&hash) { + return Ok(class.cloned()); + } + StateProvider::class(&self.db, hash) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + if let hash @ Some(_) = self.contract_state.read().get(&address).map(|i| i.class_hash) { + return Ok(hash); + } + StateProvider::class_hash_of_contract(&self.db, address) + } + + fn nonce(&self, address: ContractAddress) -> Result> { + if let nonce @ Some(_) = self.contract_state.read().get(&address).map(|i| i.nonce) { + return Ok(nonce); + } + StateProvider::nonce(&self.db, address) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + if let value @ Some(_) = self.storage.read().get(&(address, storage_key)) { + return Ok(value.cloned()); + } + StateProvider::storage(&self.db, address, storage_key) + } +} + +impl StateProviderExt for CacheStateDb { + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + if let hash @ Some(_) = self.compiled_class_hashes.read().get(&hash) { + return Ok(hash.cloned()); + } + StateProviderExt::compiled_class_hash_of_class_hash(&self.db, hash) + } + + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.shared_contract_classes.sierra_classes.read().get(&hash) { + return Ok(class.cloned()); + } + StateProviderExt::sierra_class(&self.db, hash) + } +} + +pub(super) struct LatestStateProvider(pub(super) Arc); + +impl StateProvider for LatestStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + StateProvider::nonce(&self.0, address) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + StateProvider::storage(&self.0, address, storage_key) + } + + fn class(&self, hash: ClassHash) -> Result> { + StateProvider::class(&self.0, hash) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + StateProvider::class_hash_of_contract(&self.0, address) + } +} + +impl StateProviderExt for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + StateProviderExt::sierra_class(&self.0, hash) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + StateProviderExt::compiled_class_hash_of_class_hash(&self.0, hash) + } +} + +impl StateProvider for ForkedSnapshot { + fn nonce(&self, address: ContractAddress) -> Result> { + if let nonce @ Some(_) = self.inner.contract_state.get(&address).map(|info| info.nonce) { + return Ok(nonce); + } + StateProvider::nonce(&self.inner.db, address) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + if let value @ Some(_) = self.inner.storage.get(&(address, storage_key)).cloned() { + return Ok(value); + } + StateProvider::storage(&self.inner.db, address, storage_key) + } + + fn class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.classes.compiled_classes.read().get(&hash).cloned() { + return Ok(class); + } + StateProvider::class(&self.inner.db, hash) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + if let class_hash @ Some(_) = + self.inner.contract_state.get(&address).map(|info| info.class_hash) + { + return Ok(class_hash); + } + StateProvider::class_hash_of_contract(&self.inner.db, address) + } +} + +impl StateProviderExt for ForkedSnapshot { + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.classes.sierra_classes.read().get(&hash).cloned() { + return Ok(class); + } + StateProviderExt::sierra_class(&self.inner.db, hash) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + if let hash @ Some(_) = self.inner.compiled_class_hashes.get(&hash).cloned() { + return Ok(hash); + } + StateProviderExt::compiled_class_hash_of_class_hash(&self.inner.db, hash) + } +} diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs new file mode 100644 index 0000000000..a9c664292a --- /dev/null +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use katana_db::models::block::StoredBlockBodyIndices; +use katana_primitives::block::{BlockHash, BlockNumber, Header, StateUpdate}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + SierraClass, StorageKey, StorageValue, +}; +use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use parking_lot::RwLock; + +type ContractStorageMap = HashMap<(ContractAddress, StorageKey), StorageValue>; +type ContractStateMap = HashMap; + +type SierraClassesMap = HashMap; +type CompiledClassesMap = HashMap; +type CompiledClassHashesMap = HashMap; + +#[derive(Default)] +pub struct SharedContractClasses { + pub(crate) sierra_classes: RwLock, + pub(crate) compiled_classes: RwLock, +} + +pub struct CacheSnapshotWithoutClasses { + pub(crate) db: Db, + pub(crate) storage: ContractStorageMap, + pub(crate) contract_state: ContractStateMap, + pub(crate) compiled_class_hashes: CompiledClassHashesMap, +} + +pub struct CacheStateDb { + pub(crate) db: Db, + pub(crate) storage: RwLock, + pub(crate) contract_state: RwLock, + pub(crate) shared_contract_classes: Arc, + pub(crate) compiled_class_hashes: RwLock, +} + +pub struct CacheDb { + pub(crate) db: Db, + pub(crate) block_headers: HashMap, + pub(crate) block_hashes: HashMap, + pub(crate) block_numbers: HashMap, + pub(crate) block_body_indices: HashMap, + pub(crate) latest_block_hash: BlockHash, + pub(crate) latest_block_number: BlockNumber, + pub(crate) state_update: HashMap, + pub(crate) receipts: Vec, + pub(crate) transactions: Vec, + pub(crate) transaction_hashes: HashMap, + pub(crate) transaction_numbers: HashMap, +} + +impl CacheStateDb { + pub fn new(db: Db) -> Self { + Self { + db, + storage: RwLock::new(HashMap::new()), + contract_state: RwLock::new(HashMap::new()), + compiled_class_hashes: RwLock::new(HashMap::new()), + shared_contract_classes: Arc::new(SharedContractClasses::default()), + } + } +} + +impl CacheDb { + pub fn new(db: Db) -> Self { + Self { + db, + receipts: Vec::new(), + transactions: Vec::new(), + state_update: HashMap::new(), + block_hashes: HashMap::new(), + block_headers: HashMap::new(), + block_numbers: HashMap::new(), + transaction_hashes: HashMap::new(), + block_body_indices: HashMap::new(), + transaction_numbers: HashMap::new(), + latest_block_hash: Default::default(), + latest_block_number: Default::default(), + } + } +} + +impl std::ops::Deref for CacheStateDb { + type Target = Db; + fn deref(&self) -> &Self::Target { + &self.db + } +} + +impl std::ops::Deref for CacheDb { + type Target = Db; + fn deref(&self) -> &Self::Target { + &self.db + } +} + +impl CacheStateDb { + pub fn create_snapshot_without_classes(&self) -> CacheSnapshotWithoutClasses { + CacheSnapshotWithoutClasses { + db: self.db.clone(), + storage: self.storage.read().clone(), + contract_state: self.contract_state.read().clone(), + compiled_class_hashes: self.compiled_class_hashes.read().clone(), + } + } +} diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 38e667dc96..2d5cc1ac33 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -1,6 +1,6 @@ +pub mod cache; pub mod state; -use std::collections::HashMap; use std::ops::RangeInclusive; use std::sync::Arc; @@ -13,58 +13,37 @@ use katana_primitives::contract::{ContractAddress, GenericContractInfo}; use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; use parking_lot::RwLock; -use self::state::{HistoricalStates, InMemoryState, LatestStateProvider, SnapshotStateProvider}; +use self::cache::CacheDb; +use self::state::{HistoricalStates, InMemoryStateDb, LatestStateProvider}; use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; use crate::traits::contract::ContractProvider; use crate::traits::state::{StateFactoryProvider, StateProvider}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; -#[derive(Default)] pub struct InMemoryProvider { - pub block_headers: HashMap, - pub block_hashes: HashMap, - pub block_numbers: HashMap, - pub block_body_indices: HashMap, - - pub latest_block_number: BlockNumber, - pub latest_block_hash: BlockHash, - - pub state_update: HashMap, - - pub transactions: Vec, - pub transaction_numbers: HashMap, - pub transaction_hashes: HashMap, - pub receipts: Vec, - - pub state: Arc, - - pub historical_states: RwLock, -} - -impl InMemoryProvider { - pub fn new() -> Self { - Self::default() - } + storage: CacheDb<()>, + state: Arc, + historical_states: RwLock, } impl BlockHashProvider for InMemoryProvider { fn latest_hash(&self) -> Result { - Ok(self.latest_block_hash) + Ok(self.storage.latest_block_hash) } fn block_hash_by_num(&self, num: BlockNumber) -> Result> { - Ok(self.block_hashes.get(&num).cloned()) + Ok(self.storage.block_hashes.get(&num).cloned()) } } impl BlockNumberProvider for InMemoryProvider { fn latest_number(&self) -> Result { - Ok(self.latest_block_number) + Ok(self.storage.latest_block_number) } fn block_number_by_hash(&self, hash: BlockHash) -> Result> { - Ok(self.block_numbers.get(&hash).cloned()) + Ok(self.storage.block_numbers.get(&hash).cloned()) } } @@ -72,14 +51,15 @@ impl HeaderProvider for InMemoryProvider { fn header(&self, id: katana_primitives::block::BlockHashOrNumber) -> Result> { match id { katana_primitives::block::BlockHashOrNumber::Num(num) => { - Ok(self.block_headers.get(&num).cloned()) + Ok(self.storage.block_headers.get(&num).cloned()) } katana_primitives::block::BlockHashOrNumber::Hash(hash) => { let header @ Some(_) = self + .storage .block_numbers .get(&hash) - .and_then(|num| self.block_headers.get(num).cloned()) + .and_then(|num| self.storage.block_headers.get(num).cloned()) else { return Ok(None); }; @@ -93,10 +73,11 @@ impl BlockProvider for InMemoryProvider { fn block(&self, id: BlockHashOrNumber) -> Result> { let block_num = match id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), }; - let Some(header) = block_num.and_then(|num| self.block_headers.get(&num).cloned()) else { + let Some(header) = block_num.and_then(|num| self.storage.block_headers.get(&num).cloned()) + else { return Ok(None); }; @@ -119,9 +100,10 @@ impl BlockProvider for InMemoryProvider { impl TransactionProvider for InMemoryProvider { fn transaction_by_hash(&self, hash: TxHash) -> Result> { Ok(self + .storage .transaction_numbers .get(&hash) - .and_then(|num| self.transactions.get(*num as usize).cloned())) + .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) } fn transactions_by_block( @@ -130,11 +112,11 @@ impl TransactionProvider for InMemoryProvider { ) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) else { return Ok(None); }; @@ -142,7 +124,7 @@ impl TransactionProvider for InMemoryProvider { let offset = *tx_offset as usize; let count = *tx_count as usize; - Ok(Some(self.transactions[offset..offset + count].to_vec())) + Ok(Some(self.storage.transactions[offset..offset + count].to_vec())) } fn transaction_by_block_and_idx( @@ -152,11 +134,11 @@ impl TransactionProvider for InMemoryProvider { ) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) else { return Ok(None); }; @@ -167,7 +149,7 @@ impl TransactionProvider for InMemoryProvider { return Ok(None); } - Ok(Some(self.transactions[offset + idx as usize].clone())) + Ok(Some(self.storage.transactions[offset + idx as usize].clone())) } } @@ -175,7 +157,7 @@ impl TransactionsProviderExt for InMemoryProvider { fn transaction_hashes_by_range(&self, range: std::ops::Range) -> Result> { let mut hashes = Vec::new(); for num in range { - if let Some(hash) = self.transaction_hashes.get(&num).cloned() { + if let Some(hash) = self.storage.transaction_hashes.get(&num).cloned() { hashes.push(hash); } } @@ -186,20 +168,21 @@ impl TransactionsProviderExt for InMemoryProvider { impl ReceiptProvider for InMemoryProvider { fn receipt_by_hash(&self, hash: TxHash) -> Result> { let receipt = self + .storage .transaction_numbers .get(&hash) - .and_then(|num| self.receipts.get(*num as usize).cloned()); + .and_then(|num| self.storage.receipts.get(*num as usize).cloned()); Ok(receipt) } fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.block_body_indices.get(&num)) else { return Ok(None); }; @@ -207,7 +190,7 @@ impl ReceiptProvider for InMemoryProvider { let offset = *tx_offset as usize; let count = *tx_count as usize; - Ok(Some(self.receipts[offset..offset + count].to_vec())) + Ok(Some(self.storage.receipts[offset..offset + count].to_vec())) } } @@ -222,10 +205,10 @@ impl StateUpdateProvider for InMemoryProvider { fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), }; - let state_update = block_num.and_then(|num| self.state_update.get(&num).cloned()); + let state_update = block_num.and_then(|num| self.storage.state_update.get(&num).cloned()); Ok(state_update) } } @@ -241,13 +224,13 @@ impl StateFactoryProvider for InMemoryProvider { BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash)?, }; - let provider @ Some(_) = - block_num.and_then(|num| { - self.historical_states.read().get(&num).cloned().map(|provider| { - Box::new(SnapshotStateProvider(provider)) as Box - }) - }) - else { + let provider @ Some(_) = block_num.and_then(|num| { + self.historical_states + .read() + .get(&num) + .cloned() + .map(|provider| Box::new(provider) as Box) + }) else { return Ok(None); }; @@ -257,9 +240,15 @@ impl StateFactoryProvider for InMemoryProvider { #[cfg(test)] mod tests { + use std::sync::Arc; + + use super::cache::{CacheDb, CacheStateDb}; use super::InMemoryProvider; pub(super) fn create_mock_provider() -> InMemoryProvider { - InMemoryProvider { ..Default::default() } + let storage = CacheDb::new(()); + let state = Arc::new(CacheStateDb::new(())); + let historical_states = Default::default(); + InMemoryProvider { storage, state, historical_states } } } diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index 1e84d2c1c7..f7032a4232 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -4,47 +4,16 @@ use std::sync::Arc; use anyhow::Result; use katana_primitives::block::BlockNumber; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, - Nonce, SierraClass, StorageKey, StorageValue, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, + StorageKey, StorageValue, }; -use parking_lot::RwLock; +use super::cache::{CacheSnapshotWithoutClasses, CacheStateDb, SharedContractClasses}; use crate::traits::state::{StateProvider, StateProviderExt}; -type ContractStorageMap = HashMap<(ContractAddress, StorageKey), StorageValue>; -type ContractStateMap = HashMap; - -type SierraClassesMap = HashMap; -type CompiledClassesMap = HashMap; -type CompiledClassHashesMap = HashMap; - -pub struct StateSnapshot { - pub contract_state: ContractStateMap, - pub storage: ContractStorageMap, - pub compiled_class_hashes: CompiledClassHashesMap, - pub shared_sierra_classes: Arc>, - pub shared_compiled_classes: Arc>, -} - -#[derive(Default)] -pub struct InMemoryState { - pub contract_state: RwLock, - pub storage: RwLock, - pub compiled_class_hashes: RwLock, - pub shared_sierra_classes: Arc>, - pub shared_compiled_classes: Arc>, -} - -impl InMemoryState { - pub(crate) fn create_snapshot(&self) -> StateSnapshot { - StateSnapshot { - storage: self.storage.read().clone(), - contract_state: self.contract_state.read().clone(), - compiled_class_hashes: self.compiled_class_hashes.read().clone(), - shared_sierra_classes: self.shared_sierra_classes.clone(), - shared_compiled_classes: self.shared_compiled_classes.clone(), - } - } +pub struct StateSnapshot { + pub(crate) classes: Arc, + pub(crate) inner: CacheSnapshotWithoutClasses, } const DEFAULT_HISTORY_LIMIT: usize = 500; @@ -55,7 +24,7 @@ const MIN_HISTORY_LIMIT: usize = 10; /// It should store at N - 1 states, where N is the latest block number. pub struct HistoricalStates { /// The states at a certain block based on the block number - states: HashMap>, + states: HashMap>, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory @@ -75,7 +44,7 @@ impl HistoricalStates { } /// Returns the state for the given `block_hash` if present - pub fn get(&self, block_num: &BlockNumber) -> Option<&Arc> { + pub fn get(&self, block_num: &BlockNumber) -> Option<&Arc> { self.states.get(block_num) } @@ -87,7 +56,7 @@ impl HistoricalStates { /// Since we keep a snapshot of the entire state as history, the size of the state will increase /// with the transactions processed. To counter this, we gradually decrease the cache limit with /// the number of states/blocks until we reached the `min_limit`. - pub fn insert(&mut self, block_num: BlockNumber, state: StateSnapshot) { + pub fn insert(&mut self, block_num: BlockNumber, state: Box) { if self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = @@ -118,11 +87,36 @@ impl Default for HistoricalStates { } } -pub struct LatestStateProvider(pub(super) Arc); +pub(super) type InMemoryStateDb = CacheStateDb<()>; +pub(super) type InMemorySnapshot = StateSnapshot<()>; -impl StateProvider for LatestStateProvider { +impl Default for InMemoryStateDb { + fn default() -> Self { + CacheStateDb { + db: (), + storage: Default::default(), + contract_state: Default::default(), + shared_contract_classes: Arc::new(SharedContractClasses { + sierra_classes: Default::default(), + compiled_classes: Default::default(), + }), + compiled_class_hashes: Default::default(), + } + } +} + +impl InMemoryStateDb { + pub(crate) fn create_snapshot(&self) -> StateSnapshot<()> { + StateSnapshot { + inner: self.create_snapshot_without_classes(), + classes: Arc::clone(&self.shared_contract_classes), + } + } +} + +impl StateProvider for InMemorySnapshot { fn nonce(&self, address: ContractAddress) -> Result> { - let nonce = self.0.contract_state.read().get(&address).map(|info| info.nonce); + let nonce = self.inner.contract_state.get(&address).map(|info| info.nonce); Ok(nonce) } @@ -131,24 +125,24 @@ impl StateProvider for LatestStateProvider { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - let value = self.0.storage.read().get(&(address, storage_key)).cloned(); + let value = self.inner.storage.get(&(address, storage_key)).cloned(); Ok(value) } fn class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_compiled_classes.read().get(&hash).cloned(); + let class = self.classes.compiled_classes.read().get(&hash).cloned(); Ok(class) } fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - let class_hash = self.0.contract_state.read().get(&address).map(|info| info.class_hash); + let class_hash = self.inner.contract_state.get(&address).map(|info| info.class_hash); Ok(class_hash) } } -impl StateProviderExt for LatestStateProvider { +impl StateProviderExt for InMemorySnapshot { fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_sierra_classes.read().get(&hash).cloned(); + let class = self.classes.sierra_classes.read().get(&hash).cloned(); Ok(class) } @@ -156,16 +150,16 @@ impl StateProviderExt for LatestStateProvider { &self, hash: ClassHash, ) -> Result> { - let hash = self.0.compiled_class_hashes.read().get(&hash).cloned(); + let hash = self.inner.compiled_class_hashes.get(&hash).cloned(); Ok(hash) } } -pub struct SnapshotStateProvider(pub(super) Arc); +pub(super) struct LatestStateProvider(pub(super) Arc); -impl StateProvider for SnapshotStateProvider { +impl StateProvider for LatestStateProvider { fn nonce(&self, address: ContractAddress) -> Result> { - let nonce = self.0.contract_state.get(&address).map(|info| info.nonce); + let nonce = self.0.contract_state.read().get(&address).map(|info| info.nonce); Ok(nonce) } @@ -174,24 +168,24 @@ impl StateProvider for SnapshotStateProvider { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - let value = self.0.storage.get(&(address, storage_key)).cloned(); + let value = self.0.storage.read().get(&(address, storage_key)).cloned(); Ok(value) } fn class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_compiled_classes.read().get(&hash).cloned(); + let class = self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned(); Ok(class) } fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - let class_hash = self.0.contract_state.get(&address).map(|info| info.class_hash); + let class_hash = self.0.contract_state.read().get(&address).map(|info| info.class_hash); Ok(class_hash) } } -impl StateProviderExt for SnapshotStateProvider { +impl StateProviderExt for LatestStateProvider { fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_sierra_classes.read().get(&hash).cloned(); + let class = self.0.shared_contract_classes.sierra_classes.read().get(&hash).cloned(); Ok(class) } @@ -199,7 +193,7 @@ impl StateProviderExt for SnapshotStateProvider { &self, hash: ClassHash, ) -> Result> { - let hash = self.0.compiled_class_hashes.get(&hash).cloned(); + let hash = self.0.compiled_class_hashes.read().get(&hash).cloned(); Ok(hash) } } @@ -207,7 +201,7 @@ impl StateProviderExt for SnapshotStateProvider { #[cfg(test)] mod tests { use katana_primitives::block::BlockHashOrNumber; - use katana_primitives::contract::StorageKey; + use katana_primitives::contract::{GenericContractInfo, StorageKey}; use starknet::macros::felt; use super::*; @@ -232,7 +226,7 @@ mod tests { const ADDR_2_NONCE_AT_1: Nonce = felt!("0x1"); const ADDR_2_NONCE_AT_2: Nonce = felt!("0x2"); - fn create_mock_state() -> InMemoryState { + fn create_mock_state() -> InMemoryStateDb { let storage = HashMap::from([ ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_1), ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_1), @@ -249,7 +243,7 @@ mod tests { ), ]); - InMemoryState { + InMemoryStateDb { storage: storage.into(), contract_state: contract_state.into(), ..Default::default() @@ -317,7 +311,7 @@ mod tests { let mut provider = create_mock_provider(); provider.state = Arc::new(state); - provider.historical_states.write().insert(1, snapshot); + provider.historical_states.write().insert(1, Box::new(snapshot)); // check latest state From 804fc8613609c697de9bcb800770a681b711195f Mon Sep 17 00:00:00 2001 From: Kariy Date: Sat, 25 Nov 2023 21:35:09 +0800 Subject: [PATCH 084/192] test(katana-provider): update tests for in-memory and fork providers --- Cargo.lock | 1 + crates/katana/storage/provider/Cargo.toml | 7 +- .../provider/src/providers/fork/backend.rs | 201 ++++++++++++++---- .../provider/src/providers/in_memory/state.rs | 68 +++++- .../storage/provider/src/providers/mod.rs | 2 +- 5 files changed, 229 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45b4b7c333..be2103908f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5157,6 +5157,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "url", ] [[package]] diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index 5352f6e516..99b94712de 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -22,9 +22,10 @@ starknet.workspace = true tokio.workspace = true [features] -default = [ "fork", "in_memory" ] -fork = [ ] -in_memory = [ ] +default = [ "fork", "in-memory" ] +fork = [ "in-memory" ] +in-memory = [ ] [dev-dependencies] starknet.workspace = true +url.workspace = true diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 739b634a7e..6aa2c76c6e 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -236,14 +236,12 @@ impl ForkedBackend { contract_address: ContractAddress, ) -> Result { trace!(target: "forked_backend", "request nonce for contract address {contract_address}"); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.0 - .lock() - .try_send(BackendRequest::GetNonce(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive nonce result") - }) + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetNonce(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive nonce result") } pub fn do_get_storage( @@ -252,14 +250,12 @@ impl ForkedBackend { key: StorageKey, ) -> Result { trace!(target: "forked_backend", "request storage for address {contract_address} at key {key:#x}" ); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.0 - .lock() - .try_send(BackendRequest::GetStorage(contract_address, key, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive storage result") - }) + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetStorage(contract_address, key, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive storage result") } pub fn do_get_class_hash_at( @@ -267,14 +263,12 @@ impl ForkedBackend { contract_address: ContractAddress, ) -> Result { trace!(target: "forked_backend", "request class hash at address {contract_address}"); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.0 - .lock() - .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive class hash result") - }) + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class hash result") } pub fn do_get_class_at( @@ -282,14 +276,12 @@ impl ForkedBackend { class_hash: ClassHash, ) -> Result { trace!(target: "forked_backend", "request class at hash {class_hash:#x}"); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.0 - .lock() - .try_send(BackendRequest::GetClassAt(class_hash, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive class result") - }) + let (sender, rx) = oneshot(); + self.0 + .lock() + .try_send(BackendRequest::GetClassAt(class_hash, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class result") } pub fn do_get_compiled_class_hash( @@ -302,12 +294,9 @@ impl ForkedBackend { // else if sierra class, then we have to compile it and compute the compiled class hash. match class { starknet::core::types::ContractClass::Legacy(_) => Ok(class_hash), - starknet::core::types::ContractClass::Sierra(sierra_class) => { - tokio::task::block_in_place(|| { - compiled_class_hash_from_flattened_sierra_class(&sierra_class) - }) - .map_err(|e| ForkedBackendError::ComputeClassHashError(e.to_string())) + compiled_class_hash_from_flattened_sierra_class(&sierra_class) + .map_err(|e| ForkedBackendError::ComputeClassHashError(e.to_string())) } } } @@ -408,10 +397,22 @@ impl StateProvider for SharedStateProvider { impl StateProviderExt for SharedStateProvider { fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.0.shared_contract_classes.sierra_classes.read().get(&hash) { + return Ok(class.cloned()); + } + let class = self.0.do_get_class_at(hash).unwrap(); match class { starknet::core::types::ContractClass::Legacy(_) => Ok(None), - starknet::core::types::ContractClass::Sierra(sierra_class) => Ok(Some(sierra_class)), + starknet::core::types::ContractClass::Sierra(sierra_class) => { + self.0 + .shared_contract_classes + .sierra_classes + .write() + .insert(hash, sierra_class.clone()); + + Ok(Some(sierra_class)) + } } } @@ -419,7 +420,129 @@ impl StateProviderExt for SharedStateProvider { &self, hash: ClassHash, ) -> Result> { - let hash = self.0.do_get_compiled_class_hash(hash).unwrap(); + if let hash @ Some(_) = self.0.compiled_class_hashes.read().get(&hash) { + return Ok(hash.cloned()); + } + + let compiled_hash = self.0.do_get_compiled_class_hash(hash).unwrap(); + self.0.compiled_class_hashes.write().insert(hash, compiled_hash); + Ok(Some(hash)) } } + +#[cfg(test)] +mod tests { + use katana_primitives::block::BlockNumber; + use katana_primitives::contract::GenericContractInfo; + use starknet::macros::felt; + use url::Url; + + use super::*; + + const LOCAL_RPC_URL: &str = "http://localhost:5050"; + + const STORAGE_KEY: StorageKey = felt!("0x1"); + const ADDR_1: ContractAddress = ContractAddress(felt!("0xADD1")); + const ADDR_1_NONCE: Nonce = felt!("0x1"); + const ADDR_1_STORAGE_VALUE: StorageKey = felt!("0x8080"); + const ADDR_1_CLASS_HASH: StorageKey = felt!("0x1"); + + fn create_forked_backend(rpc_url: String, block_num: BlockNumber) -> (ForkedBackend, Backend) { + ForkedBackend::new( + Arc::new(JsonRpcClient::new(HttpTransport::new( + Url::parse(&rpc_url).expect("valid url"), + ))), + BlockHashOrNumber::Num(block_num), + ) + } + + fn create_forked_backend_with_backend_thread( + rpc_url: String, + block_num: BlockNumber, + ) -> ForkedBackend { + ForkedBackend::new_with_backend_thread( + Arc::new(JsonRpcClient::new(HttpTransport::new( + Url::parse(&rpc_url).expect("valid url"), + ))), + BlockHashOrNumber::Num(block_num), + ) + } + + #[test] + fn get_from_cache_if_exist() { + // setup + let (backend, _) = create_forked_backend(LOCAL_RPC_URL.into(), 1); + let state_db = CacheStateDb::new(backend); + + state_db.storage.write().insert((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE); + state_db.contract_state.write().insert( + ADDR_1, + GenericContractInfo { nonce: ADDR_1_NONCE, class_hash: ADDR_1_CLASS_HASH }, + ); + + let provider = SharedStateProvider(Arc::new(state_db)); + + assert_eq!(StateProvider::nonce(&provider, ADDR_1).unwrap(), Some(ADDR_1_NONCE)); + assert_eq!( + StateProvider::storage(&provider, ADDR_1, STORAGE_KEY).unwrap(), + Some(ADDR_1_STORAGE_VALUE) + ); + assert_eq!( + StateProvider::class_hash_of_contract(&provider, ADDR_1).unwrap(), + Some(ADDR_1_CLASS_HASH) + ); + } + + #[test] + #[should_panic] + fn fetch_from_fork_will_panic_if_backend_thread_not_running() { + let (backend, _) = create_forked_backend(LOCAL_RPC_URL.into(), 1); + let provider = SharedStateProvider(Arc::new(CacheStateDb::new(backend))); + let _ = StateProvider::nonce(&provider, ADDR_1); + } + + const FORKED_URL: &str = + "https://starknet-goerli.infura.io/v3/369ce5ac40614952af936e4d64e40474"; + + const GOERLI_CONTRACT_ADDR: ContractAddress = ContractAddress(felt!( + "0x02b92ec12cA1e308f320e99364d4dd8fcc9efDAc574F836C8908de937C289974" + )); + const GOERLI_CONTRACT_STORAGE_KEY: StorageKey = + felt!("0x3b459c3fadecdb1a501f2fdeec06fd735cb2d93ea59779177a0981660a85352"); + + #[test] + #[ignore] + fn fetch_from_fork_if_not_in_cache() { + let backend = create_forked_backend_with_backend_thread(FORKED_URL.into(), 908622); + let provider = SharedStateProvider(Arc::new(CacheStateDb::new(backend))); + + // fetch from remote + + let class_hash = + StateProvider::class_hash_of_contract(&provider, GOERLI_CONTRACT_ADDR).unwrap(); + let storage_value = + StateProvider::storage(&provider, GOERLI_CONTRACT_ADDR, GOERLI_CONTRACT_STORAGE_KEY) + .unwrap(); + let nonce = StateProvider::nonce(&provider, GOERLI_CONTRACT_ADDR).unwrap(); + + // fetch from cache + + let class_hash_in_cache = + provider.0.contract_state.read().get(&GOERLI_CONTRACT_ADDR).map(|i| i.class_hash); + let storage_value_in_cache = provider + .0 + .storage + .read() + .get(&(GOERLI_CONTRACT_ADDR, GOERLI_CONTRACT_STORAGE_KEY)) + .cloned(); + let nonce_in_cache = + provider.0.contract_state.read().get(&GOERLI_CONTRACT_ADDR).map(|i| i.nonce); + + // check + + assert_eq!(nonce, nonce_in_cache, "value must be stored in cache"); + assert_eq!(class_hash, class_hash_in_cache, "value must be stored in cache"); + assert_eq!(storage_value, storage_value_in_cache, "value must be stored in cache"); + } +} diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index f7032a4232..cad0180068 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -213,18 +213,22 @@ mod tests { const ADDR_1: ContractAddress = ContractAddress(felt!("0xADD1")); const ADDR_1_STORAGE_VALUE_AT_1: StorageKey = felt!("0x8080"); const ADDR_1_STORAGE_VALUE_AT_2: StorageKey = felt!("0x1212"); + const ADDR_1_STORAGE_VALUE_AT_3: StorageKey = felt!("0x3434"); const ADDR_1_CLASS_HASH_AT_1: ClassHash = felt!("0x4337"); const ADDR_1_CLASS_HASH_AT_2: ClassHash = felt!("0x7334"); const ADDR_1_NONCE_AT_1: Nonce = felt!("0x1"); const ADDR_1_NONCE_AT_2: Nonce = felt!("0x2"); + const ADDR_1_NONCE_AT_3: Nonce = felt!("0x3"); const ADDR_2: ContractAddress = ContractAddress(felt!("0xADD2")); const ADDR_2_STORAGE_VALUE_AT_1: StorageKey = felt!("0x9090"); const ADDR_2_STORAGE_VALUE_AT_2: StorageKey = felt!("1313"); + const ADDR_2_STORAGE_VALUE_AT_3: StorageKey = felt!("5555"); const ADDR_2_CLASS_HASH_AT_1: ClassHash = felt!("0x1559"); const ADDR_2_CLASS_HASH_AT_2: ClassHash = felt!("0x9551"); const ADDR_2_NONCE_AT_1: Nonce = felt!("0x1"); const ADDR_2_NONCE_AT_2: Nonce = felt!("0x2"); + const ADDR_2_NONCE_AT_3: Nonce = felt!("0x3"); fn create_mock_state() -> InMemoryStateDb { let storage = HashMap::from([ @@ -285,7 +289,8 @@ mod tests { // setup let state = create_mock_state(); - let snapshot = state.create_snapshot(); + // create snapshot 1 + let snapshot_1 = state.create_snapshot(); state.storage.write().extend([ ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_2), @@ -309,35 +314,57 @@ mod tests { ), ]); + // create snapshot 2 + let snapshot_2 = state.create_snapshot(); + + state.storage.write().extend([ + ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_3), + ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_3), + ]); + + state.contract_state.write().entry(ADDR_1).and_modify(|e| e.nonce = ADDR_1_NONCE_AT_3); + state.contract_state.write().entry(ADDR_2).and_modify(|e| e.nonce = ADDR_2_NONCE_AT_3); + let mut provider = create_mock_provider(); provider.state = Arc::new(state); - provider.historical_states.write().insert(1, Box::new(snapshot)); + provider.historical_states.write().insert(1, Box::new(snapshot_1)); + provider.historical_states.write().insert(2, Box::new(snapshot_2)); // check latest state let latest_state_provider = StateFactoryProvider::latest(&provider).unwrap(); - assert_eq!(latest_state_provider.nonce(ADDR_1).unwrap(), Some(ADDR_1_NONCE_AT_2)); + assert_eq!( + latest_state_provider.nonce(ADDR_1).unwrap(), + Some(ADDR_1_NONCE_AT_3), + "nonce must be updated" + ); assert_eq!( latest_state_provider.storage(ADDR_1, STORAGE_KEY).unwrap(), - Some(ADDR_1_STORAGE_VALUE_AT_2) + Some(ADDR_1_STORAGE_VALUE_AT_3), + "storage must be updated" ); assert_eq!( latest_state_provider.class_hash_of_contract(ADDR_1).unwrap(), Some(ADDR_1_CLASS_HASH_AT_2) ); - assert_eq!(latest_state_provider.nonce(ADDR_2).unwrap(), Some(ADDR_2_NONCE_AT_2)); + assert_eq!( + latest_state_provider.nonce(ADDR_2).unwrap(), + Some(ADDR_2_NONCE_AT_3), + "nonce must be updated" + ); assert_eq!( latest_state_provider.storage(ADDR_2, STORAGE_KEY).unwrap(), - Some(ADDR_2_STORAGE_VALUE_AT_2) + Some(ADDR_2_STORAGE_VALUE_AT_3), + "storage must be updated" ); assert_eq!( latest_state_provider.class_hash_of_contract(ADDR_2).unwrap(), Some(ADDR_2_CLASS_HASH_AT_2) ); - // check historical state + // check historical state at 1 let historical_state_provider = StateFactoryProvider::historical(&provider, BlockHashOrNumber::Num(1)) @@ -363,5 +390,32 @@ mod tests { historical_state_provider.class_hash_of_contract(ADDR_2).unwrap(), Some(ADDR_2_CLASS_HASH_AT_1) ); + + // check historical state at 2 + + let historical_state_provider = + StateFactoryProvider::historical(&provider, BlockHashOrNumber::Num(2)) + .unwrap() + .unwrap(); + + assert_eq!(historical_state_provider.nonce(ADDR_1).unwrap(), Some(ADDR_1_NONCE_AT_2)); + assert_eq!( + historical_state_provider.storage(ADDR_1, STORAGE_KEY).unwrap(), + Some(ADDR_1_STORAGE_VALUE_AT_2) + ); + assert_eq!( + historical_state_provider.class_hash_of_contract(ADDR_1).unwrap(), + Some(ADDR_1_CLASS_HASH_AT_2) + ); + + assert_eq!(historical_state_provider.nonce(ADDR_2).unwrap(), Some(ADDR_2_NONCE_AT_2)); + assert_eq!( + historical_state_provider.storage(ADDR_2, STORAGE_KEY).unwrap(), + Some(ADDR_2_STORAGE_VALUE_AT_2) + ); + assert_eq!( + historical_state_provider.class_hash_of_contract(ADDR_2).unwrap(), + Some(ADDR_2_CLASS_HASH_AT_2) + ); } } diff --git a/crates/katana/storage/provider/src/providers/mod.rs b/crates/katana/storage/provider/src/providers/mod.rs index 58d9a050ca..0c2dc27d17 100644 --- a/crates/katana/storage/provider/src/providers/mod.rs +++ b/crates/katana/storage/provider/src/providers/mod.rs @@ -1,4 +1,4 @@ #[cfg(feature = "fork")] pub mod fork; -#[cfg(feature = "in_memory")] +#[cfg(feature = "in-memory")] pub mod in_memory; From a79b2ec4cc3a99a355c78562bcaa50dd69731d58 Mon Sep 17 00:00:00 2001 From: Kariy Date: Sun, 26 Nov 2023 05:50:44 +0800 Subject: [PATCH 085/192] refactor(katana-provider): auto impl traits for smart pointer --- crates/katana/storage/provider/src/traits/block.rs | 4 ++++ crates/katana/storage/provider/src/traits/state.rs | 1 + crates/katana/storage/provider/src/traits/state_update.rs | 1 + crates/katana/storage/provider/src/traits/transaction.rs | 3 +++ 4 files changed, 9 insertions(+) diff --git a/crates/katana/storage/provider/src/traits/block.rs b/crates/katana/storage/provider/src/traits/block.rs index 8a299c0257..8c7acacf38 100644 --- a/crates/katana/storage/provider/src/traits/block.rs +++ b/crates/katana/storage/provider/src/traits/block.rs @@ -5,6 +5,7 @@ use katana_primitives::block::{Block, BlockHash, BlockHashOrNumber, BlockNumber, use super::transaction::TransactionProvider; +#[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockHashProvider: Send + Sync { /// Retrieves the latest block hash. /// @@ -15,6 +16,7 @@ pub trait BlockHashProvider: Send + Sync { fn block_hash_by_num(&self, num: BlockNumber) -> Result>; } +#[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockNumberProvider: Send + Sync { /// Retrieves the latest block number. /// @@ -25,6 +27,7 @@ pub trait BlockNumberProvider: Send + Sync { fn block_number_by_hash(&self, hash: BlockHash) -> Result>; } +#[auto_impl::auto_impl(&, Box, Arc)] pub trait HeaderProvider: Send + Sync { /// Retrieves the latest header by its block id. fn header(&self, id: BlockHashOrNumber) -> Result>; @@ -38,6 +41,7 @@ pub trait HeaderProvider: Send + Sync { } } +#[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockProvider: BlockHashProvider + BlockNumberProvider + HeaderProvider + TransactionProvider + Send + Sync { diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 3769bdcb90..7cdfc0a4b9 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -39,6 +39,7 @@ pub trait StateProviderExt: StateProvider + Send + Sync { /// A state factory provider is a provider which can create state providers for /// states at a particular block. +#[auto_impl::auto_impl(&, Box, Arc)] pub trait StateFactoryProvider { /// Returns a state provider for retrieving the latest state. fn latest(&self) -> Result>; diff --git a/crates/katana/storage/provider/src/traits/state_update.rs b/crates/katana/storage/provider/src/traits/state_update.rs index 86fe0d709f..3062e18607 100644 --- a/crates/katana/storage/provider/src/traits/state_update.rs +++ b/crates/katana/storage/provider/src/traits/state_update.rs @@ -1,6 +1,7 @@ use anyhow::Result; use katana_primitives::block::{BlockHashOrNumber, StateUpdate}; +#[auto_impl::auto_impl(&, Box, Arc)] pub trait StateUpdateProvider: Send + Sync { /// Returns the state update for the given block. fn state_update(&self, block_id: BlockHashOrNumber) -> Result>; diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index 5936b16bf2..a71a0cecc7 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -4,6 +4,7 @@ use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +#[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionProvider: Send + Sync { /// Returns a transaction given its hash. fn transaction_by_hash(&self, hash: TxHash) -> Result>; @@ -22,11 +23,13 @@ pub trait TransactionProvider: Send + Sync { ) -> Result>; } +#[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionsProviderExt: TransactionProvider + Send + Sync { /// Retrieves the tx hashes for the given range of tx numbers. fn transaction_hashes_by_range(&self, range: Range) -> Result>; } +#[auto_impl::auto_impl(&, Box, Arc)] pub trait ReceiptProvider: Send + Sync { /// Returns the transaction receipt given a transaction hash. fn receipt_by_hash(&self, hash: TxHash) -> Result>; From 34ce24ad80d525d10d149f70756745d6b915da76 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 27 Nov 2023 13:12:18 +0800 Subject: [PATCH 086/192] refactor(katana-primitives): flatten tx types variants --- crates/katana/primitives/src/block.rs | 4 +- .../primitives/src/conversion/blockifier.rs | 123 +++++++++------- .../katana/primitives/src/conversion/rpc.rs | 139 +++++++++--------- crates/katana/primitives/src/transaction.rs | 52 +++---- crates/katana/storage/db/src/tables.rs | 4 +- crates/katana/storage/provider/src/lib.rs | 11 +- .../provider/src/providers/fork/mod.rs | 11 +- .../provider/src/providers/in_memory/cache.rs | 4 +- .../provider/src/providers/in_memory/mod.rs | 11 +- .../provider/src/traits/transaction.rs | 11 +- 10 files changed, 180 insertions(+), 190 deletions(-) diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 560e9710a2..1b8e161cca 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,5 +1,5 @@ use crate::contract::ContractAddress; -use crate::transaction::Transaction; +use crate::transaction::Tx; use crate::FieldElement; /// Block state update type. @@ -34,7 +34,7 @@ pub struct Header { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Block { pub header: Header, - pub body: Vec, + pub body: Vec, } impl From for BlockHashOrNumber { diff --git a/crates/katana/primitives/src/conversion/blockifier.rs b/crates/katana/primitives/src/conversion/blockifier.rs index 04f73c7ef5..bb9d24b46b 100644 --- a/crates/katana/primitives/src/conversion/blockifier.rs +++ b/crates/katana/primitives/src/conversion/blockifier.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use blockifier::transaction::account_transaction::AccountTransaction; use starknet_api::core::{ ClassHash, CompiledClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey, }; @@ -13,11 +14,13 @@ use starknet_api::transaction::{ L1HandlerTransaction, TransactionHash, TransactionSignature, TransactionVersion, }; +use crate::transaction::ExecutionTx; +use crate::FieldElement; + mod primitives { pub use crate::contract::{ContractAddress, Nonce}; pub use crate::transaction::{ - DeclareTx, DeclareTxV1, DeclareTxV2, DeclareTxWithCompiledClass, DeployAccountTx, - DeployAccountTxWithContractAddress, InvokeTx, InvokeTxV1, L1HandlerTx, + DeclareTx, DeclareTxWithClasses, DeployAccountTx, InvokeTx, L1HandlerTx, }; pub use crate::FieldElement; } @@ -28,59 +31,52 @@ impl From for ContractAddress { } } -impl From for InvokeTransaction { - fn from(tx: primitives::InvokeTx) -> Self { - match tx { - primitives::InvokeTx::V1(tx) => InvokeTransaction::V1(tx.into()), - } +impl From for primitives::ContractAddress { + fn from(address: ContractAddress) -> Self { + Self((*address.0.key()).into()) } } -impl From for InvokeTransactionV1 { - fn from(tx: primitives::InvokeTxV1) -> Self { - Self { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - calldata: calldata_from_felts_vec(tx.calldata), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), +impl From for InvokeTransaction { + fn from(tx: primitives::InvokeTx) -> Self { + if FieldElement::ONE == tx.version { + InvokeTransaction::V1(InvokeTransactionV1 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + calldata: calldata_from_felts_vec(tx.calldata), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + }) + } else { + unimplemented!("Unsupported transaction version") } } } impl From for DeclareTransaction { fn from(tx: primitives::DeclareTx) -> Self { - match tx { - primitives::DeclareTx::V1(tx) => DeclareTransaction::V1(tx.into()), - primitives::DeclareTx::V2(tx) => DeclareTransaction::V2(tx.into()), - } - } -} - -impl From for DeclareTransactionV0V1 { - fn from(tx: primitives::DeclareTxV1) -> Self { - Self { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - class_hash: ClassHash(tx.class_hash.into()), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - } - } -} - -impl From for DeclareTransactionV2 { - fn from(tx: primitives::DeclareTxV2) -> Self { - Self { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - class_hash: ClassHash(tx.class_hash.into()), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.into()), + if FieldElement::ONE == tx.version { + DeclareTransaction::V1(DeclareTransactionV0V1 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + class_hash: ClassHash(tx.class_hash.into()), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + }) + } else if FieldElement::TWO == tx.version { + DeclareTransaction::V2(DeclareTransactionV2 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + class_hash: ClassHash(tx.class_hash.into()), + signature: signature_from_felts_vec(tx.signature), + transaction_hash: TransactionHash(tx.transaction_hash.into()), + compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.unwrap().into()), + }) + } else { + unimplemented!("Unsupported transaction version") } } } @@ -113,19 +109,42 @@ impl From for L1HandlerTransaction { } } -impl From +impl From for blockifier::transaction::transactions::DeployAccountTransaction { - fn from(tx: primitives::DeployAccountTxWithContractAddress) -> Self { - Self { tx: tx.0.into(), contract_address: tx.1.into() } + fn from(tx: primitives::DeployAccountTx) -> Self { + Self { contract_address: tx.contract_address.into(), tx: tx.into() } } } -impl From +impl From for blockifier::transaction::transactions::DeclareTransaction { - fn from(tx: primitives::DeclareTxWithCompiledClass) -> Self { - Self::new(tx.0.into(), tx.1).expect("tx & class must be compatible") + fn from(tx: primitives::DeclareTxWithClasses) -> Self { + Self::new(tx.tx.into(), tx.compiled_class).expect("tx & class must be compatible") + } +} + +impl From for blockifier::transaction::transaction_execution::Transaction { + fn from(tx: ExecutionTx) -> Self { + match tx { + ExecutionTx::L1Handler(tx) => Self::L1HandlerTransaction( + blockifier::transaction::transactions::L1HandlerTransaction { + paid_fee_on_l1: Fee(tx.paid_fee_on_l1), + tx: tx.into(), + }, + ), + + ExecutionTx::Invoke(tx) => { + Self::AccountTransaction(AccountTransaction::Invoke(tx.into())) + } + ExecutionTx::Declare(tx) => { + Self::AccountTransaction(AccountTransaction::Declare(tx.into())) + } + ExecutionTx::DeployAccount(tx) => { + Self::AccountTransaction(AccountTransaction::DeployAccount(tx.into())) + } + } } } diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs index 650e703633..f4023e2a5e 100644 --- a/crates/katana/primitives/src/conversion/rpc.rs +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -16,7 +16,6 @@ use starknet::core::types::{ use starknet::core::utils::get_contract_address; use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; -use self::primitives::{ContractAddress, InvokeTxV1}; use crate::contract::{ClassHash, CompiledClassHash, CompiledContractClass, SierraClass}; use crate::utils::transaction::{ compute_declare_v1_transaction_hash, compute_declare_v2_transaction_hash, @@ -27,8 +26,7 @@ use crate::FieldElement; mod primitives { pub use crate::contract::{CompiledContractClass, ContractAddress, Nonce}; pub use crate::transaction::{ - DeclareTx, DeclareTxV1, DeclareTxV2, DeclareTxWithCompiledClass, DeployAccountTx, - DeployAccountTxWithContractAddress, InvokeTx, InvokeTxV1, L1HandlerTx, Transaction, + DeclareTx, DeclareTxWithClasses, DeployAccountTx, InvokeTx, L1HandlerTx, Tx, }; pub use crate::FieldElement; } @@ -46,14 +44,15 @@ impl primitives::InvokeTx { tx.is_query, ); - primitives::InvokeTx::V1(InvokeTxV1 { + primitives::InvokeTx { transaction_hash, nonce: tx.nonce, calldata: tx.calldata, signature: tx.signature, + version: FieldElement::ONE, sender_address: tx.sender_address.into(), max_fee: tx.max_fee.try_into().expect("max_fee is too large"), - }) + } } } @@ -61,7 +60,7 @@ impl primitives::DeployAccountTx { pub fn from_broadcasted_rpc( tx: BroadcastedDeployAccountTransaction, chain_id: FieldElement, - ) -> (Self, ContractAddress) { + ) -> Self { let contract_address = get_contract_address( tx.contract_address_salt, tx.class_hash, @@ -80,19 +79,17 @@ impl primitives::DeployAccountTx { tx.is_query, ); - ( - Self { - transaction_hash, - nonce: tx.nonce, - signature: tx.signature, - class_hash: tx.class_hash, - version: FieldElement::ONE, - constructor_calldata: tx.constructor_calldata, - contract_address_salt: tx.contract_address_salt, - max_fee: tx.max_fee.try_into().expect("max_fee is too large"), - }, - contract_address.into(), - ) + Self { + transaction_hash, + nonce: tx.nonce, + signature: tx.signature, + class_hash: tx.class_hash, + version: FieldElement::ONE, + contract_address: contract_address.into(), + constructor_calldata: tx.constructor_calldata, + contract_address_salt: tx.contract_address_salt, + max_fee: tx.max_fee.try_into().expect("max fee is too large"), + } } } @@ -100,62 +97,64 @@ impl primitives::DeclareTx { pub fn from_broadcasted_rpc( tx: BroadcastedDeclareTransaction, chain_id: FieldElement, - ) -> (Self, primitives::CompiledContractClass) { - match tx { + ) -> (Self, primitives::CompiledContractClass, Option) { + // extract class + let (class_hash, compiled_class_hash, sierra_class, compiled_class) = match &tx { BroadcastedDeclareTransaction::V1(tx) => { - let (class_hash, contract_class) = - legacy_rpc_to_inner_class(&tx.contract_class).expect("valid contract class"); - - let transaction_hash = compute_declare_v1_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.is_query, - ); - - ( - primitives::DeclareTx::V1(primitives::DeclareTxV1 { - class_hash, - nonce: tx.nonce, - transaction_hash, - signature: tx.signature, - sender_address: tx.sender_address.into(), - max_fee: tx.max_fee.try_into().expect("max_fee is too large"), - }), - contract_class, - ) + let (hash, class) = legacy_rpc_to_inner_class(&tx.contract_class).unwrap(); + (hash, None, None, class) } BroadcastedDeclareTransaction::V2(tx) => { - let (class_hash, _, contract_class) = - rpc_to_inner_class(&tx.contract_class).expect("valid contract class"); - - let transaction_hash = compute_declare_v2_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.compiled_class_hash, - tx.is_query, - ); - - ( - primitives::DeclareTx::V2(primitives::DeclareTxV2 { - class_hash, - nonce: tx.nonce, - transaction_hash, - signature: tx.signature, - sender_address: tx.sender_address.into(), - compiled_class_hash: tx.compiled_class_hash, - max_fee: tx.max_fee.try_into().expect("max_fee is too large"), - }), - contract_class, - ) + let (hash, compiled_hash, class) = rpc_to_inner_class(&tx.contract_class).unwrap(); + (hash, Some(compiled_hash), Some(tx.contract_class.as_ref().clone()), class) } - } + }; + + // compute transaction hash + let transaction_hash = match &tx { + BroadcastedDeclareTransaction::V1(tx) => compute_declare_v1_transaction_hash( + tx.sender_address, + class_hash, + tx.max_fee, + chain_id, + tx.nonce, + tx.is_query, + ), + + BroadcastedDeclareTransaction::V2(tx) => compute_declare_v2_transaction_hash( + tx.sender_address, + class_hash, + tx.max_fee, + chain_id, + tx.nonce, + tx.compiled_class_hash, + tx.is_query, + ), + }; + + // extract common fields + let (nonce, max_fee, version, signature, sender_address) = match tx { + BroadcastedDeclareTransaction::V1(tx) => { + (tx.nonce, tx.max_fee, FieldElement::ONE, tx.signature, tx.sender_address) + } + BroadcastedDeclareTransaction::V2(tx) => { + (tx.nonce, tx.max_fee, FieldElement::TWO, tx.signature, tx.sender_address) + } + }; + + let tx = Self { + nonce, + version, + signature, + class_hash, + transaction_hash, + compiled_class_hash, + sender_address: sender_address.into(), + max_fee: max_fee.try_into().expect("max fee is too large"), + }; + + (tx, compiled_class, sierra_class) } } diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index a3dd9df5dc..80aba7e49d 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use starknet::core::types::{Event, MsgToL1}; use crate::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, }; use crate::FieldElement; @@ -17,13 +17,13 @@ pub type TxNumber = u64; pub enum ExecutionTx { Invoke(InvokeTx), L1Handler(L1HandlerTx), - Declare(DeclareTxWithCompiledClass), - DeployAccount(DeployAccountTxWithContractAddress), + Declare(DeclareTxWithClasses), + DeployAccount(DeployAccountTx), } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Transaction { +pub enum Tx { Invoke(InvokeTx), Declare(DeclareTx), L1Handler(L1HandlerTx), @@ -32,55 +32,37 @@ pub enum Transaction { #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum InvokeTx { - V1(InvokeTxV1), -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct InvokeTxV1 { +pub struct InvokeTx { pub transaction_hash: TxHash, pub nonce: Nonce, pub max_fee: u128, + pub version: FieldElement, pub calldata: Vec, pub signature: Vec, pub sender_address: ContractAddress, } #[derive(Debug, Clone)] -pub struct DeclareTxWithCompiledClass(pub DeclareTx, pub CompiledContractClass); - -#[derive(Debug, Clone)] -pub struct DeployAccountTxWithContractAddress(pub DeployAccountTx, pub ContractAddress); - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum DeclareTx { - V1(DeclareTxV1), - V2(DeclareTxV2), +pub struct DeclareTxWithClasses { + pub tx: DeclareTx, + pub sierra_class: Option, + pub compiled_class: CompiledContractClass, } +/// Represents a declare transaction type. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeclareTxV1 { - pub transaction_hash: TxHash, - pub max_fee: u128, - pub nonce: Nonce, - pub class_hash: ClassHash, - pub signature: Vec, - pub sender_address: ContractAddress, -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeclareTxV2 { +pub struct DeclareTx { pub transaction_hash: TxHash, pub max_fee: u128, pub nonce: Nonce, + pub version: FieldElement, + /// The class hash of the contract class to be declared. pub class_hash: ClassHash, pub signature: Vec, pub sender_address: ContractAddress, - pub compiled_class_hash: CompiledClassHash, + /// The compiled class hash of the contract class (only if it's a Sierra class). + pub compiled_class_hash: Option, } #[derive(Debug, Clone)] @@ -89,6 +71,7 @@ pub struct L1HandlerTx { pub transaction_hash: TxHash, pub version: FieldElement, pub nonce: Nonce, + pub paid_fee_on_l1: u128, pub calldata: Vec, pub contract_address: ContractAddress, pub entry_point_selector: FieldElement, @@ -103,6 +86,7 @@ pub struct DeployAccountTx { pub class_hash: ClassHash, pub version: FieldElement, pub signature: Vec, + pub contract_address: ContractAddress, pub contract_address_salt: FieldElement, pub constructor_calldata: Vec, } diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index 48ebdc9e6c..5b50cac449 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -4,7 +4,7 @@ use katana_primitives::contract::{ StorageValue, }; use katana_primitives::serde::blockifier::SerializableContractClass; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; use crate::codecs::{Compress, Decode, Decompress, Encode}; @@ -176,7 +176,7 @@ tables! { /// Transaction hash based on its number TxHashes: (TxNumber) => TxHash, /// Store canonical transactions - Transactions: (TxNumber) => Transaction, + Transactions: (TxNumber) => Tx, /// Store transaction receipts Receipts: (TxNumber) => Receipt, /// Stores the list of class hashes according to the block number it was declared in. diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index 2513fc723b..fa7a1e9552 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -8,7 +8,7 @@ use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; pub mod providers; pub mod traits; @@ -85,7 +85,7 @@ impl TransactionProvider for BlockchainProvider where Db: TransactionProvider, { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { self.provider.transaction_by_hash(hash) } @@ -93,14 +93,11 @@ where &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { self.provider.transaction_by_block_and_idx(block_id, idx) } - fn transactions_by_block( - &self, - block_id: BlockHashOrNumber, - ) -> Result>> { + fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { self.provider.transactions_by_block(block_id) } } diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index e5307e7e70..70c8f116e9 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -10,7 +10,7 @@ use katana_primitives::block::{ Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, }; use katana_primitives::contract::{ContractAddress, GenericContractInfo}; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; use parking_lot::RwLock; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -116,7 +116,7 @@ impl BlockProvider for ForkedProvider { } impl TransactionProvider for ForkedProvider { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { Ok(self .storage .transaction_numbers @@ -124,10 +124,7 @@ impl TransactionProvider for ForkedProvider { .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) } - fn transactions_by_block( - &self, - block_id: BlockHashOrNumber, - ) -> Result>> { + fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), @@ -149,7 +146,7 @@ impl TransactionProvider for ForkedProvider { &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs index a9c664292a..dccfe740e9 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/cache.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -7,7 +7,7 @@ use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; use parking_lot::RwLock; type ContractStorageMap = HashMap<(ContractAddress, StorageKey), StorageValue>; @@ -48,7 +48,7 @@ pub struct CacheDb { pub(crate) latest_block_number: BlockNumber, pub(crate) state_update: HashMap, pub(crate) receipts: Vec, - pub(crate) transactions: Vec, + pub(crate) transactions: Vec, pub(crate) transaction_hashes: HashMap, pub(crate) transaction_numbers: HashMap, } diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 2d5cc1ac33..92f4a49c7b 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -10,7 +10,7 @@ use katana_primitives::block::{ Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, }; use katana_primitives::contract::{ContractAddress, GenericContractInfo}; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; use parking_lot::RwLock; use self::cache::CacheDb; @@ -98,7 +98,7 @@ impl BlockProvider for InMemoryProvider { } impl TransactionProvider for InMemoryProvider { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { Ok(self .storage .transaction_numbers @@ -106,10 +106,7 @@ impl TransactionProvider for InMemoryProvider { .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) } - fn transactions_by_block( - &self, - block_id: BlockHashOrNumber, - ) -> Result>> { + fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), @@ -131,7 +128,7 @@ impl TransactionProvider for InMemoryProvider { &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index a71a0cecc7..77eb6f0a31 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -2,25 +2,22 @@ use std::ops::Range; use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; -use katana_primitives::transaction::{Receipt, Transaction, TxHash, TxNumber}; +use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; #[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionProvider: Send + Sync { /// Returns a transaction given its hash. - fn transaction_by_hash(&self, hash: TxHash) -> Result>; + fn transaction_by_hash(&self, hash: TxHash) -> Result>; /// Returns all the transactions for a given block. - fn transactions_by_block( - &self, - block_id: BlockHashOrNumber, - ) -> Result>>; + fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>>; /// Returns the transaction at the given block and its exact index in the block. fn transaction_by_block_and_idx( &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result>; + ) -> Result>; } #[auto_impl::auto_impl(&, Box, Arc)] From 00edce918575f916a5c780e6db05fa8d6cbca4d4 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 27 Nov 2023 13:14:16 +0800 Subject: [PATCH 087/192] refactor(katana-provider): move `compiled_class_hash_of_class_hash` fn to `StateProvider` trait --- crates/katana/storage/provider/src/lib.rs | 14 +++---- .../provider/src/providers/fork/backend.rs | 28 ++++++------- .../provider/src/providers/fork/state.rs | 40 +++++++++---------- .../provider/src/providers/in_memory/state.rs | 28 ++++++------- .../storage/provider/src/traits/state.rs | 12 +++--- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index fa7a1e9552..476d1e02ac 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -150,6 +150,13 @@ where ) -> Result> { self.provider.storage(address, storage_key) } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + self.provider.compiled_class_hash_of_class_hash(hash) + } } impl StateProviderExt for BlockchainProvider @@ -159,13 +166,6 @@ where fn sierra_class(&self, hash: ClassHash) -> Result> { self.provider.sierra_class(hash) } - - fn compiled_class_hash_of_class_hash( - &self, - hash: ClassHash, - ) -> Result> { - self.provider.compiled_class_hash_of_class_hash(hash) - } } impl StateFactoryProvider for BlockchainProvider diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 6aa2c76c6e..68e1775064 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -393,6 +393,20 @@ impl StateProvider for SharedStateProvider { Ok(Some(class_hash)) } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + if let hash @ Some(_) = self.0.compiled_class_hashes.read().get(&hash) { + return Ok(hash.cloned()); + } + + let compiled_hash = self.0.do_get_compiled_class_hash(hash).unwrap(); + self.0.compiled_class_hashes.write().insert(hash, compiled_hash); + + Ok(Some(hash)) + } } impl StateProviderExt for SharedStateProvider { @@ -415,20 +429,6 @@ impl StateProviderExt for SharedStateProvider { } } } - - fn compiled_class_hash_of_class_hash( - &self, - hash: ClassHash, - ) -> Result> { - if let hash @ Some(_) = self.0.compiled_class_hashes.read().get(&hash) { - return Ok(hash.cloned()); - } - - let compiled_hash = self.0.do_get_compiled_class_hash(hash).unwrap(); - self.0.compiled_class_hashes.write().insert(hash, compiled_hash); - - Ok(Some(hash)) - } } #[cfg(test)] diff --git a/crates/katana/storage/provider/src/providers/fork/state.rs b/crates/katana/storage/provider/src/providers/fork/state.rs index c124adbd35..e6aa8faf44 100644 --- a/crates/katana/storage/provider/src/providers/fork/state.rs +++ b/crates/katana/storage/provider/src/providers/fork/state.rs @@ -55,9 +55,7 @@ impl StateProvider for ForkedStateDb { } StateProvider::storage(&self.db, address, storage_key) } -} -impl StateProviderExt for CacheStateDb { fn compiled_class_hash_of_class_hash( &self, hash: ClassHash, @@ -65,9 +63,11 @@ impl StateProviderExt for CacheStateDb { if let hash @ Some(_) = self.compiled_class_hashes.read().get(&hash) { return Ok(hash.cloned()); } - StateProviderExt::compiled_class_hash_of_class_hash(&self.db, hash) + StateProvider::compiled_class_hash_of_class_hash(&self.db, hash) } +} +impl StateProviderExt for CacheStateDb { fn sierra_class(&self, hash: ClassHash) -> Result> { if let class @ Some(_) = self.shared_contract_classes.sierra_classes.read().get(&hash) { return Ok(class.cloned()); @@ -98,18 +98,18 @@ impl StateProvider for LatestStateProvider { fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { StateProvider::class_hash_of_contract(&self.0, address) } -} - -impl StateProviderExt for LatestStateProvider { - fn sierra_class(&self, hash: ClassHash) -> Result> { - StateProviderExt::sierra_class(&self.0, hash) - } fn compiled_class_hash_of_class_hash( &self, hash: ClassHash, ) -> Result> { - StateProviderExt::compiled_class_hash_of_class_hash(&self.0, hash) + StateProvider::compiled_class_hash_of_class_hash(&self.0, hash) + } +} + +impl StateProviderExt for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + StateProviderExt::sierra_class(&self.0, hash) } } @@ -147,15 +147,6 @@ impl StateProvider for ForkedSnapshot { } StateProvider::class_hash_of_contract(&self.inner.db, address) } -} - -impl StateProviderExt for ForkedSnapshot { - fn sierra_class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.classes.sierra_classes.read().get(&hash).cloned() { - return Ok(class); - } - StateProviderExt::sierra_class(&self.inner.db, hash) - } fn compiled_class_hash_of_class_hash( &self, @@ -164,6 +155,15 @@ impl StateProviderExt for ForkedSnapshot { if let hash @ Some(_) = self.inner.compiled_class_hashes.get(&hash).cloned() { return Ok(hash); } - StateProviderExt::compiled_class_hash_of_class_hash(&self.inner.db, hash) + StateProvider::compiled_class_hash_of_class_hash(&self.inner.db, hash) + } +} + +impl StateProviderExt for ForkedSnapshot { + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.classes.sierra_classes.read().get(&hash).cloned() { + return Ok(class); + } + StateProviderExt::sierra_class(&self.inner.db, hash) } } diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index cad0180068..42324d9cbd 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -138,13 +138,6 @@ impl StateProvider for InMemorySnapshot { let class_hash = self.inner.contract_state.get(&address).map(|info| info.class_hash); Ok(class_hash) } -} - -impl StateProviderExt for InMemorySnapshot { - fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.classes.sierra_classes.read().get(&hash).cloned(); - Ok(class) - } fn compiled_class_hash_of_class_hash( &self, @@ -155,6 +148,13 @@ impl StateProviderExt for InMemorySnapshot { } } +impl StateProviderExt for InMemorySnapshot { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.classes.sierra_classes.read().get(&hash).cloned(); + Ok(class) + } +} + pub(super) struct LatestStateProvider(pub(super) Arc); impl StateProvider for LatestStateProvider { @@ -181,13 +181,6 @@ impl StateProvider for LatestStateProvider { let class_hash = self.0.contract_state.read().get(&address).map(|info| info.class_hash); Ok(class_hash) } -} - -impl StateProviderExt for LatestStateProvider { - fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_contract_classes.sierra_classes.read().get(&hash).cloned(); - Ok(class) - } fn compiled_class_hash_of_class_hash( &self, @@ -198,6 +191,13 @@ impl StateProviderExt for LatestStateProvider { } } +impl StateProviderExt for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_contract_classes.sierra_classes.read().get(&hash).cloned(); + Ok(class) + } +} + #[cfg(test)] mod tests { use katana_primitives::block::BlockHashOrNumber; diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 7cdfc0a4b9..27f62a55da 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -22,6 +22,12 @@ pub trait StateProvider: Send + Sync { /// Returns the class hash of a contract. fn class_hash_of_contract(&self, address: ContractAddress) -> Result>; + + /// Returns the compiled class hash for the given class hash. + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result>; } /// An extension of the `StateProvider` trait which provides additional methods. @@ -29,12 +35,6 @@ pub trait StateProvider: Send + Sync { pub trait StateProviderExt: StateProvider + Send + Sync { /// Retrieves the Sierra class definition of a contract class given its class hash. fn sierra_class(&self, hash: ClassHash) -> Result>; - - /// Returns the compiled class hash for the given class hash. - fn compiled_class_hash_of_class_hash( - &self, - hash: ClassHash, - ) -> Result>; } /// A state factory provider is a provider which can create state providers for From 35dbded5639f64cf4a971d8053935897fb247987 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 27 Nov 2023 14:14:47 +0800 Subject: [PATCH 088/192] refactor(katana-core): refactor out execution logic into separate crate --- Cargo.lock | 15 ++ Cargo.toml | 1 + crates/katana/executor/Cargo.toml | 21 ++ crates/katana/executor/src/blockifier/mod.rs | 167 +++++++++++++++ .../katana/executor/src/blockifier/outcome.rs | 69 ++++++ .../katana/executor/src/blockifier/state.rs | 198 ++++++++++++++++++ .../katana/executor/src/blockifier/utils.rs | 171 +++++++++++++++ crates/katana/executor/src/lib.rs | 1 + 8 files changed, 643 insertions(+) create mode 100644 crates/katana/executor/Cargo.toml create mode 100644 crates/katana/executor/src/blockifier/mod.rs create mode 100644 crates/katana/executor/src/blockifier/outcome.rs create mode 100644 crates/katana/executor/src/blockifier/state.rs create mode 100644 crates/katana/executor/src/blockifier/utils.rs create mode 100644 crates/katana/executor/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index be2103908f..be11983295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5128,6 +5128,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "katana-executor" +version = "0.3.12" +dependencies = [ + "blockifier", + "convert_case 0.6.0", + "katana-primitives", + "katana-provider", + "parking_lot 0.12.1", + "starknet", + "starknet_api", + "tokio", + "tracing", +] + [[package]] name = "katana-primitives" version = "0.3.14" diff --git a/Cargo.toml b/Cargo.toml index de606502c1..65aabd29ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/dojo-world", "crates/katana", "crates/katana/core", + "crates/katana/executor", "crates/katana/primitives", "crates/katana/rpc", "crates/katana/storage/db", diff --git a/crates/katana/executor/Cargo.toml b/crates/katana/executor/Cargo.toml new file mode 100644 index 0000000000..f0df3ad3d6 --- /dev/null +++ b/crates/katana/executor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +description = "Katana execution engine" +edition.workspace = true +name = "katana-executor" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +katana-primitives = { path = "../primitives" } +katana-provider = { path = "../storage/provider" } + +convert_case.workspace = true +parking_lot.workspace = true +starknet.workspace = true +tracing.workspace = true + +# blockifier deps +blockifier.workspace = true +starknet_api.workspace = true +tokio.workspace = true diff --git a/crates/katana/executor/src/blockifier/mod.rs b/crates/katana/executor/src/blockifier/mod.rs new file mode 100644 index 0000000000..35a7d7951d --- /dev/null +++ b/crates/katana/executor/src/blockifier/mod.rs @@ -0,0 +1,167 @@ +pub mod outcome; +pub mod state; +pub mod utils; + +use std::sync::Arc; + +use blockifier::block_context::BlockContext; +use blockifier::state::state_api::StateReader; +use blockifier::transaction::errors::TransactionExecutionError; +use blockifier::transaction::objects::TransactionExecutionInfo; +use blockifier::transaction::transaction_execution::Transaction as BlockifierExecuteTx; +use blockifier::transaction::transactions::ExecutableTransaction; +use katana_primitives::transaction::{DeclareTxWithClasses, ExecutionTx}; +use parking_lot::RwLock; +use tracing::{trace, warn}; + +use self::outcome::ExecutedTx; +use self::state::{CachedStateWrapper, StateRefDb}; +use self::utils::events_from_exec_info; +use crate::blockifier::utils::{ + pretty_print_resources, trace_events, warn_message_transaction_error_exec_error, +}; + +/// The result of a transaction execution. +type TxExecutionResult = Result; + +/// A transaction executor. +/// +/// The transactions will be executed in an iterator fashion, sequentially, in the +/// exact order they are provided to the executor. The execution is done within its +/// implementation of the [`Iterator`] trait. +pub struct TransactionExecutor<'a, S: StateReader> { + /// A flag to enable/disable fee charging. + charge_fee: bool, + /// The block context the transactions will be executed on. + block_context: &'a BlockContext, + /// The transactions to be executed (in the exact order they are in the iterator). + transactions: std::vec::IntoIter, + /// The state the transactions will be executed on. + state: &'a mut CachedStateWrapper, + + // logs flags + error_log: bool, + events_log: bool, + resources_log: bool, +} + +impl<'a, S: StateReader> TransactionExecutor<'a, S> { + pub fn new( + state: &'a mut CachedStateWrapper, + block_context: &'a BlockContext, + charge_fee: bool, + transactions: Vec, + ) -> Self { + Self { + state, + charge_fee, + block_context, + error_log: false, + events_log: false, + resources_log: false, + transactions: transactions.into_iter(), + } + } + + pub fn with_events_log(self) -> Self { + Self { events_log: true, ..self } + } + + pub fn with_error_log(self) -> Self { + Self { error_log: true, ..self } + } + + pub fn with_resources_log(self) -> Self { + Self { resources_log: true, ..self } + } + + /// A method to conveniently execute all the transactions and return their results. + pub fn execute(self) -> Vec { + self.collect() + } +} + +impl<'a, S: StateReader> Iterator for TransactionExecutor<'a, S> { + type Item = TxExecutionResult; + fn next(&mut self) -> Option { + self.transactions.next().map(|tx| { + let res = execute_tx(tx, &mut self.state, self.block_context, self.charge_fee); + + match res { + Ok(info) => { + if self.error_log { + if let Some(err) = &info.revert_error { + let formatted_err = format!("{err:?}").replace("\\n", "\n"); + warn!(target: "executor", "Transaction execution error: {formatted_err}"); + } + } + + if self.resources_log { + trace!( + target: "executor", + "Transaction resource usage: {}", + pretty_print_resources(&info.actual_resources) + ); + } + + if self.events_log { + trace_events(&events_from_exec_info(&info)); + } + + Ok(info) + } + + Err(err) => { + if self.error_log { + warn_message_transaction_error_exec_error(&err); + } + + Err(err) + } + } + }) + } +} + +pub struct PendingState { + pub state: RwLock>, + /// The transactions that have been executed. + pub executed_transactions: RwLock>>, +} + +fn execute_tx( + tx: ExecutionTx, + state: &mut CachedStateWrapper, + block_context: &BlockContext, + charge_fee: bool, +) -> TxExecutionResult { + let sierra = if let ExecutionTx::Declare(DeclareTxWithClasses { + tx, + sierra_class: Some(sierra_class), + .. + }) = &tx + { + Some((tx.class_hash, sierra_class.clone())) + } else { + None + }; + + let res = match tx.into() { + BlockifierExecuteTx::AccountTransaction(tx) => { + tx.execute(&mut state.inner_mut(), block_context, charge_fee) + } + BlockifierExecuteTx::L1HandlerTransaction(tx) => { + tx.execute(&mut state.inner_mut(), block_context, charge_fee) + } + }; + + if let res @ Ok(_) = res { + if let Some((class_hash, sierra_class)) = sierra { + state.sierra_class_mut().insert(class_hash, sierra_class); + } + + res + } else { + res + } +} diff --git a/crates/katana/executor/src/blockifier/outcome.rs b/crates/katana/executor/src/blockifier/outcome.rs new file mode 100644 index 0000000000..27c98a185d --- /dev/null +++ b/crates/katana/executor/src/blockifier/outcome.rs @@ -0,0 +1,69 @@ +use std::collections::HashMap; + +use blockifier::state::cached_state::CommitmentStateDiff; +use blockifier::transaction::objects::TransactionExecutionInfo; +use katana_primitives::contract::{ClassHash, CompiledContractClass, ContractAddress, SierraClass}; +use katana_primitives::transaction::{Receipt, Tx}; + +use super::utils::{events_from_exec_info, l2_to_l1_messages_from_exec_info}; + +pub struct ExecutedTx { + pub tx: Tx, + pub receipt: Receipt, + pub execution_info: TransactionExecutionInfo, +} + +impl ExecutedTx { + pub(super) fn new(tx: Tx, execution_info: TransactionExecutionInfo) -> Self { + let actual_fee = execution_info.actual_fee.0; + let events = events_from_exec_info(&execution_info); + let revert_error = execution_info.revert_error.clone(); + let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info); + let actual_resources = execution_info.actual_resources.0.clone(); + + let contract_address = if let Tx::DeployAccount(ref tx) = tx { + Some(ContractAddress(tx.contract_address.into())) + } else { + None + }; + + Self { + tx, + execution_info, + receipt: Receipt { + events, + actual_fee, + revert_error, + messages_sent, + actual_resources, + contract_address, + }, + } + } +} + +/// The outcome that after executing a list of transactions. +pub struct ExecutionOutcome { + pub transactions: Vec, + pub state_diff: CommitmentStateDiff, + pub declared_classes: HashMap, + pub declared_sierra_classes: HashMap, +} + +impl Default for ExecutionOutcome { + fn default() -> Self { + let state_diff = CommitmentStateDiff { + storage_updates: Default::default(), + address_to_nonce: Default::default(), + address_to_class_hash: Default::default(), + class_hash_to_compiled_class_hash: Default::default(), + }; + + Self { + state_diff, + transactions: Default::default(), + declared_classes: Default::default(), + declared_sierra_classes: Default::default(), + } + } +} diff --git a/crates/katana/executor/src/blockifier/state.rs b/crates/katana/executor/src/blockifier/state.rs new file mode 100644 index 0000000000..4bdb053fb7 --- /dev/null +++ b/crates/katana/executor/src/blockifier/state.rs @@ -0,0 +1,198 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::cached_state::{CachedState, CommitmentStateDiff}; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{State, StateReader, StateResult}; +use katana_primitives::contract::SierraClass; +use katana_provider::traits::state::StateProvider; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; +use tokio::sync::RwLock as AsyncRwLock; + +pub struct StateRefDb(Box); + +impl StateRefDb { + pub fn new(provider: impl StateProvider + 'static) -> Self { + Self(Box::new(provider)) + } +} + +impl From for StateRefDb +where + T: StateProvider + 'static, +{ + fn from(provider: T) -> Self { + Self::new(provider) + } +} + +impl StateReader for StateRefDb { + fn get_nonce_at( + &mut self, + contract_address: starknet_api::core::ContractAddress, + ) -> blockifier::state::state_api::StateResult { + StateProvider::nonce(&self.0, contract_address.into()) + .map(|n| Nonce(n.unwrap_or_default().into())) + .map_err(|e| StateError::StateReadError(e.to_string())) + } + + fn get_storage_at( + &mut self, + contract_address: starknet_api::core::ContractAddress, + key: starknet_api::state::StorageKey, + ) -> blockifier::state::state_api::StateResult { + StateProvider::storage(&self.0, contract_address.into(), (*key.0.key()).into()) + .map(|v| v.unwrap_or_default().into()) + .map_err(|e| StateError::StateReadError(e.to_string())) + } + + fn get_class_hash_at( + &mut self, + contract_address: starknet_api::core::ContractAddress, + ) -> blockifier::state::state_api::StateResult { + StateProvider::class_hash_of_contract(&self.0, contract_address.into()) + .map(|v| ClassHash(v.unwrap_or_default().into())) + .map_err(|e| StateError::StateReadError(e.to_string())) + } + + fn get_compiled_class_hash( + &mut self, + class_hash: starknet_api::core::ClassHash, + ) -> blockifier::state::state_api::StateResult { + if let Some(hash) = + StateProvider::compiled_class_hash_of_class_hash(&self.0, class_hash.0.into()) + .map_err(|e| StateError::StateReadError(e.to_string()))? + { + Ok(CompiledClassHash(hash.into())) + } else { + Err(StateError::UndeclaredClassHash(class_hash)) + } + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &starknet_api::core::ClassHash, + ) -> blockifier::state::state_api::StateResult< + blockifier::execution::contract_class::ContractClass, + > { + if let Some(class) = StateProvider::class(&self.0, class_hash.0.into()) + .map_err(|e| StateError::StateReadError(e.to_string()))? + { + Ok(class) + } else { + Err(StateError::UndeclaredClassHash(*class_hash)) + } + } +} + +#[derive(Clone)] +pub struct CachedStateWrapper { + inner: Arc>>, + sierra_class: Arc>>, +} + +impl CachedStateWrapper { + pub fn new(db: S) -> Self { + Self { + sierra_class: Default::default(), + inner: Arc::new(AsyncRwLock::new(CachedState::new(db))), + } + } + + pub fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, CachedState> { + tokio::task::block_in_place(|| self.inner.blocking_write()) + } + + pub fn sierra_class( + &self, + ) -> tokio::sync::RwLockReadGuard< + '_, + HashMap, + > { + tokio::task::block_in_place(|| self.sierra_class.blocking_read()) + } + + pub fn sierra_class_mut( + &self, + ) -> tokio::sync::RwLockWriteGuard< + '_, + HashMap, + > { + tokio::task::block_in_place(|| self.sierra_class.blocking_write()) + } +} + +impl State for CachedStateWrapper { + fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { + self.inner_mut().increment_nonce(contract_address) + } + + fn set_class_hash_at( + &mut self, + contract_address: ContractAddress, + class_hash: ClassHash, + ) -> StateResult<()> { + self.inner_mut().set_class_hash_at(contract_address, class_hash) + } + + fn set_compiled_class_hash( + &mut self, + class_hash: ClassHash, + compiled_class_hash: CompiledClassHash, + ) -> StateResult<()> { + self.inner_mut().set_compiled_class_hash(class_hash, compiled_class_hash) + } + + fn set_contract_class( + &mut self, + class_hash: &ClassHash, + contract_class: ContractClass, + ) -> StateResult<()> { + self.inner_mut().set_contract_class(class_hash, contract_class) + } + + fn set_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + value: StarkFelt, + ) { + self.inner_mut().set_storage_at(contract_address, key, value) + } + + fn to_state_diff(&self) -> CommitmentStateDiff { + self.inner_mut().to_state_diff() + } +} + +impl StateReader for CachedStateWrapper { + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.inner_mut().get_class_hash_at(contract_address) + } + + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + self.inner_mut().get_compiled_class_hash(class_hash) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.inner_mut().get_compiled_contract_class(class_hash) + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.inner_mut().get_nonce_at(contract_address) + } + + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.inner_mut().get_storage_at(contract_address, key) + } +} diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs new file mode 100644 index 0000000000..1d0f1a8bf7 --- /dev/null +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -0,0 +1,171 @@ +use std::collections::HashMap; + +use blockifier::execution::entry_point::CallInfo; +use blockifier::execution::errors::EntryPointExecutionError; +use blockifier::state::state_api::{State, StateReader}; +use blockifier::transaction::errors::TransactionExecutionError; +use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; +use convert_case::{Case, Casing}; +use katana_primitives::transaction::Tx; +use katana_primitives::FieldElement; +use starknet::core::types::{Event, MsgToL1}; +use starknet::core::utils::parse_cairo_short_string; +use tracing::trace; + +use super::outcome::{ExecutedTx, ExecutionOutcome}; +use super::state::{CachedStateWrapper, StateRefDb}; + +pub(crate) fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { + match err { + TransactionExecutionError::EntryPointExecutionError(ref eperr) + | TransactionExecutionError::ExecutionError(ref eperr) => match eperr { + EntryPointExecutionError::ExecutionFailed { error_data } => { + let mut reasons: Vec = vec![]; + error_data.iter().for_each(|felt| { + if let Ok(s) = parse_cairo_short_string(&FieldElement::from(*felt)) { + reasons.push(s); + } + }); + + tracing::warn!(target: "executor", + "Transaction validation error: {}", reasons.join(" ")); + } + _ => tracing::warn!(target: "executor", + "Transaction validation error: {:?}", err), + }, + _ => tracing::warn!(target: "executor", + "Transaction validation error: {:?}", err), + } +} + +pub(crate) fn pretty_print_resources(resources: &ResourcesMapping) -> String { + let mut mapped_strings: Vec<_> = resources + .0 + .iter() + .filter_map(|(k, v)| match k.as_str() { + "l1_gas_usage" => Some(format!("L1 Gas: {}", v)), + "range_check_builtin" => Some(format!("Range Checks: {}", v)), + "ecdsa_builtin" => Some(format!("ECDSA: {}", v)), + "n_steps" => None, + "pedersen_builtin" => Some(format!("Pedersen: {}", v)), + "bitwise_builtin" => Some(format!("Bitwise: {}", v)), + "keccak_builtin" => Some(format!("Keccak: {}", v)), + _ => Some(format!("{}: {}", k.to_case(Case::Title), v)), + }) + .collect::>(); + + // Sort the strings alphabetically + mapped_strings.sort(); + + // Prepend "Steps" if it exists, so it is always first + if let Some(steps) = resources.0.get("n_steps") { + mapped_strings.insert(0, format!("Steps: {}", steps)); + } + + mapped_strings.join(" | ") +} + +pub(crate) fn trace_events(events: &[Event]) { + for e in events { + let formatted_keys = + e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); + + trace!(target: "executor", "Event emitted keys=[{}]", formatted_keys); + } +} + +pub fn create_execution_outcome( + state: &mut CachedStateWrapper, + executed_txs: Vec<(Tx, TransactionExecutionInfo)>, +) -> ExecutionOutcome { + let transactions = executed_txs.into_iter().map(|(tx, res)| ExecutedTx::new(tx, res)).collect(); + let state_diff = state.to_state_diff(); + let declared_classes = state_diff + .class_hash_to_compiled_class_hash + .iter() + .map(|(class_hash, _)| { + let contract_class = state + .get_compiled_contract_class(class_hash) + .expect("qed; class must exist if declared"); + (class_hash.0.into(), contract_class) + }) + .collect::>(); + + ExecutionOutcome { + state_diff, + transactions, + declared_classes, + declared_sierra_classes: state.sierra_class().clone(), + } +} + +pub(crate) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { + let mut events: Vec = vec![]; + + fn get_events_recursively(call_info: &CallInfo) -> Vec { + let mut events: Vec = vec![]; + + events.extend(call_info.execution.events.iter().map(|e| Event { + from_address: (*call_info.call.storage_address.0.key()).into(), + data: e.event.data.0.iter().map(|d| (*d).into()).collect(), + keys: e.event.keys.iter().map(|k| k.0.into()).collect(), + })); + + call_info.inner_calls.iter().for_each(|call| { + events.extend(get_events_recursively(call)); + }); + + events + } + + if let Some(ref call) = execution_info.validate_call_info { + events.extend(get_events_recursively(call)); + } + + if let Some(ref call) = execution_info.execute_call_info { + events.extend(get_events_recursively(call)); + } + + if let Some(ref call) = execution_info.fee_transfer_call_info { + events.extend(get_events_recursively(call)); + } + + events +} + +pub(crate) fn l2_to_l1_messages_from_exec_info( + execution_info: &TransactionExecutionInfo, +) -> Vec { + let mut messages = vec![]; + + fn get_messages_recursively(info: &CallInfo) -> Vec { + let mut messages = vec![]; + + messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MsgToL1 { + to_address: + FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), + from_address: (*info.call.caller_address.0.key()).into(), + payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), + })); + + info.inner_calls.iter().for_each(|call| { + messages.extend(get_messages_recursively(call)); + }); + + messages + } + + if let Some(ref info) = execution_info.validate_call_info { + messages.extend(get_messages_recursively(info)); + } + + if let Some(ref info) = execution_info.execute_call_info { + messages.extend(get_messages_recursively(info)); + } + + if let Some(ref info) = execution_info.fee_transfer_call_info { + messages.extend(get_messages_recursively(info)); + } + + messages +} diff --git a/crates/katana/executor/src/lib.rs b/crates/katana/executor/src/lib.rs new file mode 100644 index 0000000000..22486ef41d --- /dev/null +++ b/crates/katana/executor/src/lib.rs @@ -0,0 +1 @@ +pub mod blockifier; From b895aa74ee072719a482f50ce58bae53e6eeec8c Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 28 Nov 2023 11:10:42 +0800 Subject: [PATCH 089/192] refactor(katana-provider): add contract class provider --- crates/katana/storage/provider/src/lib.rs | 27 ++-- .../provider/src/providers/fork/backend.rs | 139 +++++++++--------- .../provider/src/providers/fork/mod.rs | 7 +- .../provider/src/providers/fork/state.rs | 95 +++++++----- .../provider/src/providers/in_memory/cache.rs | 4 +- .../provider/src/providers/in_memory/mod.rs | 16 +- .../provider/src/providers/in_memory/state.rs | 77 ++++++---- .../storage/provider/src/traits/contract.rs | 41 +++++- .../storage/provider/src/traits/state.rs | 47 +++--- 9 files changed, 277 insertions(+), 176 deletions(-) diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index 476d1e02ac..36aad5bebc 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -9,13 +9,14 @@ use katana_primitives::contract::{ SierraClass, StorageKey, StorageValue, }; use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use traits::contract::ContractClassProvider; pub mod providers; pub mod traits; use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; -use crate::traits::contract::ContractProvider; -use crate::traits::state::{StateFactoryProvider, StateProvider, StateProviderExt}; +use crate::traits::contract::ContractInfoProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; @@ -128,10 +129,6 @@ impl StateProvider for BlockchainProvider where Db: StateProvider, { - fn class(&self, hash: ClassHash) -> Result> { - self.provider.class(hash) - } - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { self.provider.class_hash_of_contract(address) } @@ -150,19 +147,23 @@ where ) -> Result> { self.provider.storage(address, storage_key) } +} +impl ContractClassProvider for BlockchainProvider +where + Db: ContractClassProvider, +{ fn compiled_class_hash_of_class_hash( &self, hash: ClassHash, ) -> Result> { self.provider.compiled_class_hash_of_class_hash(hash) } -} -impl StateProviderExt for BlockchainProvider -where - Db: StateProviderExt, -{ + fn class(&self, hash: ClassHash) -> Result> { + self.provider.class(hash) + } + fn sierra_class(&self, hash: ClassHash) -> Result> { self.provider.sierra_class(hash) } @@ -190,9 +191,9 @@ where } } -impl ContractProvider for BlockchainProvider +impl ContractInfoProvider for BlockchainProvider where - Db: ContractProvider, + Db: ContractInfoProvider, { fn contract(&self, address: ContractAddress) -> Result> { self.provider.contract(address) diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 68e1775064..ec27ff7fe2 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -12,8 +12,8 @@ use futures::stream::Stream; use futures::{Future, FutureExt}; use katana_primitives::block::BlockHashOrNumber; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, - StorageKey, StorageValue, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + Nonce, SierraClass, StorageKey, StorageValue, }; use katana_primitives::conversion::rpc::{ compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, @@ -26,7 +26,8 @@ use starknet::providers::{JsonRpcClient, Provider, ProviderError}; use tracing::trace; use crate::providers::in_memory::cache::CacheStateDb; -use crate::traits::state::{StateProvider, StateProviderExt}; +use crate::traits::contract::{ContractClassProvider, ContractInfoProvider}; +use crate::traits::state::StateProvider; type GetNonceResult = Result; type GetStorageResult = Result; @@ -316,56 +317,26 @@ impl SharedStateProvider { } } -impl StateProvider for SharedStateProvider { - fn nonce(&self, address: ContractAddress) -> Result> { - if let Some(nonce) = self.0.contract_state.read().get(&address).map(|c| c.nonce) { - return Ok(Some(nonce)); +impl ContractInfoProvider for SharedStateProvider { + fn contract(&self, address: ContractAddress) -> Result> { + if let Some(info) = self.0.contract_state.read().get(&address).cloned() { + return Ok(Some(info)); } let nonce = self.0.do_get_nonce(address).unwrap(); - self.0.contract_state.write().entry(address).or_default().nonce = nonce; - - Ok(Some(nonce)) - } - - fn class(&self, hash: ClassHash) -> Result> { - if let Some(class) = - self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned() - { - return Ok(Some(class)); - } - - let class = self.0.do_get_class_at(hash).unwrap(); - let (class_hash, compiled_class_hash, casm, sierra) = match class { - ContractClass::Legacy(class) => { - let (_, compiled_class) = legacy_rpc_to_inner_class(&class)?; - (hash, hash, compiled_class, None) - } - ContractClass::Sierra(sierra_class) => { - let (_, compiled_class_hash, compiled_class) = rpc_to_inner_class(&sierra_class)?; - (hash, compiled_class_hash, compiled_class, Some(sierra_class)) - } - }; - - self.0.compiled_class_hashes.write().insert(class_hash, compiled_class_hash); + let class_hash = self.0.do_get_class_hash_at(address).unwrap(); + let info = GenericContractInfo { nonce, class_hash }; - self.0 - .shared_contract_classes - .compiled_classes - .write() - .entry(class_hash) - .or_insert(casm.clone()); + self.0.contract_state.write().insert(address, info.clone()); - if let Some(sierra) = sierra { - self.0 - .shared_contract_classes - .sierra_classes - .write() - .entry(class_hash) - .or_insert(sierra); - } + Ok(Some(info)) + } +} - Ok(Some(casm)) +impl StateProvider for SharedStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + let nonce = ContractInfoProvider::contract(&self, address)?.map(|i| i.nonce); + Ok(nonce) } fn storage( @@ -384,14 +355,30 @@ impl StateProvider for SharedStateProvider { } fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - if let Some(hash) = self.0.contract_state.read().get(&address).map(|c| c.class_hash) { - return Ok(Some(hash)); + let hash = ContractInfoProvider::contract(&self, address)?.map(|i| i.class_hash); + Ok(hash) + } +} + +impl ContractClassProvider for SharedStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.0.shared_contract_classes.sierra_classes.read().get(&hash) { + return Ok(class.cloned()); } - let class_hash = self.0.do_get_class_hash_at(address).unwrap(); - self.0.contract_state.write().entry(address).or_default().class_hash = class_hash; + let class = self.0.do_get_class_at(hash).unwrap(); + match class { + starknet::core::types::ContractClass::Legacy(_) => Ok(None), + starknet::core::types::ContractClass::Sierra(sierra_class) => { + self.0 + .shared_contract_classes + .sierra_classes + .write() + .insert(hash, sierra_class.clone()); - Ok(Some(class_hash)) + Ok(Some(sierra_class)) + } + } } fn compiled_class_hash_of_class_hash( @@ -407,27 +394,45 @@ impl StateProvider for SharedStateProvider { Ok(Some(hash)) } -} -impl StateProviderExt for SharedStateProvider { - fn sierra_class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.0.shared_contract_classes.sierra_classes.read().get(&hash) { - return Ok(class.cloned()); + fn class(&self, hash: ClassHash) -> Result> { + if let Some(class) = + self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned() + { + return Ok(Some(class)); } let class = self.0.do_get_class_at(hash).unwrap(); - match class { - starknet::core::types::ContractClass::Legacy(_) => Ok(None), - starknet::core::types::ContractClass::Sierra(sierra_class) => { - self.0 - .shared_contract_classes - .sierra_classes - .write() - .insert(hash, sierra_class.clone()); - - Ok(Some(sierra_class)) + let (class_hash, compiled_class_hash, casm, sierra) = match class { + ContractClass::Legacy(class) => { + let (_, compiled_class) = legacy_rpc_to_inner_class(&class)?; + (hash, hash, compiled_class, None) + } + ContractClass::Sierra(sierra_class) => { + let (_, compiled_class_hash, compiled_class) = rpc_to_inner_class(&sierra_class)?; + (hash, compiled_class_hash, compiled_class, Some(sierra_class)) } + }; + + self.0.compiled_class_hashes.write().insert(class_hash, compiled_class_hash); + + self.0 + .shared_contract_classes + .compiled_classes + .write() + .entry(class_hash) + .or_insert(casm.clone()); + + if let Some(sierra) = sierra { + self.0 + .shared_contract_classes + .sierra_classes + .write() + .entry(class_hash) + .or_insert(sierra); } + + Ok(Some(casm)) } } diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 70c8f116e9..838dfed6a7 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -20,7 +20,7 @@ use self::state::ForkedStateDb; use super::in_memory::cache::{CacheDb, CacheStateDb}; use super::in_memory::state::HistoricalStates; use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; -use crate::traits::contract::ContractProvider; +use crate::traits::contract::ContractInfoProvider; use crate::traits::state::{StateFactoryProvider, StateProvider}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; @@ -100,8 +100,9 @@ impl BlockProvider for ForkedProvider { }; let body = self.transactions_by_block(id)?.unwrap_or_default(); + let status = self.storage.block_statusses.get(&header.number).cloned().expect("must have"); - Ok(Some(Block { header, body })) + Ok(Some(Block { header, body, status })) } fn blocks_in_range(&self, range: RangeInclusive) -> Result> { @@ -209,7 +210,7 @@ impl ReceiptProvider for ForkedProvider { } } -impl ContractProvider for ForkedProvider { +impl ContractInfoProvider for ForkedProvider { fn contract(&self, address: ContractAddress) -> Result> { let contract = self.state.contract_state.read().get(&address).cloned(); Ok(contract) diff --git a/crates/katana/storage/provider/src/providers/fork/state.rs b/crates/katana/storage/provider/src/providers/fork/state.rs index e6aa8faf44..ced68bee25 100644 --- a/crates/katana/storage/provider/src/providers/fork/state.rs +++ b/crates/katana/storage/provider/src/providers/fork/state.rs @@ -2,14 +2,15 @@ use std::sync::Arc; use anyhow::Result; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, - StorageKey, StorageValue, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + Nonce, SierraClass, StorageKey, StorageValue, }; use super::backend::SharedStateProvider; use crate::providers::in_memory::cache::CacheStateDb; use crate::providers::in_memory::state::StateSnapshot; -use crate::traits::state::{StateProvider, StateProviderExt}; +use crate::traits::contract::{ContractClassProvider, ContractInfoProvider}; +use crate::traits::state::StateProvider; pub type ForkedStateDb = CacheStateDb; pub type ForkedSnapshot = StateSnapshot; @@ -23,14 +24,16 @@ impl ForkedStateDb { } } -impl StateProvider for ForkedStateDb { - fn class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.shared_contract_classes.compiled_classes.read().get(&hash) { - return Ok(class.cloned()); +impl ContractInfoProvider for ForkedStateDb { + fn contract(&self, address: ContractAddress) -> Result> { + if let info @ Some(_) = self.contract_state.read().get(&address).cloned() { + return Ok(info); } - StateProvider::class(&self.db, hash) + ContractInfoProvider::contract(&self.db, address) } +} +impl StateProvider for ForkedStateDb { fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { if let hash @ Some(_) = self.contract_state.read().get(&address).map(|i| i.class_hash) { return Ok(hash); @@ -55,6 +58,15 @@ impl StateProvider for ForkedStateDb { } StateProvider::storage(&self.db, address, storage_key) } +} + +impl ContractClassProvider for CacheStateDb { + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.shared_contract_classes.sierra_classes.read().get(&hash) { + return Ok(class.cloned()); + } + ContractClassProvider::sierra_class(&self.db, hash) + } fn compiled_class_hash_of_class_hash( &self, @@ -63,21 +75,25 @@ impl StateProvider for ForkedStateDb { if let hash @ Some(_) = self.compiled_class_hashes.read().get(&hash) { return Ok(hash.cloned()); } - StateProvider::compiled_class_hash_of_class_hash(&self.db, hash) + ContractClassProvider::compiled_class_hash_of_class_hash(&self.db, hash) } -} -impl StateProviderExt for CacheStateDb { - fn sierra_class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.shared_contract_classes.sierra_classes.read().get(&hash) { + fn class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.shared_contract_classes.compiled_classes.read().get(&hash) { return Ok(class.cloned()); } - StateProviderExt::sierra_class(&self.db, hash) + ContractClassProvider::class(&self.db, hash) } } pub(super) struct LatestStateProvider(pub(super) Arc); +impl ContractInfoProvider for LatestStateProvider { + fn contract(&self, address: ContractAddress) -> Result> { + ContractInfoProvider::contract(&self.0, address) + } +} + impl StateProvider for LatestStateProvider { fn nonce(&self, address: ContractAddress) -> Result> { StateProvider::nonce(&self.0, address) @@ -91,25 +107,34 @@ impl StateProvider for LatestStateProvider { StateProvider::storage(&self.0, address, storage_key) } - fn class(&self, hash: ClassHash) -> Result> { - StateProvider::class(&self.0, hash) - } - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { StateProvider::class_hash_of_contract(&self.0, address) } +} + +impl ContractClassProvider for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + ContractClassProvider::sierra_class(&self.0, hash) + } + + fn class(&self, hash: ClassHash) -> Result> { + ContractClassProvider::class(&self.0, hash) + } fn compiled_class_hash_of_class_hash( &self, hash: ClassHash, ) -> Result> { - StateProvider::compiled_class_hash_of_class_hash(&self.0, hash) + ContractClassProvider::compiled_class_hash_of_class_hash(&self.0, hash) } } -impl StateProviderExt for LatestStateProvider { - fn sierra_class(&self, hash: ClassHash) -> Result> { - StateProviderExt::sierra_class(&self.0, hash) +impl ContractInfoProvider for ForkedSnapshot { + fn contract(&self, address: ContractAddress) -> Result> { + if let info @ Some(_) = self.inner.contract_state.get(&address).cloned() { + return Ok(info); + } + ContractInfoProvider::contract(&self.inner.db, address) } } @@ -132,13 +157,6 @@ impl StateProvider for ForkedSnapshot { StateProvider::storage(&self.inner.db, address, storage_key) } - fn class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.classes.compiled_classes.read().get(&hash).cloned() { - return Ok(class); - } - StateProvider::class(&self.inner.db, hash) - } - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { if let class_hash @ Some(_) = self.inner.contract_state.get(&address).map(|info| info.class_hash) @@ -147,6 +165,15 @@ impl StateProvider for ForkedSnapshot { } StateProvider::class_hash_of_contract(&self.inner.db, address) } +} + +impl ContractClassProvider for ForkedSnapshot { + fn sierra_class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.classes.sierra_classes.read().get(&hash).cloned() { + return Ok(class); + } + ContractClassProvider::sierra_class(&self.inner.db, hash) + } fn compiled_class_hash_of_class_hash( &self, @@ -155,15 +182,13 @@ impl StateProvider for ForkedSnapshot { if let hash @ Some(_) = self.inner.compiled_class_hashes.get(&hash).cloned() { return Ok(hash); } - StateProvider::compiled_class_hash_of_class_hash(&self.inner.db, hash) + ContractClassProvider::compiled_class_hash_of_class_hash(&self.inner.db, hash) } -} -impl StateProviderExt for ForkedSnapshot { - fn sierra_class(&self, hash: ClassHash) -> Result> { - if let class @ Some(_) = self.classes.sierra_classes.read().get(&hash).cloned() { + fn class(&self, hash: ClassHash) -> Result> { + if let class @ Some(_) = self.classes.compiled_classes.read().get(&hash).cloned() { return Ok(class); } - StateProviderExt::sierra_class(&self.inner.db, hash) + ContractClassProvider::class(&self.inner.db, hash) } } diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs index dccfe740e9..4889c0e64a 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/cache.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; use katana_db::models::block::StoredBlockBodyIndices; -use katana_primitives::block::{BlockHash, BlockNumber, Header, StateUpdate}; +use katana_primitives::block::{BlockHash, BlockNumber, BlockStatus, Header, StateUpdate}; use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, @@ -43,6 +43,7 @@ pub struct CacheDb { pub(crate) block_headers: HashMap, pub(crate) block_hashes: HashMap, pub(crate) block_numbers: HashMap, + pub(crate) block_statusses: HashMap, pub(crate) block_body_indices: HashMap, pub(crate) latest_block_hash: BlockHash, pub(crate) latest_block_number: BlockNumber, @@ -75,6 +76,7 @@ impl CacheDb { block_hashes: HashMap::new(), block_headers: HashMap::new(), block_numbers: HashMap::new(), + block_statusses: HashMap::new(), transaction_hashes: HashMap::new(), block_body_indices: HashMap::new(), transaction_numbers: HashMap::new(), diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 92f4a49c7b..8c0791ddec 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -16,7 +16,7 @@ use parking_lot::RwLock; use self::cache::CacheDb; use self::state::{HistoricalStates, InMemoryStateDb, LatestStateProvider}; use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; -use crate::traits::contract::ContractProvider; +use crate::traits::contract::ContractInfoProvider; use crate::traits::state::{StateFactoryProvider, StateProvider}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; @@ -27,6 +27,15 @@ pub struct InMemoryProvider { historical_states: RwLock, } +impl InMemoryProvider { + pub fn new() -> Self { + let storage = CacheDb::new(()); + let state = Arc::new(InMemoryStateDb::new(())); + let historical_states = RwLock::new(HistoricalStates::default()); + Self { storage, state, historical_states } + } +} + impl BlockHashProvider for InMemoryProvider { fn latest_hash(&self) -> Result { Ok(self.storage.latest_block_hash) @@ -82,8 +91,9 @@ impl BlockProvider for InMemoryProvider { }; let body = self.transactions_by_block(id)?.unwrap_or_default(); + let status = self.storage.block_statusses.get(&header.number).cloned().expect("must have"); - Ok(Some(Block { header, body })) + Ok(Some(Block { header, body, status })) } fn blocks_in_range(&self, range: RangeInclusive) -> Result> { @@ -191,7 +201,7 @@ impl ReceiptProvider for InMemoryProvider { } } -impl ContractProvider for InMemoryProvider { +impl ContractInfoProvider for InMemoryProvider { fn contract(&self, address: ContractAddress) -> Result> { let contract = self.state.contract_state.read().get(&address).cloned(); Ok(contract) diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index 42324d9cbd..1dc3e1b195 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -4,12 +4,13 @@ use std::sync::Arc; use anyhow::Result; use katana_primitives::block::BlockNumber; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, - StorageKey, StorageValue, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + Nonce, SierraClass, StorageKey, StorageValue, }; use super::cache::{CacheSnapshotWithoutClasses, CacheStateDb, SharedContractClasses}; -use crate::traits::state::{StateProvider, StateProviderExt}; +use crate::traits::contract::{ContractClassProvider, ContractInfoProvider}; +use crate::traits::state::StateProvider; pub struct StateSnapshot { pub(crate) classes: Arc, @@ -24,7 +25,7 @@ const MIN_HISTORY_LIMIT: usize = 10; /// It should store at N - 1 states, where N is the latest block number. pub struct HistoricalStates { /// The states at a certain block based on the block number - states: HashMap>, + states: HashMap>, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory @@ -44,7 +45,7 @@ impl HistoricalStates { } /// Returns the state for the given `block_hash` if present - pub fn get(&self, block_num: &BlockNumber) -> Option<&Arc> { + pub fn get(&self, block_num: &BlockNumber) -> Option<&Arc> { self.states.get(block_num) } @@ -56,7 +57,7 @@ impl HistoricalStates { /// Since we keep a snapshot of the entire state as history, the size of the state will increase /// with the transactions processed. To counter this, we gradually decrease the cache limit with /// the number of states/blocks until we reached the `min_limit`. - pub fn insert(&mut self, block_num: BlockNumber, state: Box) { + pub fn insert(&mut self, block_num: BlockNumber, state: Box) { if self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = @@ -114,9 +115,16 @@ impl InMemoryStateDb { } } +impl ContractInfoProvider for InMemorySnapshot { + fn contract(&self, address: ContractAddress) -> Result> { + let info = self.inner.contract_state.get(&address).cloned(); + Ok(info) + } +} + impl StateProvider for InMemorySnapshot { fn nonce(&self, address: ContractAddress) -> Result> { - let nonce = self.inner.contract_state.get(&address).map(|info| info.nonce); + let nonce = ContractInfoProvider::contract(&self, address)?.map(|i| i.nonce); Ok(nonce) } @@ -129,14 +137,21 @@ impl StateProvider for InMemorySnapshot { Ok(value) } - fn class(&self, hash: ClassHash) -> Result> { - let class = self.classes.compiled_classes.read().get(&hash).cloned(); + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + let class_hash = ContractInfoProvider::contract(&self, address)?.map(|i| i.class_hash); + Ok(class_hash) + } +} + +impl ContractClassProvider for InMemorySnapshot { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.classes.sierra_classes.read().get(&hash).cloned(); Ok(class) } - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - let class_hash = self.inner.contract_state.get(&address).map(|info| info.class_hash); - Ok(class_hash) + fn class(&self, hash: ClassHash) -> Result> { + let class = self.classes.compiled_classes.read().get(&hash).cloned(); + Ok(class) } fn compiled_class_hash_of_class_hash( @@ -148,18 +163,18 @@ impl StateProvider for InMemorySnapshot { } } -impl StateProviderExt for InMemorySnapshot { - fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.classes.sierra_classes.read().get(&hash).cloned(); - Ok(class) +pub(super) struct LatestStateProvider(pub(super) Arc); + +impl ContractInfoProvider for LatestStateProvider { + fn contract(&self, address: ContractAddress) -> Result> { + let info = self.0.contract_state.read().get(&address).cloned(); + Ok(info) } } -pub(super) struct LatestStateProvider(pub(super) Arc); - impl StateProvider for LatestStateProvider { fn nonce(&self, address: ContractAddress) -> Result> { - let nonce = self.0.contract_state.read().get(&address).map(|info| info.nonce); + let nonce = ContractInfoProvider::contract(&self, address)?.map(|i| i.nonce); Ok(nonce) } @@ -172,14 +187,21 @@ impl StateProvider for LatestStateProvider { Ok(value) } - fn class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned(); + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + let class_hash = ContractInfoProvider::contract(&self, address)?.map(|i| i.class_hash); + Ok(class_hash) + } +} + +impl ContractClassProvider for LatestStateProvider { + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_contract_classes.sierra_classes.read().get(&hash).cloned(); Ok(class) } - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - let class_hash = self.0.contract_state.read().get(&address).map(|info| info.class_hash); - Ok(class_hash) + fn class(&self, hash: ClassHash) -> Result> { + let class = self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned(); + Ok(class) } fn compiled_class_hash_of_class_hash( @@ -191,13 +213,6 @@ impl StateProvider for LatestStateProvider { } } -impl StateProviderExt for LatestStateProvider { - fn sierra_class(&self, hash: ClassHash) -> Result> { - let class = self.0.shared_contract_classes.sierra_classes.read().get(&hash).cloned(); - Ok(class) - } -} - #[cfg(test)] mod tests { use katana_primitives::block::BlockHashOrNumber; diff --git a/crates/katana/storage/provider/src/traits/contract.rs b/crates/katana/storage/provider/src/traits/contract.rs index 6fad0bf64d..c3f5694936 100644 --- a/crates/katana/storage/provider/src/traits/contract.rs +++ b/crates/katana/storage/provider/src/traits/contract.rs @@ -1,7 +1,44 @@ use anyhow::Result; -use katana_primitives::contract::{ContractAddress, GenericContractInfo}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + SierraClass, +}; -pub trait ContractProvider: Send + Sync { +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ContractInfoProvider: Send + Sync { /// Returns the contract information given its address. fn contract(&self, address: ContractAddress) -> Result>; } + +/// A provider trait for retrieving contract class related information. +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ContractClassProvider: Send + Sync { + /// Returns the compiled class hash for the given class hash. + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result>; + + /// Returns the compiled class definition of a contract class given its class hash. + fn class(&self, hash: ClassHash) -> Result>; + + /// Retrieves the Sierra class definition of a contract class given its class hash. + fn sierra_class(&self, hash: ClassHash) -> Result>; +} + +// TEMP: added mainly for compatibility reason following the path of least resistance. +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ContractClassWriter: ContractClassProvider + Send + Sync { + /// Returns the compiled class hash for the given class hash. + fn set_compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + compiled_hash: CompiledClassHash, + ) -> Result<()>; + + /// Returns the compiled class definition of a contract class given its class hash. + fn set_class(&self, hash: ClassHash, class: CompiledContractClass) -> Result<()>; + + /// Retrieves the Sierra class definition of a contract class given its class hash. + fn set_sierra_class(&self, hash: ClassHash, sierra: SierraClass) -> Result<()>; +} diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 27f62a55da..d6e726a3a1 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -1,15 +1,11 @@ use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; -use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, - StorageKey, StorageValue, -}; +use katana_primitives::contract::{ClassHash, ContractAddress, Nonce, StorageKey, StorageValue}; -#[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateProvider: Send + Sync { - /// Returns the compiled class definition of a contract class given its class hash. - fn class(&self, hash: ClassHash) -> Result>; +use super::contract::{ContractClassProvider, ContractClassWriter, ContractInfoProvider}; +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateProvider: ContractInfoProvider + ContractClassProvider + Send + Sync { /// Returns the nonce of a contract. fn nonce(&self, address: ContractAddress) -> Result>; @@ -22,19 +18,6 @@ pub trait StateProvider: Send + Sync { /// Returns the class hash of a contract. fn class_hash_of_contract(&self, address: ContractAddress) -> Result>; - - /// Returns the compiled class hash for the given class hash. - fn compiled_class_hash_of_class_hash( - &self, - hash: ClassHash, - ) -> Result>; -} - -/// An extension of the `StateProvider` trait which provides additional methods. -#[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateProviderExt: StateProvider + Send + Sync { - /// Retrieves the Sierra class definition of a contract class given its class hash. - fn sierra_class(&self, hash: ClassHash) -> Result>; } /// A state factory provider is a provider which can create state providers for @@ -47,3 +30,25 @@ pub trait StateFactoryProvider { /// Returns a state provider for retrieving historical state at the given block. fn historical(&self, block_id: BlockHashOrNumber) -> Result>>; } + +// TEMP: added mainly for compatibility reason following the path of least resistance. +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateWriter: StateProvider + ContractClassWriter + Send + Sync { + /// Sets the nonce of a contract. + fn set_nonce(&self, address: ContractAddress, nonce: Nonce) -> Result<()>; + + /// Sets the value of a contract storage. + fn set_storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + storage_value: StorageValue, + ) -> Result<()>; + + /// Sets the class hash of a contract. + fn set_class_hash_of_contract( + &self, + address: ContractAddress, + class_hash: ClassHash, + ) -> Result<()>; +} From 2b31251958c36928403f18106d984164304ffaee Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 21:58:45 +0800 Subject: [PATCH 090/192] build(katana-db): bump `reth-libmdbx` --- Cargo.lock | 4 ++-- crates/katana/storage/db/Cargo.toml | 9 ++++++-- crates/katana/storage/db/src/lib.rs | 9 ++++---- crates/katana/storage/db/src/mdbx/cursor.rs | 14 ++++++------ crates/katana/storage/db/src/mdbx/mod.rs | 23 ++++++++++---------- crates/katana/storage/db/src/mdbx/tx.rs | 23 ++++++++++---------- crates/katana/storage/db/src/models/block.rs | 10 +++++++++ crates/katana/storage/db/src/tables.rs | 3 ++- 8 files changed, 54 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be11983295..93eebee59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6823,7 +6823,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "0.1.0-alpha.12" -source = "git+https://github.com/paradigmxyz/reth.git#b3b84c4e33feb91df415be7c6cdeede3c91c5d1d" +source = "git+https://github.com/paradigmxyz/reth.git#6f7d6d50160e0dbb84de5b74f9794e14cc04406b" dependencies = [ "bitflags 2.4.1", "byteorder", @@ -6838,7 +6838,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "0.1.0-alpha.12" -source = "git+https://github.com/paradigmxyz/reth.git#b3b84c4e33feb91df415be7c6cdeede3c91c5d1d" +source = "git+https://github.com/paradigmxyz/reth.git#6f7d6d50160e0dbb84de5b74f9794e14cc04406b" dependencies = [ "bindgen", "cc", diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index f3604a95bc..adf0975273 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -7,12 +7,17 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +katana-primitives = { path = "../../primitives" } + anyhow.workspace = true bincode = "1.3.3" flate2.workspace = true -katana-primitives = { path = "../../primitives" } -libmdbx = { git = "https://github.com/paradigmxyz/reth.git", version = "=0.1.0-alpha.10", package = "reth-libmdbx" } page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true thiserror.workspace = true + +[dependencies.libmdbx] +git = "https://github.com/paradigmxyz/reth.git" +package = "reth-libmdbx" +version = "=0.1.0-alpha.12" diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 8cc73f83d1..4be76880cd 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -3,7 +3,6 @@ use std::path::Path; use anyhow::Context; -use libmdbx::WriteMap; pub mod codecs; pub mod error; @@ -12,14 +11,14 @@ pub mod models; pub mod tables; pub mod utils; -use mdbx::{Env, EnvKind}; +use mdbx::{DbEnv, EnvKind}; use utils::is_database_empty; /// Initialize the database at the given path and returning a handle to the its /// environment. /// /// This will create the default tables, if necessary. -pub fn init_db>(path: P) -> anyhow::Result> { +pub fn init_db>(path: P) -> anyhow::Result { if is_database_empty(path.as_ref()) { // TODO: create dir if it doesn't exist and insert db version file std::fs::create_dir_all(path.as_ref()).with_context(|| { @@ -34,8 +33,8 @@ pub fn init_db>(path: P) -> anyhow::Result> { } /// Open the database at the given `path` in read-write mode. -pub fn open_db>(path: P) -> anyhow::Result> { - Env::open(path.as_ref(), EnvKind::RW).with_context(|| { +pub fn open_db>(path: P) -> anyhow::Result { + DbEnv::open(path.as_ref(), EnvKind::RW).with_context(|| { format!("Opening database in read-write mode at path {}", path.as_ref().display()) }) } diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index fce48985fd..01ac1e8a69 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -11,15 +11,15 @@ use crate::utils::{decode_one, decode_value, KeyValue}; /// Cursor wrapper to access KV items. #[derive(Debug)] -pub struct Cursor<'tx, K: TransactionKind, T: Table> { +pub struct Cursor { /// Inner `libmdbx` cursor. - pub(crate) inner: libmdbx::Cursor<'tx, K>, + pub(crate) inner: libmdbx::Cursor, /// Phantom data to enforce encoding/decoding. _dbi: PhantomData, } -impl<'tx, K: TransactionKind, T: Table> Cursor<'tx, K, T> { - pub(crate) fn new(inner: libmdbx::Cursor<'tx, K>) -> Self { +impl Cursor { + pub(crate) fn new(inner: libmdbx::Cursor) -> Self { Self { inner, _dbi: PhantomData } } } @@ -33,7 +33,7 @@ macro_rules! decode { } #[allow(clippy::should_implement_trait)] -impl Cursor<'_, K, T> { +impl Cursor { pub fn first(&mut self) -> Result>, DatabaseError> { decode!(libmdbx::Cursor::first(&mut self.inner)) } @@ -99,7 +99,7 @@ impl Cursor<'_, K, T> { } } -impl Cursor<'_, RW, T> { +impl Cursor { /// Database operation that will update an existing row if a specified value already /// exists in a table, and insert a new row if the specified value doesn't already exist /// @@ -155,7 +155,7 @@ impl Cursor<'_, RW, T> { } } -impl Cursor<'_, RW, T> { +impl Cursor { pub fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { libmdbx::Cursor::del(&mut self.inner, WriteFlags::NO_DUP_DATA) .map_err(DatabaseError::Delete) diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 7703528e0d..8dc9c7f168 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -7,8 +7,7 @@ use std::ops::Deref; use std::path::Path; use libmdbx::{ - DatabaseFlags, Environment, EnvironmentFlags, EnvironmentKind, Geometry, Mode, PageSize, - SyncMode, RO, RW, + DatabaseFlags, Environment, EnvironmentFlags, Geometry, Mode, PageSize, SyncMode, RO, RW, }; use self::tx::Tx; @@ -33,19 +32,19 @@ pub enum EnvKind { /// Wrapper for `libmdbx-sys` environment. #[derive(Debug)] -pub struct Env(pub libmdbx::Environment); +pub struct DbEnv(libmdbx::Environment); -impl Env { +impl DbEnv { /// Opens the database at the specified path with the given `EnvKind`. /// /// It does not create the tables, for that call [`Env::create_tables`]. - pub fn open(path: &Path, kind: EnvKind) -> Result, DatabaseError> { + pub fn open(path: &Path, kind: EnvKind) -> Result { let mode = match kind { EnvKind::RO => Mode::ReadOnly, EnvKind::RW => Mode::ReadWrite { sync_mode: SyncMode::Durable }, }; - let mut builder = Environment::new(); + let mut builder = libmdbx::Environment::builder(); builder .set_max_dbs(Tables::ALL.len()) .set_geometry(Geometry { @@ -67,7 +66,7 @@ impl Env { }) .set_max_readers(DEFAULT_MAX_READERS); - Ok(Env(builder.open(path).map_err(DatabaseError::OpenEnv)?)) + Ok(DbEnv(builder.open(path).map_err(DatabaseError::OpenEnv)?)) } /// Creates all the defined tables in [`Tables`], if necessary. @@ -89,20 +88,20 @@ impl Env { } } -impl<'env, E: EnvironmentKind> Env { +impl DbEnv { /// Begin a read-only transaction. - pub fn tx(&'env self) -> Result, DatabaseError> { + pub fn tx(&self) -> Result, DatabaseError> { Ok(Tx::new(self.0.begin_ro_txn().map_err(DatabaseError::CreateROTx)?)) } /// Begin a read-write transaction. - pub fn tx_mut(&'env self) -> Result, DatabaseError> { + pub fn tx_mut(&self) -> Result, DatabaseError> { Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?)) } } -impl Deref for Env { - type Target = Environment; +impl Deref for DbEnv { + type Target = Environment; fn deref(&self) -> &Self::Target { &self.0 } diff --git a/crates/katana/storage/db/src/mdbx/tx.rs b/crates/katana/storage/db/src/mdbx/tx.rs index 133f7bcfdb..cad52bcdd0 100644 --- a/crates/katana/storage/db/src/mdbx/tx.rs +++ b/crates/katana/storage/db/src/mdbx/tx.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use libmdbx::ffi::DBI; -use libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, RW}; +use libmdbx::{Transaction, TransactionKind, WriteFlags, RW}; use parking_lot::RwLock; use super::cursor::Cursor; @@ -14,21 +14,21 @@ use crate::utils::decode_one; /// Wrapper for a `libmdbx` transaction. #[derive(Debug)] -pub struct Tx<'env, K: TransactionKind, E: EnvironmentKind> { +pub struct Tx { /// Libmdbx-sys transaction. - pub inner: libmdbx::Transaction<'env, K, E>, + pub inner: libmdbx::Transaction, /// Database table handle cache. pub(crate) db_handles: RwLock<[Option; NUM_TABLES]>, } -impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { +impl Tx { /// Creates new `Tx` object with a `RO` or `RW` transaction. - pub fn new(inner: Transaction<'env, K, E>) -> Self { + pub fn new(inner: Transaction) -> Self { Self { inner, db_handles: Default::default() } } /// Create db Cursor - pub fn new_cursor(&self) -> Result, DatabaseError> { + pub fn new_cursor(&self) -> Result, DatabaseError> { let inner = self .inner .cursor_with_dbi(self.get_dbi::()?) @@ -71,17 +71,17 @@ impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { } // Creates a cursor to iterate over a table values. - pub fn cursor(&self) -> Result, DatabaseError> { + pub fn cursor(&self) -> Result, DatabaseError> { self.new_cursor() } // Creates a cursor to iterate over a `DUPSORT` table values. - pub fn cursor_dup(&self) -> Result, DatabaseError> { + pub fn cursor_dup(&self) -> Result, DatabaseError> { self.new_cursor() } } -impl<'env, E: EnvironmentKind> Tx<'env, RW, E> { +impl Tx { pub fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = key.encode(); let value = value.compress(); @@ -114,12 +114,11 @@ impl<'env, E: EnvironmentKind> Tx<'env, RW, E> { } } -impl<'env, K, E> From> for Tx<'env, K, E> +impl From> for Tx where K: TransactionKind, - E: EnvironmentKind, { - fn from(inner: libmdbx::Transaction<'env, K, E>) -> Self { + fn from(inner: libmdbx::Transaction) -> Self { Tx::new(inner) } } diff --git a/crates/katana/storage/db/src/models/block.rs b/crates/katana/storage/db/src/models/block.rs index bca0d4164d..be577651f3 100644 --- a/crates/katana/storage/db/src/models/block.rs +++ b/crates/katana/storage/db/src/models/block.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use katana_primitives::transaction::TxNumber; use serde::{Deserialize, Serialize}; @@ -10,3 +12,11 @@ pub struct StoredBlockBodyIndices { /// The total number of transactions in the block. pub tx_count: u64, } + +impl From for Range { + fn from(value: StoredBlockBodyIndices) -> Self { + let start = value.tx_offset; + let end = value.tx_offset + value.tx_count; + std::ops::Range { start, end } + } +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index 5b50cac449..f9bfed8503 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -3,8 +3,9 @@ use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; +use katana_primitives::receipt::Receipt; use katana_primitives::serde::blockifier::SerializableContractClass; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber}; use serde::{Deserialize, Serialize}; use crate::codecs::{Compress, Decode, Decompress, Encode}; From d87eee20c6762d70d7e3755e9ddcd9f7a39d1e4e Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:01:56 +0800 Subject: [PATCH 091/192] feat(katana-primitives): add more types + util functions --- crates/katana/primitives/Cargo.toml | 2 + crates/katana/primitives/src/block.rs | 99 +++++- crates/katana/primitives/src/constants.rs | 46 +++ crates/katana/primitives/src/contract.rs | 8 +- .../primitives/src/conversion/blockifier.rs | 157 +--------- .../katana/primitives/src/conversion/rpc.rs | 149 +-------- crates/katana/primitives/src/env.rs | 33 ++ crates/katana/primitives/src/event.rs | 96 ++++++ crates/katana/primitives/src/lib.rs | 8 + crates/katana/primitives/src/receipt.rs | 104 ++++++ crates/katana/primitives/src/state.rs | 33 ++ crates/katana/primitives/src/transaction.rs | 295 +++++++++++++++--- .../primitives/src/utils/transaction.rs | 92 +++++- 13 files changed, 754 insertions(+), 368 deletions(-) create mode 100644 crates/katana/primitives/src/constants.rs create mode 100644 crates/katana/primitives/src/env.rs create mode 100644 crates/katana/primitives/src/event.rs create mode 100644 crates/katana/primitives/src/receipt.rs create mode 100644 crates/katana/primitives/src/state.rs diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index c0481255c1..3a97362e0e 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -9,9 +9,11 @@ version.workspace = true [dependencies] anyhow.workspace = true cairo-vm.workspace = true +derive_more.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true +thiserror.workspace = true blockifier.workspace = true cairo-lang-starknet.workspace = true diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 1b8e161cca..aa4662a627 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,9 +1,11 @@ +use starknet::core::crypto::compute_hash_on_elements; + use crate::contract::ContractAddress; -use crate::transaction::Tx; +use crate::transaction::{TxHash, TxWithHash}; use crate::FieldElement; -/// Block state update type. -pub type StateUpdate = starknet::core::types::StateUpdate; +pub type BlockIdOrTag = starknet::core::types::BlockId; +pub type BlockTag = starknet::core::types::BlockTag; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -17,6 +19,25 @@ pub type BlockNumber = u64; /// Block hash type. pub type BlockHash = FieldElement; +/// Finality status of a canonical block. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum FinalityStatus { + AcceptedOnL2, + AcceptedOnL1, +} + +/// Represents a partial block header. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PartialHeader { + pub parent_hash: FieldElement, + pub number: BlockNumber, + pub gas_price: u128, + pub timestamp: u64, + pub sequencer_address: ContractAddress, +} + /// Represents a block header. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -29,12 +50,82 @@ pub struct Header { pub sequencer_address: ContractAddress, } +impl Header { + pub fn new(partial_header: PartialHeader, state_root: FieldElement) -> Self { + Self { + state_root, + number: partial_header.number, + gas_price: partial_header.gas_price, + timestamp: partial_header.timestamp, + parent_hash: partial_header.parent_hash, + sequencer_address: partial_header.sequencer_address, + } + } + + /// Computes the hash of the header. + pub fn compute_hash(&self) -> FieldElement { + compute_hash_on_elements(&vec![ + self.number.into(), // block number + self.state_root, // state root + self.sequencer_address.into(), // sequencer address + self.timestamp.into(), // block timestamp + FieldElement::ZERO, // transaction commitment + FieldElement::ZERO, // event commitment + FieldElement::ZERO, // protocol version + FieldElement::ZERO, // extra data + self.parent_hash, // parent hash + ]) + } + + fn seal(self) -> SealedHeader { + let hash = self.compute_hash(); + SealedHeader { hash, header: self } + } +} + /// Represents a Starknet full block. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Block { pub header: Header, - pub body: Vec, + pub body: Vec, +} + +/// A block with only the transaction hashes. +#[derive(Debug, Clone)] +pub struct BlockWithTxHashes { + pub header: Header, + pub body: Vec, +} + +impl Block { + pub fn seal(self) -> SealedBlock { + SealedBlock { header: self.header.seal(), body: self.body } + } +} + +#[derive(Debug, Clone)] +pub struct SealedHeader { + /// The hash of the header. + pub hash: BlockHash, + /// The block header. + pub header: Header, +} + +/// A full Starknet block that has been sealed. +#[derive(Debug, Clone)] +pub struct SealedBlock { + /// The sealed block header. + pub header: SealedHeader, + /// The block body. + pub body: Vec, +} + +/// A sealed block along with its status. +pub struct SealedBlockWithStatus { + pub block: SealedBlock, + /// The block status. + pub status: FinalityStatus, } impl From for BlockHashOrNumber { diff --git a/crates/katana/primitives/src/constants.rs b/crates/katana/primitives/src/constants.rs new file mode 100644 index 0000000000..22e4b36201 --- /dev/null +++ b/crates/katana/primitives/src/constants.rs @@ -0,0 +1,46 @@ +use lazy_static::lazy_static; +use starknet::macros::felt; + +use crate::contract::{ + CompiledContractClass, CompiledContractClassV0, ContractAddress, StorageKey, +}; +use crate::FieldElement; + +pub const DEFAULT_GAS_PRICE: u128 = 100 * u128::pow(10, 9); // Given in units of wei. + +pub const DEFAULT_INVOKE_MAX_STEPS: u32 = 1_000_000; +pub const DEFAULT_VALIDATE_MAX_STEPS: u32 = 1_000_000; + +lazy_static! { + + // Predefined contract addresses + + pub static ref SEQUENCER_ADDRESS: ContractAddress = ContractAddress(felt!("0x69420")); + pub static ref UDC_ADDRESS: ContractAddress = ContractAddress(felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf")); + pub static ref FEE_TOKEN_ADDRESS: ContractAddress = ContractAddress(felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")); + + // Predefined class hashes + + pub static ref DEFAULT_ACCOUNT_CONTRACT_CLASS_HASH: FieldElement = felt!("0x04d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f"); + pub static ref ERC20_CONTRACT_CLASS_HASH: FieldElement = felt!("0x02a8846878b6ad1f54f6ba46f5f40e11cee755c677f130b2c4b60566c9003f1f"); + pub static ref UDC_CLASS_HASH: FieldElement = felt!("0x07b3e05f48f0c69e4a65ce5e076a66271a527aff2c34ce1083ec6e1526997a69"); + + // Predefined contract classes + + pub static ref ERC20_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/erc20.json")); + pub static ref UDC_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/universal_deployer.json")); + pub static ref DEFAULT_ACCOUNT_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/account.json")); + + pub static ref DEFAULT_PREFUNDED_ACCOUNT_BALANCE: FieldElement = felt!("0x3635c9adc5dea00000"); // 10^21 + + // Storage slots + + pub static ref ERC20_NAME_STORAGE_SLOT: StorageKey = felt!("0x0341c1bdfd89f69748aa00b5742b03adbffd79b8e80cab5c50d91cd8c2a79be1"); + pub static ref ERC20_SYMBOL_STORAGE_SLOT: StorageKey = felt!("0x00b6ce5410fca59d078ee9b2a4371a9d684c530d697c64fbef0ae6d5e8f0ac72"); + pub static ref ERC20_DECIMALS_STORAGE_SLOT: StorageKey = felt!("0x01f0d4aa99431d246bac9b8e48c33e888245b15e9678f64f9bdfc8823dc8f979"); +} + +fn parse_legacy_contract_class(content: impl AsRef) -> CompiledContractClass { + let class: CompiledContractClassV0 = serde_json::from_str(content.as_ref()).unwrap(); + CompiledContractClass::V0(class) +} diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index de5ab62c3f..7531edb89e 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -20,7 +20,7 @@ pub type Nonce = FieldElement; pub type SierraClass = starknet::core::types::FlattenedSierraClass; /// Represents a contract address. -#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +#[derive(Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ContractAddress(pub FieldElement); @@ -61,3 +61,9 @@ pub struct GenericContractInfo { /// Represents a runnable Starknet contract class (meaning, the program is runnable by the VM). #[cfg(feature = "blockifier")] pub type CompiledContractClass = ::blockifier::execution::contract_class::ContractClass; +/// V0 of the compiled contract class +#[cfg(feature = "blockifier")] +pub type CompiledContractClassV0 = ::blockifier::execution::contract_class::ContractClassV0; +/// V1 of the compiled contract class +#[cfg(feature = "blockifier")] +pub type CompiledContractClassV1 = ::blockifier::execution::contract_class::ContractClassV1; diff --git a/crates/katana/primitives/src/conversion/blockifier.rs b/crates/katana/primitives/src/conversion/blockifier.rs index bb9d24b46b..2ed3ca2983 100644 --- a/crates/katana/primitives/src/conversion/blockifier.rs +++ b/crates/katana/primitives/src/conversion/blockifier.rs @@ -1,164 +1,17 @@ //! Translation layer for converting the primitive types to the execution engine types. -use std::sync::Arc; - -use blockifier::transaction::account_transaction::AccountTransaction; -use starknet_api::core::{ - ClassHash, CompiledClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey, -}; -use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::core::{ContractAddress, PatriciaKey}; +use starknet_api::hash::StarkHash; use starknet_api::patricia_key; -use starknet_api::transaction::{ - Calldata, ContractAddressSalt, DeclareTransaction, DeclareTransactionV0V1, - DeclareTransactionV2, DeployAccountTransaction, Fee, InvokeTransaction, InvokeTransactionV1, - L1HandlerTransaction, TransactionHash, TransactionSignature, TransactionVersion, -}; - -use crate::transaction::ExecutionTx; -use crate::FieldElement; - -mod primitives { - pub use crate::contract::{ContractAddress, Nonce}; - pub use crate::transaction::{ - DeclareTx, DeclareTxWithClasses, DeployAccountTx, InvokeTx, L1HandlerTx, - }; - pub use crate::FieldElement; -} -impl From for ContractAddress { - fn from(address: primitives::ContractAddress) -> Self { +impl From for ContractAddress { + fn from(address: crate::contract::ContractAddress) -> Self { Self(patricia_key!(address.0)) } } -impl From for primitives::ContractAddress { +impl From for crate::contract::ContractAddress { fn from(address: ContractAddress) -> Self { Self((*address.0.key()).into()) } } - -impl From for InvokeTransaction { - fn from(tx: primitives::InvokeTx) -> Self { - if FieldElement::ONE == tx.version { - InvokeTransaction::V1(InvokeTransactionV1 { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - calldata: calldata_from_felts_vec(tx.calldata), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - }) - } else { - unimplemented!("Unsupported transaction version") - } - } -} - -impl From for DeclareTransaction { - fn from(tx: primitives::DeclareTx) -> Self { - if FieldElement::ONE == tx.version { - DeclareTransaction::V1(DeclareTransactionV0V1 { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - class_hash: ClassHash(tx.class_hash.into()), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - }) - } else if FieldElement::TWO == tx.version { - DeclareTransaction::V2(DeclareTransactionV2 { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - sender_address: tx.sender_address.into(), - class_hash: ClassHash(tx.class_hash.into()), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.unwrap().into()), - }) - } else { - unimplemented!("Unsupported transaction version") - } - } -} - -impl From for DeployAccountTransaction { - fn from(tx: primitives::DeployAccountTx) -> Self { - Self { - max_fee: Fee(tx.max_fee), - nonce: Nonce(tx.nonce.into()), - class_hash: ClassHash(tx.class_hash.into()), - version: TransactionVersion(tx.version.into()), - signature: signature_from_felts_vec(tx.signature), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - contract_address_salt: ContractAddressSalt(tx.contract_address_salt.into()), - constructor_calldata: calldata_from_felts_vec(tx.constructor_calldata), - } - } -} - -impl From for L1HandlerTransaction { - fn from(tx: primitives::L1HandlerTx) -> Self { - Self { - nonce: Nonce(tx.nonce.into()), - contract_address: tx.contract_address.into(), - version: TransactionVersion(tx.version.into()), - calldata: calldata_from_felts_vec(tx.calldata), - transaction_hash: TransactionHash(tx.transaction_hash.into()), - entry_point_selector: EntryPointSelector(tx.entry_point_selector.into()), - } - } -} - -impl From - for blockifier::transaction::transactions::DeployAccountTransaction -{ - fn from(tx: primitives::DeployAccountTx) -> Self { - Self { contract_address: tx.contract_address.into(), tx: tx.into() } - } -} - -impl From - for blockifier::transaction::transactions::DeclareTransaction -{ - fn from(tx: primitives::DeclareTxWithClasses) -> Self { - Self::new(tx.tx.into(), tx.compiled_class).expect("tx & class must be compatible") - } -} - -impl From for blockifier::transaction::transaction_execution::Transaction { - fn from(tx: ExecutionTx) -> Self { - match tx { - ExecutionTx::L1Handler(tx) => Self::L1HandlerTransaction( - blockifier::transaction::transactions::L1HandlerTransaction { - paid_fee_on_l1: Fee(tx.paid_fee_on_l1), - tx: tx.into(), - }, - ), - - ExecutionTx::Invoke(tx) => { - Self::AccountTransaction(AccountTransaction::Invoke(tx.into())) - } - ExecutionTx::Declare(tx) => { - Self::AccountTransaction(AccountTransaction::Declare(tx.into())) - } - ExecutionTx::DeployAccount(tx) => { - Self::AccountTransaction(AccountTransaction::DeployAccount(tx.into())) - } - } - } -} - -#[inline] -fn felt_to_starkfelt_vec(felts: Vec) -> Vec { - felts.into_iter().map(|f| f.into()).collect() -} - -#[inline] -fn calldata_from_felts_vec(felts: Vec) -> Calldata { - Calldata(Arc::new(felt_to_starkfelt_vec(felts))) -} - -#[inline] -fn signature_from_felts_vec(felts: Vec) -> TransactionSignature { - TransactionSignature(felt_to_starkfelt_vec(felts)) -} diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs index f4023e2a5e..b5c1ef259a 100644 --- a/crates/katana/primitives/src/conversion/rpc.rs +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -9,157 +9,19 @@ use serde_json::json; pub use starknet::core::types::contract::legacy::{LegacyContractClass, LegacyProgram}; pub use starknet::core::types::contract::CompiledClass; use starknet::core::types::{ - BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, - BroadcastedInvokeTransaction, CompressedLegacyContractClass, ContractClass, - LegacyContractEntryPoint, LegacyEntryPointsByType, + CompressedLegacyContractClass, ContractClass, LegacyContractEntryPoint, LegacyEntryPointsByType, }; -use starknet::core::utils::get_contract_address; use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; use crate::contract::{ClassHash, CompiledClassHash, CompiledContractClass, SierraClass}; -use crate::utils::transaction::{ - compute_declare_v1_transaction_hash, compute_declare_v2_transaction_hash, - compute_deploy_account_v1_transaction_hash, compute_invoke_v1_transaction_hash, -}; use crate::FieldElement; mod primitives { pub use crate::contract::{CompiledContractClass, ContractAddress, Nonce}; - pub use crate::transaction::{ - DeclareTx, DeclareTxWithClasses, DeployAccountTx, InvokeTx, L1HandlerTx, Tx, - }; + pub use crate::transaction::{DeclareTx, DeployAccountTx, InvokeTx, L1HandlerTx, Tx}; pub use crate::FieldElement; } -// Transactions - -impl primitives::InvokeTx { - pub fn from_broadcasted_rpc(tx: BroadcastedInvokeTransaction, chain_id: FieldElement) -> Self { - let transaction_hash = compute_invoke_v1_transaction_hash( - tx.sender_address, - &tx.calldata, - tx.max_fee, - chain_id, - tx.nonce, - tx.is_query, - ); - - primitives::InvokeTx { - transaction_hash, - nonce: tx.nonce, - calldata: tx.calldata, - signature: tx.signature, - version: FieldElement::ONE, - sender_address: tx.sender_address.into(), - max_fee: tx.max_fee.try_into().expect("max_fee is too large"), - } - } -} - -impl primitives::DeployAccountTx { - pub fn from_broadcasted_rpc( - tx: BroadcastedDeployAccountTransaction, - chain_id: FieldElement, - ) -> Self { - let contract_address = get_contract_address( - tx.contract_address_salt, - tx.class_hash, - &tx.constructor_calldata, - FieldElement::ZERO, - ); - - let transaction_hash = compute_deploy_account_v1_transaction_hash( - contract_address, - &tx.constructor_calldata, - tx.class_hash, - tx.contract_address_salt, - tx.max_fee, - chain_id, - tx.nonce, - tx.is_query, - ); - - Self { - transaction_hash, - nonce: tx.nonce, - signature: tx.signature, - class_hash: tx.class_hash, - version: FieldElement::ONE, - contract_address: contract_address.into(), - constructor_calldata: tx.constructor_calldata, - contract_address_salt: tx.contract_address_salt, - max_fee: tx.max_fee.try_into().expect("max fee is too large"), - } - } -} - -impl primitives::DeclareTx { - pub fn from_broadcasted_rpc( - tx: BroadcastedDeclareTransaction, - chain_id: FieldElement, - ) -> (Self, primitives::CompiledContractClass, Option) { - // extract class - let (class_hash, compiled_class_hash, sierra_class, compiled_class) = match &tx { - BroadcastedDeclareTransaction::V1(tx) => { - let (hash, class) = legacy_rpc_to_inner_class(&tx.contract_class).unwrap(); - (hash, None, None, class) - } - - BroadcastedDeclareTransaction::V2(tx) => { - let (hash, compiled_hash, class) = rpc_to_inner_class(&tx.contract_class).unwrap(); - (hash, Some(compiled_hash), Some(tx.contract_class.as_ref().clone()), class) - } - }; - - // compute transaction hash - let transaction_hash = match &tx { - BroadcastedDeclareTransaction::V1(tx) => compute_declare_v1_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.is_query, - ), - - BroadcastedDeclareTransaction::V2(tx) => compute_declare_v2_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.compiled_class_hash, - tx.is_query, - ), - }; - - // extract common fields - let (nonce, max_fee, version, signature, sender_address) = match tx { - BroadcastedDeclareTransaction::V1(tx) => { - (tx.nonce, tx.max_fee, FieldElement::ONE, tx.signature, tx.sender_address) - } - BroadcastedDeclareTransaction::V2(tx) => { - (tx.nonce, tx.max_fee, FieldElement::TWO, tx.signature, tx.sender_address) - } - }; - - let tx = Self { - nonce, - version, - signature, - class_hash, - transaction_hash, - compiled_class_hash, - sender_address: sender_address.into(), - max_fee: max_fee.try_into().expect("max fee is too large"), - }; - - (tx, compiled_class, sierra_class) - } -} - -// Contract class - pub fn legacy_inner_to_rpc_class(legacy_contract_class: ContractClassV0) -> Result { let entry_points_by_type = to_rpc_legacy_entry_points_by_type(&legacy_contract_class.entry_points_by_type)?; @@ -214,14 +76,13 @@ pub fn rpc_to_cairo_contract_class( }) } -/// Compute the compiled class hash from the given [FlattenedSierraClass]. +/// Compute the compiled class hash from the given [`SierraClass`]. pub fn compiled_class_hash_from_flattened_sierra_class( contract_class: &SierraClass, ) -> Result { let contract_class = rpc_to_cairo_contract_class(contract_class)?; - let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; - let res = serde_json::to_string(&casm_contract)?; - let compiled_class: CompiledClass = serde_json::from_str(&res)?; + let casm = CasmContractClass::from_contract_class(contract_class, true)?; + let compiled_class: CompiledClass = serde_json::from_str(&serde_json::to_string(&casm)?)?; Ok(compiled_class.class_hash()?) } diff --git a/crates/katana/primitives/src/env.rs b/crates/katana/primitives/src/env.rs new file mode 100644 index 0000000000..8108e4a831 --- /dev/null +++ b/crates/katana/primitives/src/env.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use crate::contract::ContractAddress; + +/// Block environment values. +#[derive(Debug, Clone)] +pub struct BlockEnv { + /// The block height. + pub number: u64, + /// The timestamp in seconds since the UNIX epoch. + pub timestamp: u64, + /// The block gas price in wei. + pub gas_price: u128, +} + +/// Starknet configuration values. +#[derive(Debug, Clone)] +pub struct CfgEnv { + /// The chain id. + pub chain_id: u64, + /// The contract address of the sequencer. + pub sequencer_address: ContractAddress, + /// The contract address of the fee token. + pub fee_token_address: ContractAddress, + /// The fee cost of the VM resources. + pub vm_resource_fee_cost: HashMap, + /// The maximum number of steps allowed for an invoke transaction. + pub invoke_tx_max_n_steps: u32, + /// The maximum number of steps allowed for transaction validation. + pub validate_max_n_steps: u32, + /// The maximum recursion depth allowed. + pub max_recursion_depth: usize, +} diff --git a/crates/katana/primitives/src/event.rs b/crates/katana/primitives/src/event.rs new file mode 100644 index 0000000000..ebe08293cd --- /dev/null +++ b/crates/katana/primitives/src/event.rs @@ -0,0 +1,96 @@ +use core::fmt; +use std::num::ParseIntError; + +#[derive(PartialEq, Eq, Debug, Default)] +pub struct ContinuationToken { + pub block_n: u64, + pub txn_n: u64, + pub event_n: u64, +} + +#[derive(PartialEq, Eq, Debug, thiserror::Error)] +pub enum ContinuationTokenError { + #[error("Invalid data")] + InvalidToken, + #[error("Invalid format: {0}")] + ParseFailed(ParseIntError), +} + +impl ContinuationToken { + pub fn parse(token: String) -> Result { + let arr: Vec<&str> = token.split(',').collect(); + if arr.len() != 3 { + return Err(ContinuationTokenError::InvalidToken); + } + let block_n = + u64::from_str_radix(arr[0], 16).map_err(ContinuationTokenError::ParseFailed)?; + let receipt_n = + u64::from_str_radix(arr[1], 16).map_err(ContinuationTokenError::ParseFailed)?; + let event_n = + u64::from_str_radix(arr[2], 16).map_err(ContinuationTokenError::ParseFailed)?; + + Ok(ContinuationToken { block_n, txn_n: receipt_n, event_n }) + } +} + +impl fmt::Display for ContinuationToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x},{:x},{:x}", self.block_n, self.txn_n, self.event_n) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn to_string_works() { + fn helper(block_n: u64, txn_n: u64, event_n: u64) -> String { + ContinuationToken { block_n, txn_n, event_n }.to_string() + } + + assert_eq!(helper(0, 0, 0), "0,0,0"); + assert_eq!(helper(30, 255, 4), "1e,ff,4"); + } + + #[test] + fn parse_works() { + fn helper(token: &str) -> ContinuationToken { + ContinuationToken::parse(token.to_owned()).unwrap() + } + assert_eq!(helper("0,0,0"), ContinuationToken { block_n: 0, txn_n: 0, event_n: 0 }); + assert_eq!(helper("1e,ff,4"), ContinuationToken { block_n: 30, txn_n: 255, event_n: 4 }); + } + + #[test] + fn parse_should_fail() { + assert_eq!( + ContinuationToken::parse("100".to_owned()).unwrap_err(), + ContinuationTokenError::InvalidToken + ); + assert_eq!( + ContinuationToken::parse("0,".to_owned()).unwrap_err(), + ContinuationTokenError::InvalidToken + ); + assert_eq!( + ContinuationToken::parse("0,0".to_owned()).unwrap_err(), + ContinuationTokenError::InvalidToken + ); + } + + #[test] + fn parse_u64_should_fail() { + matches!( + ContinuationToken::parse("2y,100,4".to_owned()).unwrap_err(), + ContinuationTokenError::ParseFailed(_) + ); + matches!( + ContinuationToken::parse("30,255g,4".to_owned()).unwrap_err(), + ContinuationTokenError::ParseFailed(_) + ); + matches!( + ContinuationToken::parse("244,1,fv".to_owned()).unwrap_err(), + ContinuationTokenError::ParseFailed(_) + ); + } +} diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 3a752e0249..a6965c29ff 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -1,10 +1,18 @@ pub mod block; pub mod contract; +pub mod env; +pub mod event; +pub mod receipt; pub mod transaction; pub mod conversion; #[cfg(feature = "serde")] pub mod serde; + +pub mod state; pub mod utils; pub type FieldElement = starknet::core::types::FieldElement; + +/// The id of the chain. +pub type ChainId = FieldElement; diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs new file mode 100644 index 0000000000..000f7bf086 --- /dev/null +++ b/crates/katana/primitives/src/receipt.rs @@ -0,0 +1,104 @@ +use std::collections::HashMap; + +use starknet::core::types::{Event, MsgToL1}; + +use crate::contract::ContractAddress; + +/// A mapping of execution resource to the amount used. +pub type ExecutionResources = HashMap; + +/// Receipt for a `Invoke` transaction. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct InvokeTxReceipt { + /// Actual fee paid for the transaction. + pub actual_fee: u128, + /// Events emitted by contracts. + pub events: Vec, + /// Messages sent to L1. + pub messages_sent: Vec, + /// Revert error message if the transaction execution failed. + pub revert_error: Option, + /// The execution resources used by the transaction. + pub actual_resources: ExecutionResources, +} + +/// Receipt for a `Declare` transaction. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeclareTxReceipt { + /// Actual fee paid for the transaction. + pub actual_fee: u128, + /// Events emitted by contracts. + pub events: Vec, + /// Messages sent to L1. + pub messages_sent: Vec, + /// Revert error message if the transaction execution failed. + pub revert_error: Option, + /// The execution resources used by the transaction. + pub actual_resources: ExecutionResources, +} + +/// Receipt for a `L1Handler` transaction. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct L1HandlerTxReceipt { + /// Actual fee paid for the transaction. + pub actual_fee: u128, + /// Events emitted by contracts. + pub events: Vec, + /// Messages sent to L1. + pub messages_sent: Vec, + /// Revert error message if the transaction execution failed. + pub revert_error: Option, + /// The execution resources used by the transaction. + pub actual_resources: ExecutionResources, +} + +/// Receipt for a `DeployAccount` transaction. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeployAccountTxReceipt { + /// Actual fee paid for the transaction. + pub actual_fee: u128, + /// Events emitted by contracts. + pub events: Vec, + /// Messages sent to L1. + pub messages_sent: Vec, + /// Revert error message if the transaction execution failed. + pub revert_error: Option, + /// The execution resources used by the transaction. + pub actual_resources: ExecutionResources, + /// Contract address of the deployed account contract. + pub contract_address: ContractAddress, +} + +/// The receipt of a transaction containing the outputs of its execution. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Receipt { + Invoke(InvokeTxReceipt), + Declare(DeclareTxReceipt), + L1Handler(L1HandlerTxReceipt), + DeployAccount(DeployAccountTxReceipt), +} + +impl Receipt { + pub fn messages_sent(&self) -> &[MsgToL1] { + match self { + Receipt::Invoke(rct) => &rct.messages_sent, + Receipt::Declare(rct) => &rct.messages_sent, + Receipt::L1Handler(rct) => &rct.messages_sent, + Receipt::DeployAccount(rct) => &rct.messages_sent, + } + } + + pub fn events(&self) -> &[Event] { + match self { + Receipt::Invoke(rct) => &rct.events, + Receipt::Declare(rct) => &rct.events, + Receipt::L1Handler(rct) => &rct.events, + Receipt::DeployAccount(rct) => &rct.events, + } + } +} diff --git a/crates/katana/primitives/src/state.rs b/crates/katana/primitives/src/state.rs new file mode 100644 index 0000000000..ed95f1a682 --- /dev/null +++ b/crates/katana/primitives/src/state.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use crate::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, + StorageKey, StorageValue, +}; + +/// State updates. +/// +/// Represents all the state updates after performing some executions on a state. +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StateUpdates { + /// A mapping of contract addresses to their updated nonces. + pub nonce_updates: HashMap, + /// A mapping of contract addresses to their updated storage entries. + pub storage_updates: HashMap>, + /// A mapping of contract addresses to their updated class hashes. + pub contract_updates: HashMap, + /// A mapping of newly declared class hashes to their compiled class hashes. + pub declared_classes: HashMap, +} + +/// State update with declared classes definition. +#[derive(Debug, Default, Clone)] +pub struct StateUpdatesWithDeclaredClasses { + /// State updates. + pub state_updates: StateUpdates, + /// A mapping of class hashes to their sierra classes definition. + pub declared_sierra_classes: HashMap, + /// A mapping of class hashes to their compiled classes definition. + pub declared_compiled_classes: HashMap, +} diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 80aba7e49d..6a6ccf2b96 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,120 +1,315 @@ -use std::collections::HashMap; - -use starknet::core::types::{Event, MsgToL1}; +use derive_more::{AsRef, Deref}; use crate::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, }; -use crate::FieldElement; +use crate::utils::transaction::{ + compute_declare_v1_tx_hash, compute_declare_v2_tx_hash, compute_deploy_account_v1_tx_hash, + compute_invoke_v1_tx_hash, compute_l1_handler_tx_hash, +}; +use crate::{ChainId, FieldElement}; /// The hash of a transaction. pub type TxHash = FieldElement; /// The sequential number for all the transactions.. pub type TxNumber = u64; -/// Represents a transaction that can be executed. #[derive(Debug, Clone)] -pub enum ExecutionTx { +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Tx { Invoke(InvokeTx), + Declare(DeclareTx), L1Handler(L1HandlerTx), - Declare(DeclareTxWithClasses), DeployAccount(DeployAccountTx), } +pub enum TxRef<'a> { + Invoke(&'a InvokeTx), + Declare(&'a DeclareTx), + L1Handler(&'a L1HandlerTx), + DeployAccount(&'a DeployAccountTx), +} + +impl<'a> From> for Tx { + fn from(value: TxRef<'a>) -> Self { + match value { + TxRef::Invoke(tx) => Tx::Invoke(tx.clone()), + TxRef::Declare(tx) => Tx::Declare(tx.clone()), + TxRef::L1Handler(tx) => Tx::L1Handler(tx.clone()), + TxRef::DeployAccount(tx) => Tx::DeployAccount(tx.clone()), + } + } +} + +/// Represents a transaction that has all the necessary data to be executed. #[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Tx { +pub enum ExecutableTx { Invoke(InvokeTx), - Declare(DeclareTx), L1Handler(L1HandlerTx), + Declare(DeclareTxWithClass), DeployAccount(DeployAccountTx), } -#[derive(Debug, Clone)] +impl ExecutableTx { + pub fn tx_ref(&self) -> TxRef<'_> { + match self { + ExecutableTx::Invoke(tx) => TxRef::Invoke(tx), + ExecutableTx::L1Handler(tx) => TxRef::L1Handler(tx), + ExecutableTx::Declare(tx) => TxRef::Declare(tx), + ExecutableTx::DeployAccount(tx) => TxRef::DeployAccount(tx), + } + } +} + +#[derive(Debug, Clone, AsRef, Deref)] +pub struct ExecutableTxWithHash { + /// The hash of the transaction. + pub hash: TxHash, + /// The raw transaction. + #[deref] + #[as_ref] + pub transaction: ExecutableTx, +} + +impl ExecutableTxWithHash { + pub fn new(transaction: ExecutableTx) -> Self { + let hash = match &transaction { + ExecutableTx::L1Handler(tx) => tx.calculate_hash(), + ExecutableTx::Invoke(tx) => tx.calculate_hash(false), + ExecutableTx::Declare(tx) => tx.calculate_hash(false), + ExecutableTx::DeployAccount(tx) => tx.calculate_hash(false), + }; + Self { hash, transaction } + } + + pub fn new_query(transaction: ExecutableTx) -> Self { + let hash = match &transaction { + ExecutableTx::L1Handler(tx) => tx.calculate_hash(), + ExecutableTx::Invoke(tx) => tx.calculate_hash(true), + ExecutableTx::Declare(tx) => tx.calculate_hash(true), + ExecutableTx::DeployAccount(tx) => tx.calculate_hash(true), + }; + Self { hash, transaction } + } +} + +#[derive(Debug, Clone, AsRef, Deref)] +pub struct DeclareTxWithClass { + /// The Sierra class, if any. + pub sierra_class: Option, + /// The compiled contract class. + pub compiled_class: CompiledContractClass, + /// The raw transaction. + #[deref] + #[as_ref] + pub transaction: DeclareTx, +} + +impl DeclareTxWithClass { + pub fn new_with_classes( + transaction: DeclareTx, + sierra_class: SierraClass, + compiled_class: CompiledContractClass, + ) -> Self { + Self { sierra_class: Some(sierra_class), compiled_class, transaction } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvokeTx { - pub transaction_hash: TxHash, pub nonce: Nonce, pub max_fee: u128, + pub chain_id: ChainId, pub version: FieldElement, pub calldata: Vec, pub signature: Vec, pub sender_address: ContractAddress, } +impl InvokeTx { + /// Compute the hash of the transaction. + pub fn calculate_hash(&self, is_query: bool) -> TxHash { + compute_invoke_v1_tx_hash( + self.sender_address.into(), + &self.calldata, + self.max_fee, + self.chain_id, + self.nonce, + is_query, + ) + } +} + #[derive(Debug, Clone)] -pub struct DeclareTxWithClasses { - pub tx: DeclareTx, - pub sierra_class: Option, - pub compiled_class: CompiledContractClass, +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum DeclareTx { + V1(DeclareTxV1), + V2(DeclareTxV2), +} + +impl DeclareTx { + pub fn class_hash(&self) -> ClassHash { + match self { + DeclareTx::V1(tx) => tx.class_hash, + DeclareTx::V2(tx) => tx.class_hash, + } + } } /// Represents a declare transaction type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeclareTx { - pub transaction_hash: TxHash, +pub struct DeclareTxV1 { + pub nonce: Nonce, pub max_fee: u128, + pub chain_id: ChainId, + /// The class hash of the contract class to be declared. + pub class_hash: ClassHash, + pub signature: Vec, + pub sender_address: ContractAddress, +} + +/// Represents a declare transaction type. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeclareTxV2 { pub nonce: Nonce, - pub version: FieldElement, + pub max_fee: u128, + pub chain_id: ChainId, /// The class hash of the contract class to be declared. pub class_hash: ClassHash, pub signature: Vec, pub sender_address: ContractAddress, /// The compiled class hash of the contract class (only if it's a Sierra class). - pub compiled_class_hash: Option, + pub compiled_class_hash: CompiledClassHash, } -#[derive(Debug, Clone)] +impl DeclareTx { + /// Compute the hash of the transaction. + pub fn calculate_hash(&self, is_query: bool) -> TxHash { + match self { + DeclareTx::V1(tx) => compute_declare_v1_tx_hash( + tx.sender_address.into(), + tx.class_hash, + tx.max_fee, + tx.chain_id, + tx.nonce, + is_query, + ), + + DeclareTx::V2(tx) => compute_declare_v2_tx_hash( + tx.sender_address.into(), + tx.class_hash, + tx.max_fee, + tx.chain_id, + tx.nonce, + tx.compiled_class_hash, + is_query, + ), + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1HandlerTx { - pub transaction_hash: TxHash, - pub version: FieldElement, pub nonce: Nonce, + pub chain_id: ChainId, pub paid_fee_on_l1: u128, + pub version: FieldElement, pub calldata: Vec, pub contract_address: ContractAddress, pub entry_point_selector: FieldElement, } -#[derive(Debug, Clone)] +impl L1HandlerTx { + /// Compute the hash of the transaction. + pub fn calculate_hash(&self) -> TxHash { + compute_l1_handler_tx_hash( + self.version, + self.contract_address.into(), + self.entry_point_selector, + &self.calldata, + self.chain_id, + self.nonce, + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeployAccountTx { - pub transaction_hash: TxHash, - pub max_fee: u128, pub nonce: Nonce, - pub class_hash: ClassHash, + pub max_fee: u128, + pub chain_id: ChainId, pub version: FieldElement, + pub class_hash: ClassHash, pub signature: Vec, pub contract_address: ContractAddress, pub contract_address_salt: FieldElement, pub constructor_calldata: Vec, } -/// A transaction finality status. -#[derive(Debug, Copy, Clone)] +impl DeployAccountTx { + /// Compute the hash of the transaction. + pub fn calculate_hash(&self, is_query: bool) -> TxHash { + compute_deploy_account_v1_tx_hash( + self.contract_address.into(), + self.constructor_calldata.as_slice(), + self.class_hash, + self.contract_address_salt, + self.max_fee, + self.chain_id, + self.nonce, + is_query, + ) + } +} + +#[derive(Debug, Clone, AsRef, Deref)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum FinalityStatus { - AcceptedOnL2, - AcceptedOnL1, +pub struct TxWithHash { + /// The hash of the transaction. + pub hash: TxHash, + /// The raw transaction. + #[deref] + #[as_ref] + pub transaction: Tx, } -pub type ExecutionResources = HashMap; +impl From for TxWithHash { + fn from(tx: ExecutableTxWithHash) -> Self { + Self { hash: tx.hash, transaction: tx.tx_ref().into() } + } +} -/// The receipt of a transaction containing the outputs of its execution. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Receipt { - /// Actual fee paid for the transaction. - pub actual_fee: u128, - /// Events emitted by contracts. - pub events: Vec, - /// Messages sent to L1. - pub messages_sent: Vec, - /// Revert error message if the transaction execution failed. - pub revert_error: Option, - /// The execution resources used by the transaction. - pub actual_resources: ExecutionResources, - /// Contract address if the transaction deployed a contract. (only for deploy account tx) - pub contract_address: Option, +impl From<&ExecutableTxWithHash> for TxWithHash { + fn from(tx: &ExecutableTxWithHash) -> Self { + Self { hash: tx.hash, transaction: tx.tx_ref().into() } + } +} + +impl From for ExecutableTx { + fn from(tx: L1HandlerTx) -> Self { + ExecutableTx::L1Handler(tx) + } +} + +impl From for ExecutableTx { + fn from(tx: DeclareTxWithClass) -> Self { + ExecutableTx::Declare(tx) + } +} + +impl From for ExecutableTx { + fn from(tx: InvokeTx) -> Self { + ExecutableTx::Invoke(tx) + } +} + +impl From for ExecutableTx { + fn from(tx: DeployAccountTx) -> Self { + ExecutableTx::DeployAccount(tx) + } } diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs index 0b48e9a71d..4404244113 100644 --- a/crates/katana/primitives/src/utils/transaction.rs +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -44,12 +44,12 @@ const PREFIX_L1_HANDLER: FieldElement = FieldElement::from_mont([ /// Compute the hash of a V1 DeployAccount transaction. #[allow(clippy::too_many_arguments)] -pub fn compute_deploy_account_v1_transaction_hash( +pub fn compute_deploy_account_v1_tx_hash( contract_address: FieldElement, constructor_calldata: &[FieldElement], class_hash: FieldElement, salt: FieldElement, - max_fee: FieldElement, + max_fee: u128, chain_id: FieldElement, nonce: FieldElement, is_query: bool, @@ -62,17 +62,17 @@ pub fn compute_deploy_account_v1_transaction_hash( contract_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&calldata_to_hash), - max_fee, + max_fee.into(), chain_id, nonce, ]) } /// Compute the hash of a V1 Declare transaction. -pub fn compute_declare_v1_transaction_hash( +pub fn compute_declare_v1_tx_hash( sender_address: FieldElement, class_hash: FieldElement, - max_fee: FieldElement, + max_fee: u128, chain_id: FieldElement, nonce: FieldElement, is_query: bool, @@ -83,17 +83,17 @@ pub fn compute_declare_v1_transaction_hash( sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&[class_hash]), - max_fee, + max_fee.into(), chain_id, nonce, ]) } /// Compute the hash of a V2 Declare transaction. -pub fn compute_declare_v2_transaction_hash( +pub fn compute_declare_v2_tx_hash( sender_address: FieldElement, class_hash: FieldElement, - max_fee: FieldElement, + max_fee: u128, chain_id: FieldElement, nonce: FieldElement, compiled_class_hash: FieldElement, @@ -105,7 +105,7 @@ pub fn compute_declare_v2_transaction_hash( sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&[class_hash]), - max_fee, + max_fee.into(), chain_id, nonce, compiled_class_hash, @@ -113,10 +113,10 @@ pub fn compute_declare_v2_transaction_hash( } /// Compute the hash of a V1 Invoke transaction. -pub fn compute_invoke_v1_transaction_hash( +pub fn compute_invoke_v1_tx_hash( sender_address: FieldElement, calldata: &[FieldElement], - max_fee: FieldElement, + max_fee: u128, chain_id: FieldElement, nonce: FieldElement, is_query: bool, @@ -127,7 +127,7 @@ pub fn compute_invoke_v1_transaction_hash( sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(calldata), - max_fee, + max_fee.into(), chain_id, nonce, ]) @@ -136,7 +136,7 @@ pub fn compute_invoke_v1_transaction_hash( /// Computes the hash of a L1 handler transaction /// from the fields involved in the computation, /// as felts values. -pub fn compute_l1_handler_transaction_hash( +pub fn compute_l1_handler_tx_hash( version: FieldElement, contract_address: FieldElement, entry_point_selector: FieldElement, @@ -144,17 +144,75 @@ pub fn compute_l1_handler_transaction_hash( chain_id: FieldElement, nonce: FieldElement, ) -> FieldElement { - // No fee on L2 for L1 handler transaction. - let fee = FieldElement::ZERO; - compute_hash_on_elements(&[ PREFIX_L1_HANDLER, version, contract_address, entry_point_selector, compute_hash_on_elements(calldata), - fee, + FieldElement::ZERO, // No fee on L2 for L1 handler tx chain_id, nonce, ]) } + +#[cfg(test)] +mod tests { + use starknet::core::chain_id; + + use super::*; + + #[test] + fn test_compute_deploy_account_v1_transaction_hash() { + let contract_address = FieldElement::from_hex_be( + "0x0617e350ebed9897037bdef9a09af65049b85ed2e4c9604b640f34bffa152149", + ) + .unwrap(); + let constructor_calldata = vec![ + FieldElement::from_hex_be( + "0x33434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2", + ) + .unwrap(), + FieldElement::from_hex_be( + "0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463", + ) + .unwrap(), + FieldElement::from_hex_be("0x2").unwrap(), + FieldElement::from_hex_be( + "0x43a8fbe19d5ace41a2328bb870143241831180eb3c3c48096642d63709c3096", + ) + .unwrap(), + FieldElement::from_hex_be("0x0").unwrap(), + ]; + let class_hash = FieldElement::from_hex_be( + "0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", + ) + .unwrap(); + let salt = FieldElement::from_hex_be( + "0x43a8fbe19d5ace41a2328bb870143241831180eb3c3c48096642d63709c3096", + ) + .unwrap(); + let max_fee = FieldElement::from_hex_be("0x38d7ea4c68000").unwrap(); + let chain_id = chain_id::MAINNET; + let nonce = FieldElement::ZERO; + + let hash = compute_deploy_account_v1_tx_hash( + contract_address, + &constructor_calldata, + class_hash, + salt, + max_fee.try_into().unwrap(), + chain_id, + nonce, + false, + ); + + assert_eq!( + hash, + FieldElement::from_hex_be( + "0x3d013d17c20a5db05d5c2e06c948a4e0bf5ea5b851b15137316533ec4788b6b" + ) + .unwrap() + ); + } +} From 91d079e2afe1baa188185232bb7f05f0030e5ca7 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:06:03 +0800 Subject: [PATCH 092/192] Create `katana-rpc-types` and `katana-rpc-types-builder` crates --- Cargo.toml | 5 +- .../katana/rpc/rpc-types-builder/Cargo.toml | 16 + .../katana/rpc/rpc-types-builder/src/block.rs | 47 +++ .../katana/rpc/rpc-types-builder/src/lib.rs | 9 + .../rpc/rpc-types-builder/src/receipt.rs | 46 +++ .../rpc/rpc-types-builder/src/state_update.rs | 59 ++++ crates/katana/rpc/rpc-types/Cargo.toml | 19 ++ crates/katana/rpc/rpc-types/src/block.rs | 120 +++++++ crates/katana/rpc/rpc-types/src/event.rs | 2 + crates/katana/rpc/rpc-types/src/lib.rs | 70 ++++ crates/katana/rpc/rpc-types/src/message.rs | 18 ++ crates/katana/rpc/rpc-types/src/receipt.rs | 178 ++++++++++ .../katana/rpc/rpc-types/src/state_update.rs | 75 +++++ .../katana/rpc/rpc-types/src/transaction.rs | 304 ++++++++++++++++++ 14 files changed, 967 insertions(+), 1 deletion(-) create mode 100644 crates/katana/rpc/rpc-types-builder/Cargo.toml create mode 100644 crates/katana/rpc/rpc-types-builder/src/block.rs create mode 100644 crates/katana/rpc/rpc-types-builder/src/lib.rs create mode 100644 crates/katana/rpc/rpc-types-builder/src/receipt.rs create mode 100644 crates/katana/rpc/rpc-types-builder/src/state_update.rs create mode 100644 crates/katana/rpc/rpc-types/Cargo.toml create mode 100644 crates/katana/rpc/rpc-types/src/block.rs create mode 100644 crates/katana/rpc/rpc-types/src/event.rs create mode 100644 crates/katana/rpc/rpc-types/src/lib.rs create mode 100644 crates/katana/rpc/rpc-types/src/message.rs create mode 100644 crates/katana/rpc/rpc-types/src/receipt.rs create mode 100644 crates/katana/rpc/rpc-types/src/state_update.rs create mode 100644 crates/katana/rpc/rpc-types/src/transaction.rs diff --git a/Cargo.toml b/Cargo.toml index 65aabd29ba..c9f1028b12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ + "crates/benches", "crates/dojo-lang", "crates/dojo-language-server", "crates/dojo-signers", @@ -13,12 +14,13 @@ members = [ "crates/katana/executor", "crates/katana/primitives", "crates/katana/rpc", + "crates/katana/rpc/rpc-types", + "crates/katana/rpc/rpc-types-builder", "crates/katana/storage/db", "crates/katana/storage/provider", "crates/sozo", "crates/torii/client", "crates/torii/server", - "crates/benches", ] [workspace.package] @@ -71,6 +73,7 @@ colored = "2" console = "0.15.7" convert_case = "0.6.0" crypto-bigint = { version = "0.5.3", features = [ "serde" ] } +derive_more = "0.99.17" env_logger = "0.10.0" flate2 = "1.0.24" futures = "0.3.28" diff --git a/crates/katana/rpc/rpc-types-builder/Cargo.toml b/crates/katana/rpc/rpc-types-builder/Cargo.toml new file mode 100644 index 0000000000..a29f34ea29 --- /dev/null +++ b/crates/katana/rpc/rpc-types-builder/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "Helper functions for building types used in Katana RPC." +edition.workspace = true +name = "katana-rpc-types-builder" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +katana-executor = { path = "../../executor" } +katana-primitives = { path = "../../primitives" } +katana-provider = { path = "../../storage/provider" } +katana-rpc-types = { path = "../rpc-types" } + +anyhow.workspace = true +starknet.workspace = true diff --git a/crates/katana/rpc/rpc-types-builder/src/block.rs b/crates/katana/rpc/rpc-types-builder/src/block.rs new file mode 100644 index 0000000000..f894bcb7e7 --- /dev/null +++ b/crates/katana/rpc/rpc-types-builder/src/block.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use katana_primitives::block::BlockHashOrNumber; +use katana_provider::traits::block::{BlockHashProvider, BlockProvider, BlockStatusProvider}; +use katana_rpc_types::block::{BlockWithTxHashes, BlockWithTxs}; + +/// A builder for building RPC block types. +pub struct BlockBuilder

{ + provider: P, + block_id: BlockHashOrNumber, +} + +impl

BlockBuilder

{ + pub fn new(block_id: BlockHashOrNumber, provider: P) -> Self { + Self { provider, block_id } + } +} + +impl

BlockBuilder

+where + P: BlockProvider + BlockHashProvider, +{ + pub fn build(self) -> Result> { + let Some(hash) = BlockHashProvider::block_hash_by_id(&self.provider, self.block_id)? else { + return Ok(None); + }; + + let block = BlockProvider::block(&self.provider, self.block_id)? + .expect("should exist if hash exists"); + let finality_status = BlockStatusProvider::block_status(&self.provider, self.block_id)? + .expect("should exist if block exists"); + + Ok(Some(BlockWithTxs::new(hash, block, finality_status))) + } + + pub fn build_with_tx_hash(self) -> Result> { + let Some(hash) = BlockHashProvider::block_hash_by_id(&self.provider, self.block_id)? else { + return Ok(None); + }; + + let block = BlockProvider::block_with_tx_hashes(&self.provider, self.block_id)? + .expect("should exist if block exists"); + let finality_status = BlockStatusProvider::block_status(&self.provider, self.block_id)? + .expect("should exist if block exists"); + + Ok(Some(BlockWithTxHashes::new(hash, block, finality_status))) + } +} diff --git a/crates/katana/rpc/rpc-types-builder/src/lib.rs b/crates/katana/rpc/rpc-types-builder/src/lib.rs new file mode 100644 index 0000000000..d40380d4fe --- /dev/null +++ b/crates/katana/rpc/rpc-types-builder/src/lib.rs @@ -0,0 +1,9 @@ +//! This crate provides convenient builders for complex types used in Katana wire format. + +mod block; +mod receipt; +mod state_update; + +pub use block::*; +pub use receipt::*; +pub use state_update::*; diff --git a/crates/katana/rpc/rpc-types-builder/src/receipt.rs b/crates/katana/rpc/rpc-types-builder/src/receipt.rs new file mode 100644 index 0000000000..36dac22825 --- /dev/null +++ b/crates/katana/rpc/rpc-types-builder/src/receipt.rs @@ -0,0 +1,46 @@ +use katana_primitives::transaction::TxHash; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, +}; +use katana_rpc_types::receipt::TxReceipt; + +/// A builder for building RPC transaction receipt types. +pub struct ReceiptBuilder

{ + provider: P, + transaction_hash: TxHash, +} + +impl

ReceiptBuilder

{ + pub fn new(transaction_hash: TxHash, provider: P) -> Self { + Self { provider, transaction_hash } + } +} + +impl

ReceiptBuilder

+where + P: TransactionProvider + TransactionStatusProvider + ReceiptProvider, +{ + pub fn build(&self) -> anyhow::Result> { + let receipt = ReceiptProvider::receipt_by_hash(&self.provider, self.transaction_hash)?; + let Some(receipt) = receipt else { return Ok(None) }; + + let transaction_hash = self.transaction_hash; + let (block_number, block_hash) = TransactionProvider::transaction_block_num_and_hash( + &self.provider, + self.transaction_hash, + )? + .expect("must exist"); + + let finality_status = + TransactionStatusProvider::transaction_status(&self.provider, self.transaction_hash)? + .expect("must exist"); + + Ok(Some(TxReceipt::new( + transaction_hash, + block_number, + block_hash, + finality_status, + receipt, + ))) + } +} diff --git a/crates/katana/rpc/rpc-types-builder/src/state_update.rs b/crates/katana/rpc/rpc-types-builder/src/state_update.rs new file mode 100644 index 0000000000..b0f85cbc58 --- /dev/null +++ b/crates/katana/rpc/rpc-types-builder/src/state_update.rs @@ -0,0 +1,59 @@ +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::FieldElement; +use katana_provider::traits::block::{BlockHashProvider, BlockNumberProvider}; +use katana_provider::traits::state::StateRootProvider; +use katana_provider::traits::state_update::StateUpdateProvider; +use katana_rpc_types::state_update::{StateDiff, StateUpdate}; + +/// A builder for building RPC state update type. +pub struct StateUpdateBuilder

{ + provider: P, + block_id: BlockHashOrNumber, +} + +impl

StateUpdateBuilder

{ + pub fn new(block_id: BlockHashOrNumber, provider: P) -> Self { + Self { provider, block_id } + } +} + +impl

StateUpdateBuilder

+where + P: BlockHashProvider + BlockNumberProvider + StateRootProvider + StateUpdateProvider, +{ + /// Builds a state update for the given block. + pub fn build(self) -> anyhow::Result> { + let Some(block_hash) = BlockHashProvider::block_hash_by_id(&self.provider, self.block_id)? + else { + return Ok(None); + }; + + let new_root = StateRootProvider::state_root(&self.provider, self.block_id)? + .expect("should exist if block exists"); + let old_root = { + let block_num = BlockNumberProvider::block_number_by_hash(&self.provider, block_hash)? + .expect("should exist if block exists"); + + match block_num { + 0 => FieldElement::ZERO, + _ => StateRootProvider::state_root(&self.provider, (block_num - 1).into())? + .expect("should exist if not genesis"), + } + }; + + let state_diff: StateDiff = + StateUpdateProvider::state_update(&self.provider, self.block_id)? + .expect("should exist if block exists") + .into(); + + Ok(Some( + starknet::core::types::StateUpdate { + block_hash, + new_root, + old_root, + state_diff: state_diff.0, + } + .into(), + )) + } +} diff --git a/crates/katana/rpc/rpc-types/Cargo.toml b/crates/katana/rpc/rpc-types/Cargo.toml new file mode 100644 index 0000000000..792d2cccc2 --- /dev/null +++ b/crates/katana/rpc/rpc-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +description = "Types used in Katana RPC layer." +edition.workspace = true +name = "katana-rpc-types" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +katana-primitives = { path = "../../primitives" } + +anyhow.workspace = true +derive_more.workspace = true +serde.workspace = true +serde_with.workspace = true +starknet.workspace = true + +[dev-dependencies] +serde_json.workspace = true diff --git a/crates/katana/rpc/rpc-types/src/block.rs b/crates/katana/rpc/rpc-types/src/block.rs new file mode 100644 index 0000000000..32f3b12ae3 --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/block.rs @@ -0,0 +1,120 @@ +use katana_primitives::block::{Block, BlockHash, BlockNumber, FinalityStatus, PartialHeader}; +use katana_primitives::transaction::{TxHash, TxWithHash}; +use serde::Serialize; +use starknet::core::types::BlockStatus; + +pub type BlockTxCount = u64; + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct BlockWithTxs(starknet::core::types::BlockWithTxs); + +impl BlockWithTxs { + pub fn new(block_hash: BlockHash, block: Block, finality_status: FinalityStatus) -> Self { + let transactions = + block.body.into_iter().map(|tx| crate::transaction::Tx::from(tx).0).collect(); + + Self(starknet::core::types::BlockWithTxs { + block_hash, + transactions, + new_root: block.header.state_root, + timestamp: block.header.timestamp, + block_number: block.header.number, + parent_hash: block.header.parent_hash, + sequencer_address: block.header.sequencer_address.into(), + status: match finality_status { + FinalityStatus::AcceptedOnL1 => BlockStatus::AcceptedOnL1, + FinalityStatus::AcceptedOnL2 => BlockStatus::AcceptedOnL2, + }, + }) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct PendingBlockWithTxs(starknet::core::types::PendingBlockWithTxs); + +impl PendingBlockWithTxs { + pub fn new(header: PartialHeader, transactions: Vec) -> Self { + let transactions = + transactions.into_iter().map(|tx| crate::transaction::Tx::from(tx).0).collect(); + + Self(starknet::core::types::PendingBlockWithTxs { + transactions, + timestamp: header.timestamp, + parent_hash: header.parent_hash, + sequencer_address: header.sequencer_address.into(), + }) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum MaybePendingBlockWithTxs { + Pending(PendingBlockWithTxs), + Block(BlockWithTxs), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct BlockWithTxHashes(starknet::core::types::BlockWithTxHashes); + +impl BlockWithTxHashes { + pub fn new( + block_hash: BlockHash, + block: katana_primitives::block::BlockWithTxHashes, + finality_status: FinalityStatus, + ) -> Self { + Self(starknet::core::types::BlockWithTxHashes { + block_hash, + transactions: block.body, + new_root: block.header.state_root, + timestamp: block.header.timestamp, + block_number: block.header.number, + parent_hash: block.header.parent_hash, + sequencer_address: block.header.sequencer_address.into(), + status: match finality_status { + FinalityStatus::AcceptedOnL1 => BlockStatus::AcceptedOnL1, + FinalityStatus::AcceptedOnL2 => BlockStatus::AcceptedOnL2, + }, + }) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct PendingBlockWithTxHashes(starknet::core::types::PendingBlockWithTxHashes); + +impl PendingBlockWithTxHashes { + pub fn new(header: PartialHeader, transactions: Vec) -> Self { + Self(starknet::core::types::PendingBlockWithTxHashes { + transactions, + timestamp: header.timestamp, + parent_hash: header.parent_hash, + sequencer_address: header.sequencer_address.into(), + }) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum MaybePendingBlockWithTxHashes { + Pending(PendingBlockWithTxHashes), + Block(BlockWithTxHashes), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct BlockHashAndNumber(starknet::core::types::BlockHashAndNumber); + +impl BlockHashAndNumber { + pub fn new(hash: BlockHash, number: BlockNumber) -> Self { + Self(starknet::core::types::BlockHashAndNumber { block_hash: hash, block_number: number }) + } +} + +impl From<(BlockHash, BlockNumber)> for BlockHashAndNumber { + fn from((hash, number): (BlockHash, BlockNumber)) -> Self { + Self::new(hash, number) + } +} diff --git a/crates/katana/rpc/rpc-types/src/event.rs b/crates/katana/rpc/rpc-types/src/event.rs new file mode 100644 index 0000000000..f171b32082 --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/event.rs @@ -0,0 +1,2 @@ +pub type EventFilterWithPage = starknet::core::types::EventFilterWithPage; +pub type EventsPage = starknet::core::types::EventsPage; diff --git a/crates/katana/rpc/rpc-types/src/lib.rs b/crates/katana/rpc/rpc-types/src/lib.rs new file mode 100644 index 0000000000..e5035af7ed --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/lib.rs @@ -0,0 +1,70 @@ +//! Types used in the Katana JSON-RPC API. +//! +//! Most of the types defined in this crate are simple wrappers around types imported from +//! `starknet-rs`. + +pub mod block; +pub mod event; +pub mod message; +pub mod receipt; +pub mod state_update; +pub mod transaction; + +use std::ops::Deref; + +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use starknet::core::serde::unsigned_field_element::UfeHex; + +/// A wrapper around [`FieldElement`](katana_primitives::FieldElement) that serializes to hex as +/// default. +#[serde_as] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct FeltAsHex(#[serde_as(serialize_as = "UfeHex")] katana_primitives::FieldElement); + +impl From for FeltAsHex { + fn from(value: katana_primitives::FieldElement) -> Self { + Self(value) + } +} + +impl From for katana_primitives::FieldElement { + fn from(value: FeltAsHex) -> Self { + value.0 + } +} + +impl Deref for FeltAsHex { + type Target = katana_primitives::FieldElement; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub type FunctionCall = starknet::core::types::FunctionCall; + +pub type FeeEstimate = starknet::core::types::FeeEstimate; + +pub type ContractClass = starknet::core::types::ContractClass; + +#[cfg(test)] +mod tests { + use serde_json::json; + use starknet::macros::felt; + + use super::FeltAsHex; + + #[test] + fn serde_felt() { + let value = felt!("0x12345"); + let value_as_dec = json!(value); + let value_as_hex = json!(format!("{value:#x}")); + + let expected_value = FeltAsHex(value); + let actual_des_value: FeltAsHex = serde_json::from_value(value_as_dec).unwrap(); + assert_eq!(expected_value, actual_des_value, "should deserialize to decimal"); + + let actual_ser_value = serde_json::to_value(expected_value).unwrap(); + assert_eq!(value_as_hex, actual_ser_value, "should serialize to hex"); + } +} diff --git a/crates/katana/rpc/rpc-types/src/message.rs b/crates/katana/rpc/rpc-types/src/message.rs new file mode 100644 index 0000000000..cb51551957 --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/message.rs @@ -0,0 +1,18 @@ +use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx}; +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct MsgFromL1(starknet::core::types::MsgFromL1); + +impl From for ExecutableTxWithHash { + fn from(value: MsgFromL1) -> Self { + let tx = L1HandlerTx { + calldata: value.0.payload, + contract_address: value.0.to_address.into(), + entry_point_selector: value.0.entry_point_selector, + ..Default::default() + }; + let hash = tx.calculate_hash(); + ExecutableTxWithHash { hash, transaction: tx.into() } + } +} diff --git a/crates/katana/rpc/rpc-types/src/receipt.rs b/crates/katana/rpc/rpc-types/src/receipt.rs new file mode 100644 index 0000000000..b08b28bba5 --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/receipt.rs @@ -0,0 +1,178 @@ +use katana_primitives::block::{BlockHash, BlockNumber, FinalityStatus}; +use katana_primitives::receipt::Receipt; +use katana_primitives::transaction::TxHash; +use serde::Serialize; +use starknet::core::types::{ + DeclareTransactionReceipt, DeployAccountTransactionReceipt, ExecutionResult, + InvokeTransactionReceipt, L1HandlerTransactionReceipt, PendingDeclareTransactionReceipt, + PendingDeployAccountTransactionReceipt, PendingInvokeTransactionReceipt, + PendingL1HandlerTransactionReceipt, PendingTransactionReceipt, TransactionFinalityStatus, + TransactionReceipt, +}; + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct TxReceipt(starknet::core::types::TransactionReceipt); + +impl TxReceipt { + pub fn new( + transaction_hash: TxHash, + block_number: BlockNumber, + block_hash: BlockHash, + finality_status: FinalityStatus, + receipt: Receipt, + ) -> Self { + let finality_status = match finality_status { + FinalityStatus::AcceptedOnL1 => TransactionFinalityStatus::AcceptedOnL1, + FinalityStatus::AcceptedOnL2 => TransactionFinalityStatus::AcceptedOnL2, + }; + + let receipt = match receipt { + Receipt::Invoke(rct) => TransactionReceipt::Invoke(InvokeTransactionReceipt { + block_hash, + block_number, + finality_status, + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }), + + Receipt::Declare(rct) => TransactionReceipt::Declare(DeclareTransactionReceipt { + block_hash, + block_number, + finality_status, + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }), + + Receipt::L1Handler(rct) => TransactionReceipt::L1Handler(L1HandlerTransactionReceipt { + block_hash, + block_number, + finality_status, + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }), + + Receipt::DeployAccount(rct) => { + TransactionReceipt::DeployAccount(DeployAccountTransactionReceipt { + block_hash, + block_number, + finality_status, + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + contract_address: rct.contract_address.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + }; + + Self(receipt) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct PendingTxReceipt(starknet::core::types::PendingTransactionReceipt); + +impl PendingTxReceipt { + pub fn new(transaction_hash: TxHash, receipt: Receipt) -> Self { + let receipt = match receipt { + Receipt::Invoke(rct) => { + PendingTransactionReceipt::Invoke(PendingInvokeTransactionReceipt { + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + + Receipt::Declare(rct) => { + PendingTransactionReceipt::Declare(PendingDeclareTransactionReceipt { + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + + Receipt::L1Handler(rct) => { + PendingTransactionReceipt::L1Handler(PendingL1HandlerTransactionReceipt { + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + + Receipt::DeployAccount(rct) => { + PendingTransactionReceipt::DeployAccount(PendingDeployAccountTransactionReceipt { + transaction_hash, + events: rct.events, + messages_sent: rct.messages_sent, + actual_fee: rct.actual_fee.into(), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + }; + + Self(receipt) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum MaybePendingTxReceipt { + Receipt(TxReceipt), + Pending(PendingTxReceipt), +} + +impl From for TxReceipt { + fn from(receipt: starknet::core::types::TransactionReceipt) -> Self { + Self(receipt) + } +} diff --git a/crates/katana/rpc/rpc-types/src/state_update.rs b/crates/katana/rpc/rpc-types/src/state_update.rs new file mode 100644 index 0000000000..456cbf7f99 --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/state_update.rs @@ -0,0 +1,75 @@ +use serde::Serialize; +use starknet::core::types::{ + ContractStorageDiffItem, DeclaredClassItem, DeployedContractItem, NonceUpdate, StorageEntry, +}; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum MaybePendingStateUpdate { + Pending(PendingStateUpdate), + Update(StateUpdate), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct StateUpdate(starknet::core::types::StateUpdate); + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct PendingStateUpdate(starknet::core::types::PendingStateUpdate); + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct StateDiff(pub starknet::core::types::StateDiff); + +impl From for StateUpdate { + fn from(value: starknet::core::types::StateUpdate) -> Self { + StateUpdate(value) + } +} + +impl From for StateDiff { + fn from(value: katana_primitives::state::StateUpdates) -> Self { + let nonces: Vec = value + .nonce_updates + .into_iter() + .map(|(addr, nonce)| NonceUpdate { nonce, contract_address: addr.into() }) + .collect(); + + let declared_classes: Vec = value + .declared_classes + .into_iter() + .map(|(class_hash, compiled_class_hash)| DeclaredClassItem { + class_hash, + compiled_class_hash, + }) + .collect(); + + let deployed_contracts: Vec = value + .contract_updates + .into_iter() + .map(|(addr, class_hash)| DeployedContractItem { address: addr.into(), class_hash }) + .collect(); + + let storage_diffs: Vec = value + .storage_updates + .into_iter() + .map(|(addr, entries)| ContractStorageDiffItem { + address: addr.into(), + storage_entries: entries + .into_iter() + .map(|(key, value)| StorageEntry { key, value }) + .collect(), + }) + .collect(); + + StateDiff(starknet::core::types::StateDiff { + nonces, + storage_diffs, + declared_classes, + deployed_contracts, + replaced_classes: Default::default(), + deprecated_declared_classes: Default::default(), + }) + } +} diff --git a/crates/katana/rpc/rpc-types/src/transaction.rs b/crates/katana/rpc/rpc-types/src/transaction.rs new file mode 100644 index 0000000000..a28ff1f0ea --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/transaction.rs @@ -0,0 +1,304 @@ +use std::sync::Arc; + +use anyhow::Result; +use derive_more::Deref; +use katana_primitives::contract::{ClassHash, ContractAddress}; +use katana_primitives::conversion::rpc::{ + compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, +}; +use katana_primitives::transaction::{ + DeclareTx, DeclareTxV1, DeclareTxV2, DeclareTxWithClass, DeployAccountTx, InvokeTx, TxHash, + TxWithHash, +}; +use katana_primitives::FieldElement; +use serde::{Deserialize, Serialize}; +use starknet::core::types::{ + BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, + BroadcastedInvokeTransaction, DeclareTransactionResult, DeployAccountTransactionResult, + InvokeTransactionResult, +}; +use starknet::core::utils::get_contract_address; + +#[derive(Debug, Clone, Deserialize, Deref)] +#[serde(transparent)] +pub struct BroadcastedInvokeTx(BroadcastedInvokeTransaction); + +impl BroadcastedInvokeTx { + pub fn into_tx_with_chain_id(self, chain_id: FieldElement) -> InvokeTx { + InvokeTx { + chain_id, + nonce: self.0.nonce, + calldata: self.0.calldata, + signature: self.0.signature, + version: FieldElement::ONE, + sender_address: self.0.sender_address.into(), + max_fee: self.0.max_fee.try_into().expect("max_fee is too big"), + } + } +} + +#[derive(Debug, Clone, Deserialize, Deref)] +#[serde(transparent)] +pub struct BroadcastedDeclareTx(BroadcastedDeclareTransaction); + +impl BroadcastedDeclareTx { + /// Validates that the provided compiled class hash is computed correctly from the class + /// provided in the transaction. + pub fn validate_compiled_class_hash(&self) -> Result { + let res = match &self.0 { + BroadcastedDeclareTransaction::V1(_) => true, + BroadcastedDeclareTransaction::V2(tx) => { + let hash = compiled_class_hash_from_flattened_sierra_class(&tx.contract_class)?; + hash == tx.compiled_class_hash + } + }; + Ok(res) + } + + /// This function assumes that the compiled class hash is valid. + pub fn try_into_tx_with_chain_id(self, chain_id: FieldElement) -> Result { + match self.0 { + BroadcastedDeclareTransaction::V1(tx) => { + let (class_hash, compiled_class) = legacy_rpc_to_inner_class(&tx.contract_class)?; + + Ok(DeclareTxWithClass { + compiled_class, + sierra_class: None, + transaction: DeclareTx::V1(DeclareTxV1 { + chain_id, + class_hash, + nonce: tx.nonce, + signature: tx.signature, + sender_address: tx.sender_address.into(), + max_fee: tx.max_fee.try_into().expect("max fee is too large"), + }), + }) + } + + BroadcastedDeclareTransaction::V2(tx) => { + // TODO: avoid computing the class hash again + let (class_hash, _, compiled_class) = rpc_to_inner_class(&tx.contract_class)?; + + Ok(DeclareTxWithClass { + compiled_class, + sierra_class: Arc::into_inner(tx.contract_class), + transaction: DeclareTx::V2(DeclareTxV2 { + chain_id, + class_hash, + nonce: tx.nonce, + signature: tx.signature, + sender_address: tx.sender_address.into(), + compiled_class_hash: tx.compiled_class_hash, + max_fee: tx.max_fee.try_into().expect("max fee is too large"), + }), + }) + } + } + } + + pub fn is_query(&self) -> bool { + match &self.0 { + BroadcastedDeclareTransaction::V1(tx) => tx.is_query, + BroadcastedDeclareTransaction::V2(tx) => tx.is_query, + } + } +} + +#[derive(Debug, Clone, Deserialize, Deref)] +#[serde(transparent)] +pub struct BroadcastedDeployAccountTx(BroadcastedDeployAccountTransaction); + +impl BroadcastedDeployAccountTx { + pub fn into_tx_with_chain_id(self, chain_id: FieldElement) -> DeployAccountTx { + let contract_address = get_contract_address( + self.0.contract_address_salt, + self.0.class_hash, + &self.0.constructor_calldata, + chain_id, + ); + + DeployAccountTx { + chain_id, + nonce: self.0.nonce, + version: FieldElement::ONE, + signature: self.0.signature, + class_hash: self.0.class_hash, + contract_address: contract_address.into(), + constructor_calldata: self.0.constructor_calldata, + contract_address_salt: self.0.contract_address_salt, + max_fee: self.0.max_fee.try_into().expect("max_fee is too big"), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum BroadcastedTx { + Invoke(BroadcastedInvokeTx), + Declare(BroadcastedDeclareTx), + DeployAccount(BroadcastedDeployAccountTx), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct Tx(pub starknet::core::types::Transaction); + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct DeployAccountTxResult(DeployAccountTransactionResult); + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct DeclareTxResult(DeclareTransactionResult); + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct InvokeTxResult(InvokeTransactionResult); + +impl From for Tx { + fn from(value: TxWithHash) -> Self { + use katana_primitives::transaction::Tx as InternalTx; + + let transaction_hash = value.hash; + let tx = match value.transaction { + InternalTx::Invoke(tx) => starknet::core::types::Transaction::Invoke( + starknet::core::types::InvokeTransaction::V1( + starknet::core::types::InvokeTransactionV1 { + nonce: tx.nonce, + transaction_hash, + calldata: tx.calldata, + signature: tx.signature, + max_fee: tx.max_fee.into(), + sender_address: tx.sender_address.into(), + }, + ), + ), + + InternalTx::Declare(tx) => starknet::core::types::Transaction::Declare(match tx { + DeclareTx::V1(tx) => starknet::core::types::DeclareTransaction::V1( + starknet::core::types::DeclareTransactionV1 { + nonce: tx.nonce, + transaction_hash, + signature: tx.signature, + class_hash: tx.class_hash, + max_fee: tx.max_fee.into(), + sender_address: tx.sender_address.into(), + }, + ), + + DeclareTx::V2(tx) => starknet::core::types::DeclareTransaction::V2( + starknet::core::types::DeclareTransactionV2 { + nonce: tx.nonce, + transaction_hash, + signature: tx.signature, + class_hash: tx.class_hash, + max_fee: tx.max_fee.into(), + sender_address: tx.sender_address.into(), + compiled_class_hash: tx.compiled_class_hash, + }, + ), + }), + + InternalTx::L1Handler(tx) => starknet::core::types::Transaction::L1Handler( + starknet::core::types::L1HandlerTransaction { + transaction_hash, + calldata: tx.calldata, + contract_address: tx.contract_address.into(), + entry_point_selector: tx.entry_point_selector, + nonce: tx.nonce.try_into().expect("nonce should fit in u64"), + version: tx.version.try_into().expect("version should fit in u64"), + }, + ), + + InternalTx::DeployAccount(tx) => starknet::core::types::Transaction::DeployAccount( + starknet::core::types::DeployAccountTransaction { + transaction_hash, + nonce: tx.nonce, + signature: tx.signature, + class_hash: tx.class_hash, + max_fee: tx.max_fee.into(), + constructor_calldata: tx.constructor_calldata, + contract_address_salt: tx.contract_address_salt, + }, + ), + }; + + Tx(tx) + } +} + +impl DeployAccountTxResult { + pub fn new(transaction_hash: TxHash, contract_address: ContractAddress) -> Self { + Self(DeployAccountTransactionResult { + transaction_hash, + contract_address: contract_address.into(), + }) + } +} + +impl DeclareTxResult { + pub fn new(transaction_hash: TxHash, class_hash: ClassHash) -> Self { + Self(DeclareTransactionResult { transaction_hash, class_hash }) + } +} + +impl InvokeTxResult { + pub fn new(transaction_hash: TxHash) -> Self { + Self(InvokeTransactionResult { transaction_hash }) + } +} + +impl From<(TxHash, ContractAddress)> for DeployAccountTxResult { + fn from((transaction_hash, contract_address): (TxHash, ContractAddress)) -> Self { + Self::new(transaction_hash, contract_address) + } +} + +impl From<(TxHash, ClassHash)> for DeclareTxResult { + fn from((transaction_hash, class_hash): (TxHash, ClassHash)) -> Self { + Self::new(transaction_hash, class_hash) + } +} + +impl From for InvokeTxResult { + fn from(transaction_hash: TxHash) -> Self { + Self::new(transaction_hash) + } +} + +impl From for InvokeTx { + fn from(tx: BroadcastedInvokeTx) -> Self { + InvokeTx { + nonce: tx.0.nonce, + calldata: tx.0.calldata, + signature: tx.0.signature, + version: FieldElement::ONE, + chain_id: FieldElement::ZERO, + sender_address: tx.0.sender_address.into(), + max_fee: tx.0.max_fee.try_into().expect("max_fee is too big"), + } + } +} + +impl From for DeployAccountTx { + fn from(tx: BroadcastedDeployAccountTx) -> Self { + let contract_address = get_contract_address( + tx.0.contract_address_salt, + tx.0.class_hash, + &tx.0.constructor_calldata, + FieldElement::ZERO, + ); + + DeployAccountTx { + nonce: tx.0.nonce, + signature: tx.0.signature, + version: FieldElement::ONE, + class_hash: tx.0.class_hash, + chain_id: FieldElement::ZERO, + contract_address: contract_address.into(), + constructor_calldata: tx.0.constructor_calldata, + contract_address_salt: tx.0.contract_address_salt, + max_fee: tx.0.max_fee.try_into().expect("max_fee is too big"), + } + } +} From c3435b1c197eeacd0fbf6ed5bb0bfdbb54fd2459 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:10:08 +0800 Subject: [PATCH 093/192] refactor(katana-providers): update traits --- crates/katana/storage/provider/src/lib.rs | 152 +++++++- .../provider/src/providers/fork/backend.rs | 21 +- .../provider/src/providers/fork/mod.rs | 335 ++++++++++++++--- .../provider/src/providers/fork/state.rs | 10 +- .../provider/src/providers/in_memory/cache.rs | 48 ++- .../provider/src/providers/in_memory/mod.rs | 350 +++++++++++++++--- .../provider/src/providers/in_memory/state.rs | 18 +- .../storage/provider/src/traits/block.rs | 87 ++++- .../storage/provider/src/traits/contract.rs | 4 +- .../katana/storage/provider/src/traits/env.rs | 8 + .../katana/storage/provider/src/traits/mod.rs | 1 + .../storage/provider/src/traits/state.rs | 20 +- .../provider/src/traits/state_update.rs | 7 +- .../provider/src/traits/transaction.rs | 29 +- 14 files changed, 930 insertions(+), 160 deletions(-) create mode 100644 crates/katana/storage/provider/src/traits/env.rs diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index 36aad5bebc..ce11642b2d 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -1,15 +1,23 @@ use std::ops::{Range, RangeInclusive}; use anyhow::Result; +use katana_db::models::block::StoredBlockBodyIndices; use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, + Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, Header, + SealedBlockWithStatus, }; use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; -use traits::contract::ContractClassProvider; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::{TxHash, TxNumber, TxWithHash}; +use katana_primitives::FieldElement; +use traits::block::{BlockIdReader, BlockStatusProvider, BlockWriter}; +use traits::contract::{ContractClassProvider, ContractClassWriter}; +use traits::state::{StateRootProvider, StateWriter}; +use traits::transaction::TransactionStatusProvider; pub mod providers; pub mod traits; @@ -42,9 +50,17 @@ where self.provider.block(id) } + fn block_with_tx_hashes(&self, id: BlockHashOrNumber) -> Result> { + self.provider.block_with_tx_hashes(id) + } + fn blocks_in_range(&self, range: RangeInclusive) -> Result> { self.provider.blocks_in_range(range) } + + fn block_body_indices(&self, id: BlockHashOrNumber) -> Result> { + self.provider.block_body_indices(id) + } } impl HeaderProvider for BlockchainProvider @@ -82,24 +98,72 @@ where } } +impl BlockIdReader for BlockchainProvider where Db: BlockNumberProvider {} + +impl BlockStatusProvider for BlockchainProvider +where + Db: BlockStatusProvider, +{ + fn block_status(&self, id: BlockHashOrNumber) -> Result> { + self.provider.block_status(id) + } +} + +impl BlockWriter for BlockchainProvider +where + Db: BlockWriter, +{ + fn insert_block_with_states_and_receipts( + &self, + block: SealedBlockWithStatus, + states: StateUpdatesWithDeclaredClasses, + receipts: Vec, + ) -> Result<()> { + self.provider.insert_block_with_states_and_receipts(block, states, receipts) + } +} + impl TransactionProvider for BlockchainProvider where Db: TransactionProvider, { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { self.provider.transaction_by_hash(hash) } + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { + self.provider.transactions_by_block(block_id) + } + fn transaction_by_block_and_idx( &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { self.provider.transaction_by_block_and_idx(block_id, idx) } - fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { - self.provider.transactions_by_block(block_id) + fn transaction_count_by_block(&self, block_id: BlockHashOrNumber) -> Result> { + self.provider.transaction_count_by_block(block_id) + } + + fn transaction_block_num_and_hash( + &self, + hash: TxHash, + ) -> Result> { + TransactionProvider::transaction_block_num_and_hash(&self.provider, hash) + } +} + +impl TransactionStatusProvider for BlockchainProvider +where + Db: TransactionStatusProvider, +{ + fn transaction_status(&self, hash: TxHash) -> Result> { + TransactionStatusProvider::transaction_status(&self.provider, hash) } } @@ -107,8 +171,8 @@ impl TransactionsProviderExt for BlockchainProvider where Db: TransactionsProviderExt, { - fn transaction_hashes_by_range(&self, range: Range) -> Result> { - self.provider.transaction_hashes_by_range(range) + fn transaction_hashes_in_range(&self, range: Range) -> Result> { + TransactionsProviderExt::transaction_hashes_in_range(&self.provider, range) } } @@ -129,10 +193,6 @@ impl StateProvider for BlockchainProvider where Db: StateProvider, { - fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - self.provider.class_hash_of_contract(address) - } - fn nonce( &self, address: ContractAddress, @@ -147,6 +207,10 @@ where ) -> Result> { self.provider.storage(address, storage_key) } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + self.provider.class_hash_of_contract(address) + } } impl ContractClassProvider for BlockchainProvider @@ -186,7 +250,7 @@ impl StateUpdateProvider for BlockchainProvider where Db: StateUpdateProvider, { - fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { self.provider.state_update(block_id) } } @@ -199,3 +263,63 @@ where self.provider.contract(address) } } + +impl StateRootProvider for BlockchainProvider +where + Db: StateRootProvider, +{ + fn state_root(&self, block_id: BlockHashOrNumber) -> Result> { + self.provider.state_root(block_id) + } +} + +impl ContractClassWriter for BlockchainProvider +where + Db: ContractClassWriter, +{ + fn set_class(&self, hash: ClassHash, class: CompiledContractClass) -> Result<()> { + self.provider.set_class(hash, class) + } + + fn set_compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + compiled_hash: CompiledClassHash, + ) -> Result<()> { + self.provider.set_compiled_class_hash_of_class_hash(hash, compiled_hash) + } + + fn set_sierra_class(&self, hash: ClassHash, sierra: SierraClass) -> Result<()> { + self.provider.set_sierra_class(hash, sierra) + } +} + +impl StateWriter for BlockchainProvider +where + Db: StateWriter, +{ + fn set_storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + storage_value: StorageValue, + ) -> Result<()> { + self.provider.set_storage(address, storage_key, storage_value) + } + + fn set_class_hash_of_contract( + &self, + address: ContractAddress, + class_hash: ClassHash, + ) -> Result<()> { + self.provider.set_class_hash_of_contract(address, class_hash) + } + + fn set_nonce( + &self, + address: ContractAddress, + nonce: katana_primitives::contract::Nonce, + ) -> Result<()> { + self.provider.set_nonce(address, nonce) + } +} diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index ec27ff7fe2..95b2abc9ca 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -344,12 +344,14 @@ impl StateProvider for SharedStateProvider { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - if let Some(value) = self.0.storage.read().get(&(address, storage_key)).cloned() { - return Ok(Some(value)); + if let value @ Some(_) = + self.0.storage.read().get(&address).and_then(|s| s.get(&storage_key)).copied() + { + return Ok(value); } let value = self.0.do_get_storage(address, storage_key).unwrap(); - self.0.storage.write().entry((address, storage_key)).or_insert(value); + self.0.storage.write().entry(address).or_default().insert(storage_key, value); Ok(Some(value)) } @@ -480,7 +482,13 @@ mod tests { let (backend, _) = create_forked_backend(LOCAL_RPC_URL.into(), 1); let state_db = CacheStateDb::new(backend); - state_db.storage.write().insert((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE); + state_db + .storage + .write() + .entry(ADDR_1) + .or_default() + .insert(STORAGE_KEY, ADDR_1_STORAGE_VALUE); + state_db.contract_state.write().insert( ADDR_1, GenericContractInfo { nonce: ADDR_1_NONCE, class_hash: ADDR_1_CLASS_HASH }, @@ -539,8 +547,9 @@ mod tests { .0 .storage .read() - .get(&(GOERLI_CONTRACT_ADDR, GOERLI_CONTRACT_STORAGE_KEY)) - .cloned(); + .get(&GOERLI_CONTRACT_ADDR) + .and_then(|s| s.get(&GOERLI_CONTRACT_STORAGE_KEY)) + .copied(); let nonce_in_cache = provider.0.contract_state.read().get(&GOERLI_CONTRACT_ADDR).map(|i| i.nonce); diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 838dfed6a7..32b4df213a 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -7,10 +7,16 @@ use std::sync::Arc; use anyhow::Result; use katana_db::models::block::StoredBlockBodyIndices; use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, + Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, Header, + SealedBlockWithStatus, }; -use katana_primitives::contract::{ContractAddress, GenericContractInfo}; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + SierraClass, +}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; use parking_lot::RwLock; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -19,15 +25,20 @@ use self::backend::{ForkedBackend, SharedStateProvider}; use self::state::ForkedStateDb; use super::in_memory::cache::{CacheDb, CacheStateDb}; use super::in_memory::state::HistoricalStates; -use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; -use crate::traits::contract::ContractInfoProvider; -use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::block::{ + BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, + HeaderProvider, +}; +use crate::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use crate::traits::state::{StateFactoryProvider, StateProvider, StateRootProvider, StateWriter}; use crate::traits::state_update::StateUpdateProvider; -use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; +use crate::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionsProviderExt, +}; pub struct ForkedProvider { // TODO: insert `ForkedBackend` into `CacheDb` - storage: CacheDb<()>, + storage: RwLock>, state: Arc, historical_states: RwLock, } @@ -37,7 +48,7 @@ impl ForkedProvider { let backend = ForkedBackend::new_with_backend_thread(provider, block_id); let shared_provider = SharedStateProvider::new_with_backend(backend); - let storage = CacheDb::new(()); + let storage = RwLock::new(CacheDb::new(())); let state = Arc::new(CacheStateDb::new(shared_provider)); let historical_states = RwLock::new(HistoricalStates::default()); @@ -47,21 +58,21 @@ impl ForkedProvider { impl BlockHashProvider for ForkedProvider { fn latest_hash(&self) -> Result { - Ok(self.storage.latest_block_hash) + Ok(self.storage.read().latest_block_hash) } fn block_hash_by_num(&self, num: BlockNumber) -> Result> { - Ok(self.storage.block_hashes.get(&num).cloned()) + Ok(self.storage.read().block_hashes.get(&num).cloned()) } } impl BlockNumberProvider for ForkedProvider { fn latest_number(&self) -> Result { - Ok(self.storage.latest_block_number) + Ok(self.storage.read().latest_block_number) } fn block_number_by_hash(&self, hash: BlockHash) -> Result> { - Ok(self.storage.block_numbers.get(&hash).cloned()) + Ok(self.storage.read().block_numbers.get(&hash).cloned()) } } @@ -69,15 +80,16 @@ impl HeaderProvider for ForkedProvider { fn header(&self, id: katana_primitives::block::BlockHashOrNumber) -> Result> { match id { katana_primitives::block::BlockHashOrNumber::Num(num) => { - Ok(self.storage.block_headers.get(&num).cloned()) + Ok(self.storage.read().block_headers.get(&num).cloned()) } katana_primitives::block::BlockHashOrNumber::Hash(hash) => { let header @ Some(_) = self .storage + .read() .block_numbers .get(&hash) - .and_then(|num| self.storage.block_headers.get(num).cloned()) + .and_then(|num| self.storage.read().block_headers.get(num).cloned()) else { return Ok(None); }; @@ -87,22 +99,48 @@ impl HeaderProvider for ForkedProvider { } } +impl BlockStatusProvider for ForkedProvider { + fn block_status(&self, id: BlockHashOrNumber) -> Result> { + let num = match id { + BlockHashOrNumber::Num(num) => num, + BlockHashOrNumber::Hash(hash) => { + match self.storage.read().block_numbers.get(&hash).copied() { + Some(num) => num, + None => return Ok(None), + } + } + }; + Ok(self.storage.read().block_statusses.get(&num).cloned()) + } +} + impl BlockProvider for ForkedProvider { fn block(&self, id: BlockHashOrNumber) -> Result> { let block_num = match id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; - let Some(header) = block_num.and_then(|num| self.storage.block_headers.get(&num).cloned()) + let Some(header) = + block_num.and_then(|num| self.storage.read().block_headers.get(&num).cloned()) else { return Ok(None); }; let body = self.transactions_by_block(id)?.unwrap_or_default(); - let status = self.storage.block_statusses.get(&header.number).cloned().expect("must have"); - Ok(Some(Block { header, body, status })) + Ok(Some(Block { header, body })) + } + + fn block_with_tx_hashes(&self, id: BlockHashOrNumber) -> Result> { + let Some(header) = self.header(id)? else { + return Ok(None); + }; + + let tx_range = self.block_body_indices(id)?.expect("should exist"); + let tx_hashes = self.transaction_hashes_in_range(tx_range.into())?; + + Ok(Some(katana_primitives::block::BlockWithTxHashes { header, body: tx_hashes })) } fn blocks_in_range(&self, range: RangeInclusive) -> Result> { @@ -114,66 +152,135 @@ impl BlockProvider for ForkedProvider { } Ok(blocks) } + + fn block_body_indices(&self, id: BlockHashOrNumber) -> Result> { + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), + }; + + let Some(indices) = + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) + else { + return Ok(None); + }; + + Ok(Some(indices)) + } } impl TransactionProvider for ForkedProvider { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { - Ok(self - .storage - .transaction_numbers - .get(&hash) - .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + let tx = self.storage.read().transaction_numbers.get(&hash).and_then(|num| { + let transaction = self.storage.read().transactions.get(*num as usize).cloned()?; + let hash = self.storage.read().transaction_hashes.get(num).copied()?; + Some(TxWithHash { hash, transaction }) + }); + Ok(tx) } - fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; - let count = *tx_count as usize; + let offset = tx_offset as usize; + let count = tx_count as usize; - Ok(Some(self.storage.transactions[offset..offset + count].to_vec())) + let txs = self + .storage + .read() + .transactions + .iter() + .enumerate() + .skip(offset) + .take(offset + count) + .map(|(n, tx)| { + let hash = + self.storage.read().transaction_hashes.get(&(n as u64)).cloned().unwrap(); + TxWithHash { hash, transaction: tx.clone() } + }) + .collect(); + + Ok(Some(txs)) } fn transaction_by_block_and_idx( &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; + let offset = tx_offset as usize; - if idx >= *tx_count { + if idx >= tx_count { return Ok(None); } - Ok(Some(self.storage.transactions[offset + idx as usize].clone())) + let id = offset + idx as usize; + + let tx = self.storage.read().transactions.get(id).cloned().and_then(|tx| { + let hash = self.storage.read().transaction_hashes.get(&(id as u64)).copied()?; + Some(TxWithHash { hash, transaction: tx }) + }); + + Ok(tx) + } + + fn transaction_count_by_block(&self, block_id: BlockHashOrNumber) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), + }; + + let Some(tx_count) = block_num + .and_then(|n| self.storage.read().block_body_indices.get(&n).map(|b| b.tx_count)) + else { + return Ok(None); + }; + + Ok(Some(tx_count)) + } + + fn transaction_block_num_and_hash( + &self, + hash: TxHash, + ) -> Result> { + let storage_read = self.storage.read(); + + let Some(number) = storage_read.transaction_numbers.get(&hash) else { return Ok(None) }; + let block_num = storage_read.transaction_block.get(number).expect("block num should exist"); + let block_hash = storage_read.block_hashes.get(block_num).expect("block hash should exist"); + + Ok(Some((*block_num, *block_hash))) } } impl TransactionsProviderExt for ForkedProvider { - fn transaction_hashes_by_range(&self, range: std::ops::Range) -> Result> { + fn transaction_hashes_in_range(&self, range: std::ops::Range) -> Result> { let mut hashes = Vec::new(); for num in range { - if let Some(hash) = self.storage.transaction_hashes.get(&num).cloned() { + if let Some(hash) = self.storage.read().transaction_hashes.get(&num).cloned() { hashes.push(hash); } } @@ -181,32 +288,45 @@ impl TransactionsProviderExt for ForkedProvider { } } +impl TransactionStatusProvider for ForkedProvider { + fn transaction_status(&self, hash: TxHash) -> Result> { + let status = self + .storage + .read() + .transaction_numbers + .get(&hash) + .and_then(|n| self.storage.read().transaction_status.get(n).copied()); + Ok(status) + } +} + impl ReceiptProvider for ForkedProvider { fn receipt_by_hash(&self, hash: TxHash) -> Result> { let receipt = self .storage + .read() .transaction_numbers .get(&hash) - .and_then(|num| self.storage.receipts.get(*num as usize).cloned()); + .and_then(|num| self.storage.read().receipts.get(*num as usize).cloned()); Ok(receipt) } fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; - let count = *tx_count as usize; + let offset = tx_offset as usize; + let count = tx_count as usize; - Ok(Some(self.storage.receipts[offset..offset + count].to_vec())) + Ok(Some(self.storage.read().receipts[offset..offset + count].to_vec())) } } @@ -217,14 +337,27 @@ impl ContractInfoProvider for ForkedProvider { } } +impl StateRootProvider for ForkedProvider { + fn state_root( + &self, + block_id: BlockHashOrNumber, + ) -> Result> { + let state_root = self.block_number_by_id(block_id)?.and_then(|num| { + self.storage.read().block_headers.get(&num).map(|header| header.state_root) + }); + Ok(state_root) + } +} + impl StateUpdateProvider for ForkedProvider { - fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; - let state_update = block_num.and_then(|num| self.storage.state_update.get(&num).cloned()); + let state_update = + block_num.and_then(|num| self.storage.read().state_update.get(&num).cloned()); Ok(state_update) } } @@ -253,3 +386,109 @@ impl StateFactoryProvider for ForkedProvider { Ok(provider) } } + +impl BlockWriter for ForkedProvider { + fn insert_block_with_states_and_receipts( + &self, + block: SealedBlockWithStatus, + states: StateUpdatesWithDeclaredClasses, + receipts: Vec, + ) -> Result<()> { + let mut storage = self.storage.write(); + + let block_hash = block.block.header.hash; + let block_number = block.block.header.header.number; + + let block_header = block.block.header.header; + let txs = block.block.body; + + // create block body indices + let tx_count = txs.len() as u64; + let tx_offset = self.storage.read().transactions.len() as u64; + let block_body_indices = StoredBlockBodyIndices { tx_offset, tx_count }; + + let (txs_id, txs): (Vec<(TxNumber, TxHash)>, Vec) = txs + .into_iter() + .enumerate() + .map(|(num, tx)| ((num as u64 + tx_offset, tx.hash), tx.transaction)) + .unzip(); + + let txs_num = txs_id.clone().into_iter().map(|(num, hash)| (hash, num)); + let txs_block = txs_id.clone().into_iter().map(|(num, _)| (num, block_number)); + + storage.latest_block_hash = block_hash; + storage.latest_block_number = block_number; + + storage.block_numbers.insert(block_hash, block_number); + storage.block_hashes.insert(block_number, block_hash); + storage.block_headers.insert(block_number, block_header); + storage.block_statusses.insert(block_number, block.status); + storage.block_body_indices.insert(block_number, block_body_indices); + + storage.transactions.extend(txs); + storage.transaction_hashes.extend(txs_id); + storage.transaction_numbers.extend(txs_num); + storage.transaction_block.extend(txs_block); + storage.receipts.extend(receipts); + + storage.state_update.insert(block_number, states.state_updates.clone()); + + self.state.insert_updates(states); + + let snapshot = self.state.create_snapshot(); + self.historical_states.write().insert(block_number, Box::new(snapshot)); + + Ok(()) + } +} + +impl ContractClassWriter for ForkedProvider { + fn set_class(&self, hash: ClassHash, class: CompiledContractClass) -> Result<()> { + self.state.shared_contract_classes.compiled_classes.write().insert(hash, class); + Ok(()) + } + + fn set_sierra_class(&self, hash: ClassHash, sierra: SierraClass) -> Result<()> { + self.state.shared_contract_classes.sierra_classes.write().insert(hash, sierra); + Ok(()) + } + + fn set_compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + compiled_hash: CompiledClassHash, + ) -> Result<()> { + self.state.compiled_class_hashes.write().insert(hash, compiled_hash); + Ok(()) + } +} + +impl StateWriter for ForkedProvider { + fn set_storage( + &self, + address: ContractAddress, + storage_key: katana_primitives::contract::StorageKey, + storage_value: katana_primitives::contract::StorageValue, + ) -> Result<()> { + self.state.storage.write().entry(address).or_default().insert(storage_key, storage_value); + Ok(()) + } + + fn set_class_hash_of_contract( + &self, + address: ContractAddress, + class_hash: ClassHash, + ) -> Result<()> { + self.state.contract_state.write().entry(address).or_default().class_hash = class_hash; + Ok(()) + } + + fn set_nonce( + &self, + address: ContractAddress, + nonce: katana_primitives::contract::Nonce, + ) -> Result<()> { + self.state.contract_state.write().entry(address).or_default().nonce = nonce; + Ok(()) + } +} diff --git a/crates/katana/storage/provider/src/providers/fork/state.rs b/crates/katana/storage/provider/src/providers/fork/state.rs index ced68bee25..816fd8add8 100644 --- a/crates/katana/storage/provider/src/providers/fork/state.rs +++ b/crates/katana/storage/provider/src/providers/fork/state.rs @@ -53,8 +53,10 @@ impl StateProvider for ForkedStateDb { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - if let value @ Some(_) = self.storage.read().get(&(address, storage_key)) { - return Ok(value.cloned()); + if let value @ Some(_) = + self.storage.read().get(&address).and_then(|s| s.get(&storage_key)).copied() + { + return Ok(value); } StateProvider::storage(&self.db, address, storage_key) } @@ -151,7 +153,9 @@ impl StateProvider for ForkedSnapshot { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - if let value @ Some(_) = self.inner.storage.get(&(address, storage_key)).cloned() { + if let value @ Some(_) = + self.inner.storage.get(&address).and_then(|s| s.get(&storage_key)).copied() + { return Ok(value); } StateProvider::storage(&self.inner.db, address, storage_key) diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs index 4889c0e64a..0544012692 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/cache.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -2,15 +2,17 @@ use std::collections::HashMap; use std::sync::Arc; use katana_db::models::block::StoredBlockBodyIndices; -use katana_primitives::block::{BlockHash, BlockNumber, BlockStatus, Header, StateUpdate}; +use katana_primitives::block::{BlockHash, BlockNumber, FinalityStatus, Header}; use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, SierraClass, StorageKey, StorageValue, }; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber}; use parking_lot::RwLock; -type ContractStorageMap = HashMap<(ContractAddress, StorageKey), StorageValue>; +type ContractStorageMap = HashMap>; type ContractStateMap = HashMap; type SierraClassesMap = HashMap; @@ -38,20 +40,52 @@ pub struct CacheStateDb { pub(crate) compiled_class_hashes: RwLock, } +impl CacheStateDb { + /// Applies the given state updates to the cache. + pub fn insert_updates(&self, updates: StateUpdatesWithDeclaredClasses) { + let mut storage = self.storage.write(); + let mut contract_state = self.contract_state.write(); + let mut compiled_class_hashes = self.compiled_class_hashes.write(); + let mut sierra_classes = self.shared_contract_classes.sierra_classes.write(); + let mut compiled_classes = self.shared_contract_classes.compiled_classes.write(); + + for (contract_address, nonce) in updates.state_updates.nonce_updates { + let info = contract_state.entry(contract_address).or_default(); + info.nonce = nonce; + } + + for (contract_address, class_hash) in updates.state_updates.contract_updates { + let info = contract_state.entry(contract_address).or_default(); + info.class_hash = class_hash; + } + + for (contract_address, storage_changes) in updates.state_updates.storage_updates { + let contract_storage = storage.entry(contract_address).or_default(); + contract_storage.extend(storage_changes); + } + + compiled_class_hashes.extend(updates.state_updates.declared_classes); + sierra_classes.extend(updates.declared_sierra_classes); + compiled_classes.extend(updates.declared_compiled_classes); + } +} + pub struct CacheDb { pub(crate) db: Db, pub(crate) block_headers: HashMap, pub(crate) block_hashes: HashMap, pub(crate) block_numbers: HashMap, - pub(crate) block_statusses: HashMap, + pub(crate) block_statusses: HashMap, pub(crate) block_body_indices: HashMap, pub(crate) latest_block_hash: BlockHash, pub(crate) latest_block_number: BlockNumber, - pub(crate) state_update: HashMap, + pub(crate) state_update: HashMap, pub(crate) receipts: Vec, pub(crate) transactions: Vec, pub(crate) transaction_hashes: HashMap, pub(crate) transaction_numbers: HashMap, + pub(crate) transaction_block: HashMap, + pub(crate) transaction_status: HashMap, } impl CacheStateDb { @@ -77,6 +111,8 @@ impl CacheDb { block_headers: HashMap::new(), block_numbers: HashMap::new(), block_statusses: HashMap::new(), + transaction_block: HashMap::new(), + transaction_status: HashMap::new(), transaction_hashes: HashMap::new(), block_body_indices: HashMap::new(), transaction_numbers: HashMap::new(), @@ -101,7 +137,7 @@ impl std::ops::Deref for CacheDb { } impl CacheStateDb { - pub fn create_snapshot_without_classes(&self) -> CacheSnapshotWithoutClasses { + pub(crate) fn create_snapshot_without_classes(&self) -> CacheSnapshotWithoutClasses { CacheSnapshotWithoutClasses { db: self.db.clone(), storage: self.storage.read().clone(), diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 8c0791ddec..477f8ba149 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -7,52 +7,69 @@ use std::sync::Arc; use anyhow::Result; use katana_db::models::block::StoredBlockBodyIndices; use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, BlockNumber, Header, StateUpdate, + Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, Header, + SealedBlockWithStatus, }; -use katana_primitives::contract::{ContractAddress, GenericContractInfo}; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + SierraClass, +}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; use parking_lot::RwLock; use self::cache::CacheDb; use self::state::{HistoricalStates, InMemoryStateDb, LatestStateProvider}; -use crate::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider, HeaderProvider}; -use crate::traits::contract::ContractInfoProvider; -use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::block::{ + BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, + HeaderProvider, +}; +use crate::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use crate::traits::state::{StateFactoryProvider, StateProvider, StateRootProvider, StateWriter}; use crate::traits::state_update::StateUpdateProvider; -use crate::traits::transaction::{ReceiptProvider, TransactionProvider, TransactionsProviderExt}; +use crate::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionsProviderExt, +}; pub struct InMemoryProvider { - storage: CacheDb<()>, + storage: RwLock>, state: Arc, historical_states: RwLock, } impl InMemoryProvider { pub fn new() -> Self { - let storage = CacheDb::new(()); + let storage = RwLock::new(CacheDb::new(())); let state = Arc::new(InMemoryStateDb::new(())); let historical_states = RwLock::new(HistoricalStates::default()); Self { storage, state, historical_states } } } +impl Default for InMemoryProvider { + fn default() -> Self { + Self::new() + } +} + impl BlockHashProvider for InMemoryProvider { fn latest_hash(&self) -> Result { - Ok(self.storage.latest_block_hash) + Ok(self.storage.read().latest_block_hash) } fn block_hash_by_num(&self, num: BlockNumber) -> Result> { - Ok(self.storage.block_hashes.get(&num).cloned()) + Ok(self.storage.read().block_hashes.get(&num).cloned()) } } impl BlockNumberProvider for InMemoryProvider { fn latest_number(&self) -> Result { - Ok(self.storage.latest_block_number) + Ok(self.storage.read().latest_block_number) } fn block_number_by_hash(&self, hash: BlockHash) -> Result> { - Ok(self.storage.block_numbers.get(&hash).cloned()) + Ok(self.storage.read().block_numbers.get(&hash).cloned()) } } @@ -60,15 +77,16 @@ impl HeaderProvider for InMemoryProvider { fn header(&self, id: katana_primitives::block::BlockHashOrNumber) -> Result> { match id { katana_primitives::block::BlockHashOrNumber::Num(num) => { - Ok(self.storage.block_headers.get(&num).cloned()) + Ok(self.storage.read().block_headers.get(&num).cloned()) } katana_primitives::block::BlockHashOrNumber::Hash(hash) => { let header @ Some(_) = self .storage + .read() .block_numbers .get(&hash) - .and_then(|num| self.storage.block_headers.get(num).cloned()) + .and_then(|num| self.storage.read().block_headers.get(num).cloned()) else { return Ok(None); }; @@ -78,22 +96,48 @@ impl HeaderProvider for InMemoryProvider { } } +impl BlockStatusProvider for InMemoryProvider { + fn block_status(&self, id: BlockHashOrNumber) -> Result> { + let num = match id { + BlockHashOrNumber::Num(num) => num, + BlockHashOrNumber::Hash(hash) => { + match self.storage.read().block_numbers.get(&hash).copied() { + Some(num) => num, + None => return Ok(None), + } + } + }; + Ok(self.storage.read().block_statusses.get(&num).cloned()) + } +} + impl BlockProvider for InMemoryProvider { fn block(&self, id: BlockHashOrNumber) -> Result> { let block_num = match id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; - let Some(header) = block_num.and_then(|num| self.storage.block_headers.get(&num).cloned()) + let Some(header) = + block_num.and_then(|num| self.storage.read().block_headers.get(&num).cloned()) else { return Ok(None); }; - let body = self.transactions_by_block(id)?.unwrap_or_default(); - let status = self.storage.block_statusses.get(&header.number).cloned().expect("must have"); + let body = TransactionProvider::transactions_by_block(&self, id)?.unwrap_or_default(); + + Ok(Some(Block { header, body })) + } + + fn block_with_tx_hashes(&self, id: BlockHashOrNumber) -> Result> { + let Some(header) = self.header(id)? else { + return Ok(None); + }; + + let tx_range = self.block_body_indices(id)?.expect("should exist"); + let tx_hashes = self.transaction_hashes_in_range(tx_range.into())?; - Ok(Some(Block { header, body, status })) + Ok(Some(katana_primitives::block::BlockWithTxHashes { header, body: tx_hashes })) } fn blocks_in_range(&self, range: RangeInclusive) -> Result> { @@ -105,66 +149,136 @@ impl BlockProvider for InMemoryProvider { } Ok(blocks) } + + fn block_body_indices(&self, id: BlockHashOrNumber) -> Result> { + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), + }; + + let Some(indices) = + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) + else { + return Ok(None); + }; + + Ok(Some(indices)) + } } impl TransactionProvider for InMemoryProvider { - fn transaction_by_hash(&self, hash: TxHash) -> Result> { - Ok(self - .storage - .transaction_numbers - .get(&hash) - .and_then(|num| self.storage.transactions.get(*num as usize).cloned())) + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + let tx = self.storage.read().transaction_numbers.get(&hash).and_then(|num| { + let transaction = self.storage.read().transactions.get(*num as usize)?.clone(); + let hash = *self.storage.read().transaction_hashes.get(num)?; + Some(TxWithHash { hash, transaction }) + }); + Ok(tx) } - fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; - let count = *tx_count as usize; + let offset = tx_offset as usize; + let count = tx_count as usize; - Ok(Some(self.storage.transactions[offset..offset + count].to_vec())) + let txs = self + .storage + .read() + .transactions + .iter() + .enumerate() + .skip(offset) + .take(offset + count) + .map(|(n, tx)| { + let hash = + self.storage.read().transaction_hashes.get(&(n as u64)).cloned().unwrap(); + TxWithHash { hash, transaction: tx.clone() } + }) + .collect(); + + Ok(Some(txs)) } fn transaction_by_block_and_idx( &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result> { + ) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; + let offset = tx_offset as usize; - if idx >= *tx_count { + if idx >= tx_count { return Ok(None); } - Ok(Some(self.storage.transactions[offset + idx as usize].clone())) + let id = offset + idx as usize; + + let tx = self.storage.read().transactions.get(id).cloned().and_then(|tx| { + let hash = *self.storage.read().transaction_hashes.get(&(id as u64))?; + Some(TxWithHash { hash, transaction: tx }) + }); + + Ok(tx) + } + + fn transaction_count_by_block(&self, block_id: BlockHashOrNumber) -> Result> { + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), + }; + + let Some(tx_count) = block_num + .and_then(|n| self.storage.read().block_body_indices.get(&n).map(|b| b.tx_count)) + else { + return Ok(None); + }; + + Ok(Some(tx_count)) + } + + fn transaction_block_num_and_hash( + &self, + hash: TxHash, + ) -> Result> { + let storage_read = self.storage.read(); + + let Some(number) = storage_read.transaction_numbers.get(&hash) else { return Ok(None) }; + println!("number: {:?}", number); + let block_num = storage_read.transaction_block.get(number).expect("block num should exist"); + let block_hash = storage_read.block_hashes.get(block_num).expect("block hash should exist"); + + Ok(Some((*block_num, *block_hash))) } } impl TransactionsProviderExt for InMemoryProvider { - fn transaction_hashes_by_range(&self, range: std::ops::Range) -> Result> { + fn transaction_hashes_in_range(&self, range: std::ops::Range) -> Result> { let mut hashes = Vec::new(); for num in range { - if let Some(hash) = self.storage.transaction_hashes.get(&num).cloned() { + if let Some(hash) = self.storage.read().transaction_hashes.get(&num).cloned() { hashes.push(hash); } } @@ -172,32 +286,45 @@ impl TransactionsProviderExt for InMemoryProvider { } } +impl TransactionStatusProvider for InMemoryProvider { + fn transaction_status(&self, hash: TxHash) -> Result> { + let status = self + .storage + .read() + .transaction_numbers + .get(&hash) + .and_then(|n| self.storage.read().transaction_status.get(n).copied()); + Ok(status) + } +} + impl ReceiptProvider for InMemoryProvider { fn receipt_by_hash(&self, hash: TxHash) -> Result> { let receipt = self .storage + .read() .transaction_numbers .get(&hash) - .and_then(|num| self.storage.receipts.get(*num as usize).cloned()); + .and_then(|num| self.storage.read().receipts.get(*num as usize).cloned()); Ok(receipt) } fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; let Some(StoredBlockBodyIndices { tx_offset, tx_count }) = - block_num.and_then(|num| self.storage.block_body_indices.get(&num)) + block_num.and_then(|num| self.storage.read().block_body_indices.get(&num).cloned()) else { return Ok(None); }; - let offset = *tx_offset as usize; - let count = *tx_count as usize; + let offset = tx_offset as usize; + let count = tx_count as usize; - Ok(Some(self.storage.receipts[offset..offset + count].to_vec())) + Ok(Some(self.storage.read().receipts[offset..offset + count].to_vec())) } } @@ -209,13 +336,14 @@ impl ContractInfoProvider for InMemoryProvider { } impl StateUpdateProvider for InMemoryProvider { - fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { let block_num = match block_id { BlockHashOrNumber::Num(num) => Some(num), - BlockHashOrNumber::Hash(hash) => self.storage.block_numbers.get(&hash).cloned(), + BlockHashOrNumber::Hash(hash) => self.storage.read().block_numbers.get(&hash).cloned(), }; - let state_update = block_num.and_then(|num| self.storage.state_update.get(&num).cloned()); + let state_update = + block_num.and_then(|num| self.storage.read().state_update.get(&num).cloned()); Ok(state_update) } } @@ -245,15 +373,137 @@ impl StateFactoryProvider for InMemoryProvider { } } +impl StateRootProvider for InMemoryProvider { + fn state_root( + &self, + block_id: BlockHashOrNumber, + ) -> Result> { + let state_root = self.block_number_by_id(block_id)?.and_then(|num| { + self.storage.read().block_headers.get(&num).map(|header| header.state_root) + }); + Ok(state_root) + } +} + +impl BlockWriter for InMemoryProvider { + fn insert_block_with_states_and_receipts( + &self, + block: SealedBlockWithStatus, + states: StateUpdatesWithDeclaredClasses, + receipts: Vec, + ) -> Result<()> { + let mut storage = self.storage.write(); + + let block_hash = block.block.header.hash; + let block_number = block.block.header.header.number; + + let block_header = block.block.header.header; + let txs = block.block.body; + + // create block body indices + let tx_count = txs.len() as u64; + let tx_offset = storage.transactions.len() as u64; + let block_body_indices = StoredBlockBodyIndices { tx_offset, tx_count }; + + let (txs_id, txs): (Vec<(TxNumber, TxHash)>, Vec) = txs + .into_iter() + .enumerate() + .map(|(num, tx)| ((num as u64 + tx_offset, tx.hash), tx.transaction)) + .unzip(); + + let txs_num = txs_id.clone().into_iter().map(|(num, hash)| (hash, num)); + let txs_block = txs_id.clone().into_iter().map(|(num, _)| (num, block_number)); + let txs_status = txs_id.clone().into_iter().map(|(num, _)| (num, block.status)); + + storage.latest_block_hash = block_hash; + storage.latest_block_number = block_number; + + storage.block_numbers.insert(block_hash, block_number); + storage.block_hashes.insert(block_number, block_hash); + storage.block_headers.insert(block_number, block_header); + storage.block_statusses.insert(block_number, block.status); + storage.block_body_indices.insert(block_number, block_body_indices); + + storage.transactions.extend(txs); + storage.transaction_hashes.extend(txs_id); + storage.transaction_numbers.extend(txs_num); + storage.transaction_block.extend(txs_block); + storage.transaction_status.extend(txs_status); + storage.receipts.extend(receipts); + + storage.state_update.insert(block_number, states.state_updates.clone()); + + self.state.insert_updates(states); + + let snapshot = self.state.create_snapshot(); + self.historical_states.write().insert(block_number, Box::new(snapshot)); + + Ok(()) + } +} + +impl ContractClassWriter for InMemoryProvider { + fn set_class(&self, hash: ClassHash, class: CompiledContractClass) -> Result<()> { + self.state.shared_contract_classes.compiled_classes.write().insert(hash, class); + Ok(()) + } + + fn set_sierra_class(&self, hash: ClassHash, sierra: SierraClass) -> Result<()> { + self.state.shared_contract_classes.sierra_classes.write().insert(hash, sierra); + Ok(()) + } + + fn set_compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + compiled_hash: CompiledClassHash, + ) -> Result<()> { + self.state.compiled_class_hashes.write().insert(hash, compiled_hash); + Ok(()) + } +} + +impl StateWriter for InMemoryProvider { + fn set_storage( + &self, + address: ContractAddress, + storage_key: katana_primitives::contract::StorageKey, + storage_value: katana_primitives::contract::StorageValue, + ) -> Result<()> { + self.state.storage.write().entry(address).or_default().insert(storage_key, storage_value); + Ok(()) + } + + fn set_class_hash_of_contract( + &self, + address: ContractAddress, + class_hash: ClassHash, + ) -> Result<()> { + self.state.contract_state.write().entry(address).or_default().class_hash = class_hash; + Ok(()) + } + + fn set_nonce( + &self, + address: ContractAddress, + nonce: katana_primitives::contract::Nonce, + ) -> Result<()> { + self.state.contract_state.write().entry(address).or_default().nonce = nonce; + Ok(()) + } +} + #[cfg(test)] mod tests { use std::sync::Arc; + use parking_lot::RwLock; + use super::cache::{CacheDb, CacheStateDb}; use super::InMemoryProvider; pub(super) fn create_mock_provider() -> InMemoryProvider { - let storage = CacheDb::new(()); + let storage = RwLock::new(CacheDb::new(())); let state = Arc::new(CacheStateDb::new(())); let historical_states = Default::default(); InMemoryProvider { storage, state, historical_states } diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index 1dc3e1b195..6d90feae5b 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -1,7 +1,6 @@ use std::collections::{HashMap, VecDeque}; use std::sync::Arc; -use anyhow::Result; use katana_primitives::block::BlockNumber; use katana_primitives::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, @@ -11,6 +10,7 @@ use katana_primitives::contract::{ use super::cache::{CacheSnapshotWithoutClasses, CacheStateDb, SharedContractClasses}; use crate::traits::contract::{ContractClassProvider, ContractInfoProvider}; use crate::traits::state::StateProvider; +use crate::Result; pub struct StateSnapshot { pub(crate) classes: Arc, @@ -133,7 +133,7 @@ impl StateProvider for InMemorySnapshot { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - let value = self.inner.storage.get(&(address, storage_key)).cloned(); + let value = self.inner.storage.get(&address).and_then(|s| s.get(&storage_key)).copied(); Ok(value) } @@ -183,7 +183,7 @@ impl StateProvider for LatestStateProvider { address: ContractAddress, storage_key: StorageKey, ) -> Result> { - let value = self.0.storage.read().get(&(address, storage_key)).cloned(); + let value = self.0.storage.read().get(&address).and_then(|s| s.get(&storage_key)).copied(); Ok(value) } @@ -247,8 +247,8 @@ mod tests { fn create_mock_state() -> InMemoryStateDb { let storage = HashMap::from([ - ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_1), - ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_1), + (ADDR_1, HashMap::from([(STORAGE_KEY, ADDR_1_STORAGE_VALUE_AT_1)])), + (ADDR_2, HashMap::from([(STORAGE_KEY, ADDR_2_STORAGE_VALUE_AT_1)])), ]); let contract_state = HashMap::from([ @@ -308,8 +308,8 @@ mod tests { let snapshot_1 = state.create_snapshot(); state.storage.write().extend([ - ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_2), - ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_2), + (ADDR_1, HashMap::from([(STORAGE_KEY, ADDR_1_STORAGE_VALUE_AT_2)])), + (ADDR_2, HashMap::from([(STORAGE_KEY, ADDR_2_STORAGE_VALUE_AT_2)])), ]); state.contract_state.write().extend([ @@ -333,8 +333,8 @@ mod tests { let snapshot_2 = state.create_snapshot(); state.storage.write().extend([ - ((ADDR_1, STORAGE_KEY), ADDR_1_STORAGE_VALUE_AT_3), - ((ADDR_2, STORAGE_KEY), ADDR_2_STORAGE_VALUE_AT_3), + (ADDR_1, HashMap::from([(STORAGE_KEY, ADDR_1_STORAGE_VALUE_AT_3)])), + (ADDR_2, HashMap::from([(STORAGE_KEY, ADDR_2_STORAGE_VALUE_AT_3)])), ]); state.contract_state.write().entry(ADDR_1).and_modify(|e| e.nonce = ADDR_1_NONCE_AT_3); diff --git a/crates/katana/storage/provider/src/traits/block.rs b/crates/katana/storage/provider/src/traits/block.rs index 8c7acacf38..8b7dfdb992 100644 --- a/crates/katana/storage/provider/src/traits/block.rs +++ b/crates/katana/storage/provider/src/traits/block.rs @@ -1,9 +1,43 @@ use std::ops::RangeInclusive; use anyhow::Result; -use katana_primitives::block::{Block, BlockHash, BlockHashOrNumber, BlockNumber, Header}; +use katana_db::models::block::StoredBlockBodyIndices; +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber, BlockTag, BlockWithTxHashes, + FinalityStatus, Header, SealedBlockWithStatus, +}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::StateUpdatesWithDeclaredClasses; -use super::transaction::TransactionProvider; +use super::transaction::{TransactionProvider, TransactionsProviderExt}; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockIdReader: BlockNumberProvider + Send + Sync { + /// Converts the block tag into its block number. + fn convert_block_id(&self, id: BlockIdOrTag) -> Result> { + match id { + BlockIdOrTag::Number(number) => Ok(Some(number)), + BlockIdOrTag::Hash(hash) => BlockNumberProvider::block_number_by_hash(self, hash), + + BlockIdOrTag::Tag(BlockTag::Latest) => { + BlockNumberProvider::latest_number(&self).map(Some) + } + + BlockIdOrTag::Tag(BlockTag::Pending) => { + if let Some((num, _)) = Self::pending_block_id(self)? { + Ok(Some(num)) + } else { + Ok(None) + } + } + } + } + + /// Retrieves the pending block number and hash. + fn pending_block_id(&self) -> Result> { + Ok(None) // Returns `None` for now + } +} #[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockHashProvider: Send + Sync { @@ -14,6 +48,14 @@ pub trait BlockHashProvider: Send + Sync { /// Retrieves the block hash given its id. fn block_hash_by_num(&self, num: BlockNumber) -> Result>; + + /// Retrieves the block hash given its id. + fn block_hash_by_id(&self, id: BlockHashOrNumber) -> Result> { + match id { + BlockHashOrNumber::Hash(hash) => Ok(Some(hash)), + BlockHashOrNumber::Num(number) => self.block_hash_by_num(number), + } + } } #[auto_impl::auto_impl(&, Box, Arc)] @@ -25,6 +67,14 @@ pub trait BlockNumberProvider: Send + Sync { /// Retrieves the block number given its id. fn block_number_by_hash(&self, hash: BlockHash) -> Result>; + + /// Retrieves the block number given its id. + fn block_number_by_id(&self, id: BlockHashOrNumber) -> Result> { + match id { + BlockHashOrNumber::Num(number) => Ok(Some(number)), + BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash), + } + } } #[auto_impl::auto_impl(&, Box, Arc)] @@ -41,16 +91,35 @@ pub trait HeaderProvider: Send + Sync { } } +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockStatusProvider: Send + Sync { + /// Retrieves the finality status of a block. + fn block_status(&self, id: BlockHashOrNumber) -> Result>; +} + #[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockProvider: - BlockHashProvider + BlockNumberProvider + HeaderProvider + TransactionProvider + Send + Sync + BlockHashProvider + + BlockNumberProvider + + HeaderProvider + + BlockStatusProvider + + TransactionProvider + + TransactionsProviderExt + + Send + + Sync { /// Returns a block by its id. fn block(&self, id: BlockHashOrNumber) -> Result>; + /// Returns a block with only the transaction hashes. + fn block_with_tx_hashes(&self, id: BlockHashOrNumber) -> Result>; + /// Returns all available blocks in the given range. fn blocks_in_range(&self, range: RangeInclusive) -> Result>; + /// Returns the block body indices of a block. + fn block_body_indices(&self, id: BlockHashOrNumber) -> Result>; + /// Returns the block based on its hash. fn block_by_hash(&self, hash: BlockHash) -> Result> { self.block(hash.into()) @@ -62,7 +131,13 @@ pub trait BlockProvider: } } -pub trait BlockExecutionWriter: Send + Sync { - /// Store an executed block along with its output to the storage. - fn store_block(&mut self, block: Block) -> Result<()>; +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockWriter: Send + Sync { + /// Store an executed block along with its execution output to the storage. + fn insert_block_with_states_and_receipts( + &self, + block: SealedBlockWithStatus, + states: StateUpdatesWithDeclaredClasses, + receipts: Vec, + ) -> Result<()>; } diff --git a/crates/katana/storage/provider/src/traits/contract.rs b/crates/katana/storage/provider/src/traits/contract.rs index c3f5694936..4645913ccb 100644 --- a/crates/katana/storage/provider/src/traits/contract.rs +++ b/crates/katana/storage/provider/src/traits/contract.rs @@ -26,9 +26,9 @@ pub trait ContractClassProvider: Send + Sync { fn sierra_class(&self, hash: ClassHash) -> Result>; } -// TEMP: added mainly for compatibility reason following the path of least resistance. +// TEMP: added mainly for compatibility reason. might be removed in the future. #[auto_impl::auto_impl(&, Box, Arc)] -pub trait ContractClassWriter: ContractClassProvider + Send + Sync { +pub trait ContractClassWriter: Send + Sync { /// Returns the compiled class hash for the given class hash. fn set_compiled_class_hash_of_class_hash( &self, diff --git a/crates/katana/storage/provider/src/traits/env.rs b/crates/katana/storage/provider/src/traits/env.rs new file mode 100644 index 0000000000..73cc26cca9 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/env.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::env::BlockEnv; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockEnvProvider: Send + Sync { + fn env_at(&self, block_id: BlockHashOrNumber) -> Result; +} diff --git a/crates/katana/storage/provider/src/traits/mod.rs b/crates/katana/storage/provider/src/traits/mod.rs index 2598632fb1..762de20465 100644 --- a/crates/katana/storage/provider/src/traits/mod.rs +++ b/crates/katana/storage/provider/src/traits/mod.rs @@ -1,5 +1,6 @@ pub mod block; pub mod contract; +pub mod env; pub mod state; pub mod state_update; pub mod transaction; diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index d6e726a3a1..d201e33c59 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -1,11 +1,18 @@ use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; use katana_primitives::contract::{ClassHash, ContractAddress, Nonce, StorageKey, StorageValue}; +use katana_primitives::FieldElement; -use super::contract::{ContractClassProvider, ContractClassWriter, ContractInfoProvider}; +use super::contract::ContractClassProvider; #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateProvider: ContractInfoProvider + ContractClassProvider + Send + Sync { +pub trait StateRootProvider: Send + Sync { + /// Retrieves the state root of a block. + fn state_root(&self, block_id: BlockHashOrNumber) -> Result>; +} + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateProvider: ContractClassProvider + Send + Sync { /// Returns the nonce of a contract. fn nonce(&self, address: ContractAddress) -> Result>; @@ -20,10 +27,9 @@ pub trait StateProvider: ContractInfoProvider + ContractClassProvider + Send + S fn class_hash_of_contract(&self, address: ContractAddress) -> Result>; } -/// A state factory provider is a provider which can create state providers for -/// states at a particular block. +/// A type which can create [`StateProvider`] for states at a particular block. #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateFactoryProvider { +pub trait StateFactoryProvider: Send + Sync { /// Returns a state provider for retrieving the latest state. fn latest(&self) -> Result>; @@ -31,9 +37,9 @@ pub trait StateFactoryProvider { fn historical(&self, block_id: BlockHashOrNumber) -> Result>>; } -// TEMP: added mainly for compatibility reason following the path of least resistance. +// TEMP: added mainly for compatibility reason. it might be removed in the future. #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateWriter: StateProvider + ContractClassWriter + Send + Sync { +pub trait StateWriter: Send + Sync { /// Sets the nonce of a contract. fn set_nonce(&self, address: ContractAddress, nonce: Nonce) -> Result<()>; diff --git a/crates/katana/storage/provider/src/traits/state_update.rs b/crates/katana/storage/provider/src/traits/state_update.rs index 3062e18607..ddbac3e60f 100644 --- a/crates/katana/storage/provider/src/traits/state_update.rs +++ b/crates/katana/storage/provider/src/traits/state_update.rs @@ -1,8 +1,9 @@ use anyhow::Result; -use katana_primitives::block::{BlockHashOrNumber, StateUpdate}; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::state::StateUpdates; #[auto_impl::auto_impl(&, Box, Arc)] pub trait StateUpdateProvider: Send + Sync { - /// Returns the state update for the given block. - fn state_update(&self, block_id: BlockHashOrNumber) -> Result>; + /// Returns the state update at the given block. + fn state_update(&self, block_id: BlockHashOrNumber) -> Result>; } diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index 77eb6f0a31..8023d86568 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -1,29 +1,46 @@ use std::ops::Range; use anyhow::Result; -use katana_primitives::block::BlockHashOrNumber; -use katana_primitives::transaction::{Receipt, Tx, TxHash, TxNumber}; +use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockNumber, FinalityStatus}; +use katana_primitives::receipt::Receipt; +use katana_primitives::transaction::{TxHash, TxNumber, TxWithHash}; #[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionProvider: Send + Sync { /// Returns a transaction given its hash. - fn transaction_by_hash(&self, hash: TxHash) -> Result>; + fn transaction_by_hash(&self, hash: TxHash) -> Result>; /// Returns all the transactions for a given block. - fn transactions_by_block(&self, block_id: BlockHashOrNumber) -> Result>>; + fn transactions_by_block(&self, block_id: BlockHashOrNumber) + -> Result>>; /// Returns the transaction at the given block and its exact index in the block. fn transaction_by_block_and_idx( &self, block_id: BlockHashOrNumber, idx: u64, - ) -> Result>; + ) -> Result>; + + /// Returns the total number of transactions in a block. + fn transaction_count_by_block(&self, block_id: BlockHashOrNumber) -> Result>; + + /// Returns the block number and hash of a transaction. + fn transaction_block_num_and_hash( + &self, + hash: TxHash, + ) -> Result>; } #[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionsProviderExt: TransactionProvider + Send + Sync { /// Retrieves the tx hashes for the given range of tx numbers. - fn transaction_hashes_by_range(&self, range: Range) -> Result>; + fn transaction_hashes_in_range(&self, range: Range) -> Result>; +} + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait TransactionStatusProvider: Send + Sync { + /// Retrieves the finality status of a transaction. + fn transaction_status(&self, hash: TxHash) -> Result>; } #[auto_impl::auto_impl(&, Box, Arc)] From 3f2fda0f8ab5aa1c536f7d34e2e14e691dfbfa0b Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:15:25 +0800 Subject: [PATCH 094/192] refactor(katana-executor): update executor implementations --- crates/katana/executor/Cargo.toml | 3 +- crates/katana/executor/src/blockifier/mod.rs | 134 ++++++----- .../katana/executor/src/blockifier/outcome.rs | 79 +++---- .../katana/executor/src/blockifier/state.rs | 172 +++++++------- .../executor/src/blockifier/transactions.rs | 112 +++++++++ .../katana/executor/src/blockifier/utils.rs | 223 +++++++++++++++--- 6 files changed, 504 insertions(+), 219 deletions(-) create mode 100644 crates/katana/executor/src/blockifier/transactions.rs diff --git a/crates/katana/executor/Cargo.toml b/crates/katana/executor/Cargo.toml index f0df3ad3d6..10240980a7 100644 --- a/crates/katana/executor/Cargo.toml +++ b/crates/katana/executor/Cargo.toml @@ -1,5 +1,5 @@ [package] -description = "Katana execution engine" +description = "Katana execution engine. This crate provides implementations for executing transactions." edition.workspace = true name = "katana-executor" version.workspace = true @@ -10,6 +10,7 @@ version.workspace = true katana-primitives = { path = "../primitives" } katana-provider = { path = "../storage/provider" } +anyhow.workspace = true convert_case.workspace = true parking_lot.workspace = true starknet.workspace = true diff --git a/crates/katana/executor/src/blockifier/mod.rs b/crates/katana/executor/src/blockifier/mod.rs index 35a7d7951d..0dc61e0aaf 100644 --- a/crates/katana/executor/src/blockifier/mod.rs +++ b/crates/katana/executor/src/blockifier/mod.rs @@ -1,5 +1,6 @@ pub mod outcome; pub mod state; +pub mod transactions; pub mod utils; use std::sync::Arc; @@ -8,14 +9,17 @@ use blockifier::block_context::BlockContext; use blockifier::state::state_api::StateReader; use blockifier::transaction::errors::TransactionExecutionError; use blockifier::transaction::objects::TransactionExecutionInfo; -use blockifier::transaction::transaction_execution::Transaction as BlockifierExecuteTx; +use blockifier::transaction::transaction_execution::Transaction; use blockifier::transaction::transactions::ExecutableTransaction; -use katana_primitives::transaction::{DeclareTxWithClasses, ExecutionTx}; +use katana_primitives::transaction::{ + DeclareTxWithClass, ExecutableTx, ExecutableTxWithHash, TxWithHash, +}; use parking_lot::RwLock; use tracing::{trace, warn}; -use self::outcome::ExecutedTx; +use self::outcome::TxReceiptWithExecInfo; use self::state::{CachedStateWrapper, StateRefDb}; +use self::transactions::BlockifierTx; use self::utils::events_from_exec_info; use crate::blockifier::utils::{ pretty_print_resources, trace_events, warn_message_transaction_error_exec_error, @@ -29,15 +33,15 @@ type TxExecutionResult = Result { +pub struct TransactionExecutor<'a, S: StateReader, T> { /// A flag to enable/disable fee charging. charge_fee: bool, /// The block context the transactions will be executed on. block_context: &'a BlockContext, /// The transactions to be executed (in the exact order they are in the iterator). - transactions: std::vec::IntoIter, + transactions: T, /// The state the transactions will be executed on. - state: &'a mut CachedStateWrapper, + state: &'a CachedStateWrapper, // logs flags error_log: bool, @@ -45,21 +49,25 @@ pub struct TransactionExecutor<'a, S: StateReader> { resources_log: bool, } -impl<'a, S: StateReader> TransactionExecutor<'a, S> { +impl<'a, S, T> TransactionExecutor<'a, S, T> +where + S: StateReader, + T: Iterator, +{ pub fn new( - state: &'a mut CachedStateWrapper, + state: &'a CachedStateWrapper, block_context: &'a BlockContext, charge_fee: bool, - transactions: Vec, + transactions: T, ) -> Self { Self { state, charge_fee, + transactions, block_context, error_log: false, events_log: false, resources_log: false, - transactions: transactions.into_iter(), } } @@ -81,87 +89,91 @@ impl<'a, S: StateReader> TransactionExecutor<'a, S> { } } -impl<'a, S: StateReader> Iterator for TransactionExecutor<'a, S> { +impl<'a, S, T> Iterator for TransactionExecutor<'a, S, T> +where + S: StateReader, + T: Iterator, +{ type Item = TxExecutionResult; - fn next(&mut self) -> Option { - self.transactions.next().map(|tx| { - let res = execute_tx(tx, &mut self.state, self.block_context, self.charge_fee); - - match res { - Ok(info) => { - if self.error_log { - if let Some(err) = &info.revert_error { - let formatted_err = format!("{err:?}").replace("\\n", "\n"); - warn!(target: "executor", "Transaction execution error: {formatted_err}"); - } - } - if self.resources_log { - trace!( - target: "executor", - "Transaction resource usage: {}", - pretty_print_resources(&info.actual_resources) - ); + fn next(&mut self) -> Option { + let res = self + .transactions + .next() + .map(|tx| execute_tx(tx, self.state, self.block_context, self.charge_fee))?; + + match res { + Ok(ref info) => { + if self.error_log { + if let Some(err) = &info.revert_error { + let formatted_err = format!("{err:?}").replace("\\n", "\n"); + warn!(target: "executor", "Transaction execution error: {formatted_err}"); } + } - if self.events_log { - trace_events(&events_from_exec_info(&info)); - } + if self.resources_log { + trace!( + target: "executor", + "Transaction resource usage: {}", + pretty_print_resources(&info.actual_resources) + ); + } - Ok(info) + if self.events_log { + trace_events(&events_from_exec_info(info)); } - Err(err) => { - if self.error_log { - warn_message_transaction_error_exec_error(&err); - } + Some(res) + } - Err(err) + Err(ref err) => { + if self.error_log { + warn_message_transaction_error_exec_error(err); } + + Some(res) } - }) + } } } -pub struct PendingState { - pub state: RwLock>, - /// The transactions that have been executed. - pub executed_transactions: RwLock>>, -} - fn execute_tx( - tx: ExecutionTx, - state: &mut CachedStateWrapper, + tx: ExecutableTxWithHash, + state: &CachedStateWrapper, block_context: &BlockContext, charge_fee: bool, ) -> TxExecutionResult { - let sierra = if let ExecutionTx::Declare(DeclareTxWithClasses { - tx, + let sierra = if let ExecutableTx::Declare(DeclareTxWithClass { + transaction, sierra_class: Some(sierra_class), .. - }) = &tx + }) = tx.as_ref() { - Some((tx.class_hash, sierra_class.clone())) + Some((transaction.class_hash(), sierra_class.clone())) } else { None }; - let res = match tx.into() { - BlockifierExecuteTx::AccountTransaction(tx) => { - tx.execute(&mut state.inner_mut(), block_context, charge_fee) + let res = match BlockifierTx::from(tx).0 { + Transaction::AccountTransaction(tx) => { + tx.execute(&mut state.inner(), block_context, charge_fee) } - BlockifierExecuteTx::L1HandlerTransaction(tx) => { - tx.execute(&mut state.inner_mut(), block_context, charge_fee) + Transaction::L1HandlerTransaction(tx) => { + tx.execute(&mut state.inner(), block_context, charge_fee) } }; - if let res @ Ok(_) = res { + if res.is_ok() { if let Some((class_hash, sierra_class)) = sierra { state.sierra_class_mut().insert(class_hash, sierra_class); } - - res - } else { - res } + + res +} + +pub struct PendingState { + pub state: Arc>, + /// The transactions that have been executed. + pub executed_txs: RwLock>, } diff --git a/crates/katana/executor/src/blockifier/outcome.rs b/crates/katana/executor/src/blockifier/outcome.rs index 27c98a185d..3cd7a9c910 100644 --- a/crates/katana/executor/src/blockifier/outcome.rs +++ b/crates/katana/executor/src/blockifier/outcome.rs @@ -1,69 +1,62 @@ -use std::collections::HashMap; - -use blockifier::state::cached_state::CommitmentStateDiff; use blockifier::transaction::objects::TransactionExecutionInfo; -use katana_primitives::contract::{ClassHash, CompiledContractClass, ContractAddress, SierraClass}; -use katana_primitives::transaction::{Receipt, Tx}; +use katana_primitives::receipt::{ + DeclareTxReceipt, DeployAccountTxReceipt, InvokeTxReceipt, L1HandlerTxReceipt, Receipt, +}; +use katana_primitives::transaction::Tx; use super::utils::{events_from_exec_info, l2_to_l1_messages_from_exec_info}; -pub struct ExecutedTx { - pub tx: Tx, +pub struct TxReceiptWithExecInfo { pub receipt: Receipt, pub execution_info: TransactionExecutionInfo, } -impl ExecutedTx { - pub(super) fn new(tx: Tx, execution_info: TransactionExecutionInfo) -> Self { +impl TxReceiptWithExecInfo { + pub fn from_tx_exec_result( + tx: impl AsRef, + execution_info: TransactionExecutionInfo, + ) -> Self { let actual_fee = execution_info.actual_fee.0; let events = events_from_exec_info(&execution_info); let revert_error = execution_info.revert_error.clone(); let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info); let actual_resources = execution_info.actual_resources.0.clone(); - let contract_address = if let Tx::DeployAccount(ref tx) = tx { - Some(ContractAddress(tx.contract_address.into())) - } else { - None - }; + let receipt = match tx.as_ref() { + Tx::Invoke(_) => Receipt::Invoke(InvokeTxReceipt { + events, + actual_fee, + revert_error, + messages_sent, + actual_resources, + }), - Self { - tx, - execution_info, - receipt: Receipt { + Tx::Declare(_) => Receipt::Declare(DeclareTxReceipt { events, actual_fee, revert_error, messages_sent, actual_resources, - contract_address, - }, - } - } -} + }), -/// The outcome that after executing a list of transactions. -pub struct ExecutionOutcome { - pub transactions: Vec, - pub state_diff: CommitmentStateDiff, - pub declared_classes: HashMap, - pub declared_sierra_classes: HashMap, -} + Tx::L1Handler(_) => Receipt::L1Handler(L1HandlerTxReceipt { + events, + actual_fee, + revert_error, + messages_sent, + actual_resources, + }), -impl Default for ExecutionOutcome { - fn default() -> Self { - let state_diff = CommitmentStateDiff { - storage_updates: Default::default(), - address_to_nonce: Default::default(), - address_to_class_hash: Default::default(), - class_hash_to_compiled_class_hash: Default::default(), + Tx::DeployAccount(tx) => Receipt::DeployAccount(DeployAccountTxReceipt { + events, + actual_fee, + revert_error, + messages_sent, + actual_resources, + contract_address: tx.contract_address, + }), }; - Self { - state_diff, - transactions: Default::default(), - declared_classes: Default::default(), - declared_sierra_classes: Default::default(), - } + Self { receipt, execution_info } } } diff --git a/crates/katana/executor/src/blockifier/state.rs b/crates/katana/executor/src/blockifier/state.rs index 4bdb053fb7..efba599a7f 100644 --- a/crates/katana/executor/src/blockifier/state.rs +++ b/crates/katana/executor/src/blockifier/state.rs @@ -1,17 +1,21 @@ use std::collections::HashMap; -use std::sync::Arc; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::{CachedState, CommitmentStateDiff}; +use blockifier::state::cached_state::CachedState; use blockifier::state::errors::StateError; -use blockifier::state::state_api::{State, StateReader, StateResult}; +use blockifier::state::state_api::StateReader; use katana_primitives::contract::SierraClass; +use katana_primitives::FieldElement; +use katana_provider::traits::contract::ContractClassProvider; use katana_provider::traits::state::StateProvider; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkFelt; +use parking_lot::{Mutex, RawMutex, RwLock}; +use starknet_api::core::{ClassHash, CompiledClassHash, Nonce, PatriciaKey}; +use starknet_api::hash::StarkHash; +use starknet_api::patricia_key; use starknet_api::state::StorageKey; -use tokio::sync::RwLock as AsyncRwLock; +/// A state db only provide read access. +/// +/// This type implements the [`StateReader`] trait so that it can be used as a with [`CachedState`]. pub struct StateRefDb(Box); impl StateRefDb { @@ -63,7 +67,7 @@ impl StateReader for StateRefDb { class_hash: starknet_api::core::ClassHash, ) -> blockifier::state::state_api::StateResult { if let Some(hash) = - StateProvider::compiled_class_hash_of_class_hash(&self.0, class_hash.0.into()) + ContractClassProvider::compiled_class_hash_of_class_hash(&self.0, class_hash.0.into()) .map_err(|e| StateError::StateReadError(e.to_string()))? { Ok(CompiledClassHash(hash.into())) @@ -78,7 +82,7 @@ impl StateReader for StateRefDb { ) -> blockifier::state::state_api::StateResult< blockifier::execution::contract_class::ContractClass, > { - if let Some(class) = StateProvider::class(&self.0, class_hash.0.into()) + if let Some(class) = ContractClassProvider::class(&self.0, class_hash.0.into()) .map_err(|e| StateError::StateReadError(e.to_string()))? { Ok(class) @@ -88,111 +92,115 @@ impl StateReader for StateRefDb { } } -#[derive(Clone)] pub struct CachedStateWrapper { - inner: Arc>>, - sierra_class: Arc>>, + inner: Mutex>, + sierra_class: RwLock>, } impl CachedStateWrapper { pub fn new(db: S) -> Self { - Self { - sierra_class: Default::default(), - inner: Arc::new(AsyncRwLock::new(CachedState::new(db))), - } + Self { sierra_class: Default::default(), inner: Mutex::new(CachedState::new(db)) } + } + + pub fn reset_with_new_state(&self, db: S) { + *self.inner() = CachedState::new(db); + self.sierra_class_mut().clear(); } - pub fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, CachedState> { - tokio::task::block_in_place(|| self.inner.blocking_write()) + pub fn inner(&self) -> parking_lot::lock_api::MutexGuard<'_, RawMutex, CachedState> { + self.inner.lock() } pub fn sierra_class( &self, - ) -> tokio::sync::RwLockReadGuard< + ) -> parking_lot::RwLockReadGuard< '_, HashMap, > { - tokio::task::block_in_place(|| self.sierra_class.blocking_read()) + self.sierra_class.read() } pub fn sierra_class_mut( &self, - ) -> tokio::sync::RwLockWriteGuard< + ) -> parking_lot::RwLockWriteGuard< '_, HashMap, > { - tokio::task::block_in_place(|| self.sierra_class.blocking_write()) + self.sierra_class.write() } } -impl State for CachedStateWrapper { - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - self.inner_mut().increment_nonce(contract_address) - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - self.inner_mut().set_class_hash_at(contract_address, class_hash) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - self.inner_mut().set_compiled_class_hash(class_hash, compiled_class_hash) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - self.inner_mut().set_contract_class(class_hash, contract_class) +impl ContractClassProvider for CachedStateWrapper +where + Db: StateReader + Sync + Send, +{ + fn class( + &self, + hash: katana_primitives::contract::ClassHash, + ) -> anyhow::Result> { + let Ok(class) = self.inner().get_compiled_contract_class(&ClassHash(hash.into())) else { + return Ok(None); + }; + Ok(Some(class)) } - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.inner_mut().set_storage_at(contract_address, key, value) + fn compiled_class_hash_of_class_hash( + &self, + hash: katana_primitives::contract::ClassHash, + ) -> anyhow::Result> { + let Ok(hash) = self.inner().get_compiled_class_hash(ClassHash(hash.into())) else { + return Ok(None); + }; + Ok(Some(hash.0.into())) } - fn to_state_diff(&self) -> CommitmentStateDiff { - self.inner_mut().to_state_diff() + fn sierra_class( + &self, + hash: katana_primitives::contract::ClassHash, + ) -> anyhow::Result> { + let class @ Some(_) = self.sierra_class().get(&hash).cloned() else { + return Ok(None); + }; + Ok(class) } } -impl StateReader for CachedStateWrapper { - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.inner_mut().get_class_hash_at(contract_address) - } - - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - self.inner_mut().get_compiled_class_hash(class_hash) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.inner_mut().get_compiled_contract_class(class_hash) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.inner_mut().get_nonce_at(contract_address) +impl StateProvider for CachedStateWrapper +where + Db: StateReader + Sync + Send, +{ + fn storage( + &self, + address: katana_primitives::contract::ContractAddress, + storage_key: katana_primitives::contract::StorageKey, + ) -> anyhow::Result> { + let Ok(value) = + self.inner().get_storage_at(address.into(), StorageKey(patricia_key!(storage_key))) + else { + return Ok(None); + }; + Ok(Some(value.into())) + } + + fn nonce( + &self, + address: katana_primitives::contract::ContractAddress, + ) -> anyhow::Result> { + let Ok(nonce) = self.inner().get_nonce_at(address.into()) else { + return Ok(None); + }; + Ok(Some(nonce.0.into())) } - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - self.inner_mut().get_storage_at(contract_address, key) + fn class_hash_of_contract( + &self, + address: katana_primitives::contract::ContractAddress, + ) -> anyhow::Result> { + let Ok(hash) = self.inner().get_class_hash_at(address.into()) else { + return Ok(None); + }; + + let hash = hash.0.into(); + if hash == FieldElement::ZERO { Ok(None) } else { Ok(Some(hash)) } } } diff --git a/crates/katana/executor/src/blockifier/transactions.rs b/crates/katana/executor/src/blockifier/transactions.rs new file mode 100644 index 0000000000..70f4b0c37d --- /dev/null +++ b/crates/katana/executor/src/blockifier/transactions.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use ::blockifier::transaction::transaction_execution::Transaction; +use ::blockifier::transaction::transactions::DeployAccountTransaction; +use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::transactions::{DeclareTransaction, L1HandlerTransaction}; +use katana_primitives::transaction::{DeclareTx, ExecutableTx, ExecutableTxWithHash}; +use starknet_api::core::{ClassHash, CompiledClassHash, EntryPointSelector, Nonce}; +use starknet_api::transaction::{ + Calldata, ContractAddressSalt, DeclareTransactionV0V1, DeclareTransactionV2, Fee, + InvokeTransaction, InvokeTransactionV1, TransactionHash, TransactionSignature, + TransactionVersion, +}; + +/// A newtype wrapper for execution transaction used in `blockifier`. +pub struct BlockifierTx(pub(super) ::blockifier::transaction::transaction_execution::Transaction); + +impl From for BlockifierTx { + fn from(value: ExecutableTxWithHash) -> Self { + let hash = value.hash; + + let tx = match value.transaction { + ExecutableTx::Invoke(tx) => { + let calldata = tx.calldata.into_iter().map(|f| f.into()).collect(); + let signature = tx.signature.into_iter().map(|f| f.into()).collect(); + Transaction::AccountTransaction(AccountTransaction::Invoke(InvokeTransaction::V1( + InvokeTransactionV1 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + signature: TransactionSignature(signature), + calldata: Calldata(Arc::new(calldata)), + transaction_hash: TransactionHash(hash.into()), + }, + ))) + } + + ExecutableTx::DeployAccount(tx) => { + let calldata = tx.constructor_calldata.into_iter().map(|f| f.into()).collect(); + let signature = tx.signature.into_iter().map(|f| f.into()).collect(); + Transaction::AccountTransaction(AccountTransaction::DeployAccount( + DeployAccountTransaction { + contract_address: tx.contract_address.into(), + tx: starknet_api::transaction::DeployAccountTransaction { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + version: TransactionVersion(1u128.into()), + signature: TransactionSignature(signature), + class_hash: ClassHash(tx.class_hash.into()), + transaction_hash: TransactionHash(hash.into()), + constructor_calldata: Calldata(Arc::new(calldata)), + contract_address_salt: ContractAddressSalt( + tx.contract_address_salt.into(), + ), + }, + }, + )) + } + + ExecutableTx::Declare(tx) => { + let contract_class = tx.compiled_class; + + let tx = match tx.transaction { + DeclareTx::V1(tx) => { + let signature = tx.signature.into_iter().map(|f| f.into()).collect(); + starknet_api::transaction::DeclareTransaction::V1(DeclareTransactionV0V1 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + signature: TransactionSignature(signature), + class_hash: ClassHash(tx.class_hash.into()), + transaction_hash: TransactionHash(hash.into()), + }) + } + + DeclareTx::V2(tx) => { + let signature = tx.signature.into_iter().map(|f| f.into()).collect(); + starknet_api::transaction::DeclareTransaction::V2(DeclareTransactionV2 { + max_fee: Fee(tx.max_fee), + nonce: Nonce(tx.nonce.into()), + sender_address: tx.sender_address.into(), + signature: TransactionSignature(signature), + class_hash: ClassHash(tx.class_hash.into()), + transaction_hash: TransactionHash(hash.into()), + compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.into()), + }) + } + }; + + let tx = DeclareTransaction::new(tx, contract_class).expect("class mismatch"); + Transaction::AccountTransaction(AccountTransaction::Declare(tx)) + } + + ExecutableTx::L1Handler(tx) => { + let calldata = tx.calldata.into_iter().map(|f| f.into()).collect(); + Transaction::L1HandlerTransaction(L1HandlerTransaction { + paid_fee_on_l1: Fee(tx.paid_fee_on_l1), + tx: starknet_api::transaction::L1HandlerTransaction { + nonce: Nonce(tx.nonce.into()), + calldata: Calldata(Arc::new(calldata)), + version: TransactionVersion(1u128.into()), + contract_address: tx.contract_address.into(), + transaction_hash: TransactionHash(hash.into()), + entry_point_selector: EntryPointSelector(tx.entry_point_selector.into()), + }, + }) + } + }; + + Self(tx) + } +} diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs index 1d0f1a8bf7..cf8edbffb8 100644 --- a/crates/katana/executor/src/blockifier/utils.rs +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -1,19 +1,128 @@ use std::collections::HashMap; +use std::sync::Arc; -use blockifier::execution::entry_point::CallInfo; -use blockifier::execution::errors::EntryPointExecutionError; -use blockifier::state::state_api::{State, StateReader}; +use ::blockifier::block_context::BlockContext; +use ::blockifier::execution::entry_point::{ + CallEntryPoint, CallInfo, EntryPointExecutionContext, ExecutionResources, +}; +use ::blockifier::execution::errors::EntryPointExecutionError; +use ::blockifier::state::cached_state::{CachedState, MutRefState}; +use ::blockifier::transaction::objects::AccountTransactionContext; +use blockifier::fee::fee_utils::{calculate_l1_gas_by_vm_usage, extract_l1_gas_and_vm_usage}; +use blockifier::state::state_api::State; use blockifier::transaction::errors::TransactionExecutionError; use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; use convert_case::{Case, Casing}; -use katana_primitives::transaction::Tx; +use katana_primitives::contract::ContractAddress; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::ExecutableTxWithHash; use katana_primitives::FieldElement; -use starknet::core::types::{Event, MsgToL1}; +use katana_provider::traits::contract::ContractClassProvider; +use katana_provider::traits::state::StateProvider; +use starknet::core::types::{Event, FeeEstimate, MsgToL1}; use starknet::core::utils::parse_cairo_short_string; +use starknet_api::core::EntryPointSelector; +use starknet_api::transaction::Calldata; use tracing::trace; -use super::outcome::{ExecutedTx, ExecutionOutcome}; use super::state::{CachedStateWrapper, StateRefDb}; +use super::TransactionExecutor; + +#[derive(Debug)] +pub struct EntryPointCall { + /// The address of the contract whose function you're calling. + pub contract_address: ContractAddress, + /// The input to the function. + pub calldata: Vec, + /// The function selector. + pub entry_point_selector: FieldElement, +} + +/// Perform a function call on a contract and retrieve the return values. +pub fn call( + request: EntryPointCall, + block_context: BlockContext, + state: Box, +) -> Result, EntryPointExecutionError> { + let res = raw_call(request, block_context, state, 1_000_000_000, 1_000_000_000)?; + let retdata = res.execution.retdata.0; + let retdata = retdata.into_iter().map(|f| f.into()).collect::>(); + Ok(retdata) +} + +/// Estimate the execution fee for a list of transactions. +pub fn estimate_fee( + transactions: impl Iterator, + block_context: BlockContext, + state: Box, +) -> Result, TransactionExecutionError> { + let state = CachedStateWrapper::new(StateRefDb::from(state)); + let results = TransactionExecutor::new(&state, &block_context, false, transactions) + .with_error_log() + .execute(); + + results + .into_iter() + .map(|res| { + let exec_info = res?; + + if exec_info.revert_error.is_some() { + return Err(TransactionExecutionError::ExecutionError( + EntryPointExecutionError::ExecutionFailed { error_data: Default::default() }, + )); + } + + calculate_execution_fee(&block_context, &exec_info) + }) + .collect::, _>>() +} + +/// Perform a raw entrypoint call of a contract. +pub fn raw_call( + request: EntryPointCall, + block_context: BlockContext, + state: Box, + initial_gas: u64, + max_n_steps: usize, +) -> Result { + let mut state = CachedState::new(StateRefDb::from(state)); + let mut state = CachedState::new(MutRefState::new(&mut state)); + + let call = CallEntryPoint { + initial_gas, + storage_address: request.contract_address.into(), + entry_point_selector: EntryPointSelector(request.entry_point_selector.into()), + calldata: Calldata(Arc::new(request.calldata.into_iter().map(|f| f.into()).collect())), + ..Default::default() + }; + + call.execute( + &mut state, + &mut ExecutionResources::default(), + &mut EntryPointExecutionContext::new( + block_context, + AccountTransactionContext::default(), + max_n_steps, + ), + ) +} + +/// Calculate the fee of a transaction execution. +pub fn calculate_execution_fee( + block_context: &BlockContext, + exec_info: &TransactionExecutionInfo, +) -> Result { + let (l1_gas_usage, vm_resources) = extract_l1_gas_and_vm_usage(&exec_info.actual_resources); + let l1_gas_by_vm_usage = calculate_l1_gas_by_vm_usage(block_context, &vm_resources)?; + + let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; + + let gas_price = block_context.gas_price as u64; + let gas_consumed = total_l1_gas_usage.ceil() as u64; + let overall_fee = total_l1_gas_usage.ceil() as u64 * gas_price; + + Ok(FeeEstimate { gas_price, gas_consumed, overall_fee }) +} pub(crate) fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { match err { @@ -65,41 +174,91 @@ pub(crate) fn pretty_print_resources(resources: &ResourcesMapping) -> String { mapped_strings.join(" | ") } -pub(crate) fn trace_events(events: &[Event]) { - for e in events { - let formatted_keys = - e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); +pub fn get_state_update_from_cached_state( + state: &CachedStateWrapper, +) -> StateUpdatesWithDeclaredClasses { + let state_diff = state.inner().to_state_diff(); - trace!(target: "executor", "Event emitted keys=[{}]", formatted_keys); - } -} + let declared_sierra_classes = state.sierra_class().clone(); -pub fn create_execution_outcome( - state: &mut CachedStateWrapper, - executed_txs: Vec<(Tx, TransactionExecutionInfo)>, -) -> ExecutionOutcome { - let transactions = executed_txs.into_iter().map(|(tx, res)| ExecutedTx::new(tx, res)).collect(); - let state_diff = state.to_state_diff(); - let declared_classes = state_diff + let declared_compiled_classes = state_diff .class_hash_to_compiled_class_hash .iter() .map(|(class_hash, _)| { - let contract_class = state - .get_compiled_contract_class(class_hash) - .expect("qed; class must exist if declared"); - (class_hash.0.into(), contract_class) + let class = state.class(class_hash.0.into()).unwrap().expect("must exist if declared"); + (class_hash.0.into(), class) }) - .collect::>(); + .collect::>(); + + let nonce_updates = + state_diff + .address_to_nonce + .into_iter() + .map(|(key, value)| (key.into(), value.0.into())) + .collect::>(); + + let storage_changes = state_diff + .storage_updates + .into_iter() + .map(|(addr, entries)| { + let entries = entries + .into_iter() + .map(|(k, v)| ((*k.0.key()).into(), v.into())) + .collect::>(); + + (addr.into(), entries) + }) + .collect::>(); + + let contract_updates = state_diff + .address_to_class_hash + .into_iter() + .map(|(key, value)| (key.into(), value.0.into())) + .collect::>(); + + let declared_classes = state_diff + .class_hash_to_compiled_class_hash + .into_iter() + .map(|(key, value)| (key.0.into(), value.0.into())) + .collect::>(); + + StateUpdatesWithDeclaredClasses { + declared_sierra_classes, + declared_compiled_classes, + state_updates: StateUpdates { + nonce_updates, + storage_updates: storage_changes, + contract_updates, + declared_classes, + }, + } +} - ExecutionOutcome { - state_diff, - transactions, - declared_classes, - declared_sierra_classes: state.sierra_class().clone(), +pub(super) fn trace_events(events: &[Event]) { + for e in events { + let formatted_keys = + e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); + + trace!(target: "executor", "Event emitted keys=[{}]", formatted_keys); } } -pub(crate) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { +pub(super) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { let mut events: Vec = vec![]; fn get_events_recursively(call_info: &CallInfo) -> Vec { @@ -133,7 +292,7 @@ pub(crate) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) - events } -pub(crate) fn l2_to_l1_messages_from_exec_info( +pub(super) fn l2_to_l1_messages_from_exec_info( execution_info: &TransactionExecutionInfo, ) -> Vec { let mut messages = vec![]; From 431d015e3fc43a72f528fbeac741a098b667f7e7 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:25:48 +0800 Subject: [PATCH 095/192] refactor(katana): integrate current impl with new design --- Cargo.lock | 40 +- crates/katana/core/Cargo.toml | 4 + crates/katana/core/src/accounts.rs | 62 +- crates/katana/core/src/backend/config.rs | 11 +- .../katana/core/src/backend/in_memory_db.rs | 358 ----------- crates/katana/core/src/backend/mod.rs | 362 +++-------- crates/katana/core/src/backend/storage.rs | 92 +++ .../katana/core/src/backend/storage/block.rs | 221 ------- crates/katana/core/src/backend/storage/mod.rs | 227 ------- .../core/src/backend/storage/transaction.rs | 294 --------- crates/katana/core/src/constants.rs | 46 +- crates/katana/core/src/db/cached.rs | 597 ------------------ crates/katana/core/src/db/mod.rs | 136 ---- crates/katana/core/src/db/serde/contract.rs | 229 ------- crates/katana/core/src/db/serde/mod.rs | 3 - crates/katana/core/src/db/serde/program.rs | 248 -------- crates/katana/core/src/db/serde/state.rs | 61 -- crates/katana/core/src/env.rs | 8 +- crates/katana/core/src/execution.rs | 407 ------------ crates/katana/core/src/fork/backend.rs | 475 -------------- crates/katana/core/src/fork/db.rs | 287 --------- crates/katana/core/src/fork/mod.rs | 2 - crates/katana/core/src/lib.rs | 3 - crates/katana/core/src/pool.rs | 11 +- crates/katana/core/src/sequencer.rs | 488 ++++++-------- crates/katana/core/src/sequencer_error.rs | 16 +- .../katana/core/src/service/block_producer.rs | 176 +++--- .../core/src/service/messaging/ethereum.rs | 197 +++--- .../core/src/service/messaging/service.rs | 47 +- .../core/src/service/messaging/starknet.rs | 94 +-- crates/katana/core/src/service/mod.rs | 6 +- crates/katana/core/src/utils/mod.rs | 55 +- crates/katana/core/src/utils/transaction.rs | 594 ----------------- crates/katana/core/tests/backend.rs | 23 +- crates/katana/core/tests/sequencer.rs | 230 ++----- crates/katana/rpc/Cargo.toml | 8 +- crates/katana/rpc/src/api/starknet.rs | 106 ++-- crates/katana/rpc/src/katana.rs | 27 +- crates/katana/rpc/src/starknet.rs | 557 +++++++++------- crates/katana/src/args.rs | 8 - crates/katana/src/main.rs | 24 +- 41 files changed, 1242 insertions(+), 5598 deletions(-) delete mode 100644 crates/katana/core/src/backend/in_memory_db.rs create mode 100644 crates/katana/core/src/backend/storage.rs delete mode 100644 crates/katana/core/src/backend/storage/block.rs delete mode 100644 crates/katana/core/src/backend/storage/mod.rs delete mode 100644 crates/katana/core/src/backend/storage/transaction.rs delete mode 100644 crates/katana/core/src/db/cached.rs delete mode 100644 crates/katana/core/src/db/mod.rs delete mode 100644 crates/katana/core/src/db/serde/contract.rs delete mode 100644 crates/katana/core/src/db/serde/mod.rs delete mode 100644 crates/katana/core/src/db/serde/program.rs delete mode 100644 crates/katana/core/src/db/serde/state.rs delete mode 100644 crates/katana/core/src/execution.rs delete mode 100644 crates/katana/core/src/fork/backend.rs delete mode 100644 crates/katana/core/src/fork/db.rs delete mode 100644 crates/katana/core/src/fork/mod.rs delete mode 100644 crates/katana/core/src/utils/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 93eebee59a..0a604d9ced 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5098,6 +5098,9 @@ dependencies = [ "flate2", "futures", "hex", + "katana-executor", + "katana-primitives", + "katana-provider", "lazy_static", "parking_lot 0.12.1", "rand", @@ -5130,8 +5133,9 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.3.12" +version = "0.3.14" dependencies = [ + "anyhow", "blockifier", "convert_case 0.6.0", "katana-primitives", @@ -5151,11 +5155,13 @@ dependencies = [ "blockifier", "cairo-lang-starknet", "cairo-vm", + "derive_more", "flate2", "serde", "serde_json", "starknet", "starknet_api", + "thiserror", ] [[package]] @@ -5181,9 +5187,7 @@ version = "0.3.15" dependencies = [ "anyhow", "assert_matches", - "blockifier", "cairo-lang-starknet", - "cairo-vm", "dojo-test-utils", "flate2", "futures", @@ -5191,6 +5195,11 @@ dependencies = [ "hyper", "jsonrpsee", "katana-core", + "katana-executor", + "katana-primitives", + "katana-provider", + "katana-rpc-types", + "katana-rpc-types-builder", "serde", "serde_json", "serde_with", @@ -5204,6 +5213,31 @@ dependencies = [ "url", ] +[[package]] +name = "katana-rpc-types" +version = "0.3.14" +dependencies = [ + "anyhow", + "derive_more", + "katana-primitives", + "serde", + "serde_json", + "serde_with", + "starknet", +] + +[[package]] +name = "katana-rpc-types-builder" +version = "0.3.14" +dependencies = [ + "anyhow", + "katana-executor", + "katana-primitives", + "katana-provider", + "katana-rpc-types", + "starknet", +] + [[package]] name = "keccak" version = "0.1.4" diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 563734b5ec..25f13ca1ff 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -7,6 +7,10 @@ repository.workspace = true version.workspace = true [dependencies] +katana-executor = { path = "../executor" } +katana-primitives = { path = "../primitives" } +katana-provider = { path = "../storage/provider" } + anyhow.workspace = true async-trait.workspace = true blockifier.workspace = true diff --git a/crates/katana/core/src/accounts.rs b/crates/katana/core/src/accounts.rs index 88a1df43dd..439afb85ff 100644 --- a/crates/katana/core/src/accounts.rs +++ b/crates/katana/core/src/accounts.rs @@ -1,25 +1,22 @@ use std::fmt::Display; use std::sync::Arc; -use blockifier::abi::abi_utils::get_storage_var_address; +use anyhow::Result; use blockifier::execution::contract_class::ContractClass; -use blockifier::state::state_api::StateResult; +use katana_primitives::contract::ContractAddress; +use katana_primitives::FieldElement; +use katana_provider::traits::state::StateWriter; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; use serde::Serialize; use serde_with::serde_as; use starknet::core::serde::unsigned_field_element::UfeHex; -use starknet::core::types::FieldElement; -use starknet::core::utils::get_contract_address; +use starknet::core::utils::{get_contract_address, get_storage_var_address}; use starknet::signers::SigningKey; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; use crate::constants::{ - DEFAULT_ACCOUNT_CONTRACT, DEFAULT_ACCOUNT_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, + FEE_TOKEN_ADDRESS, OZ_V0_ACCOUNT_CONTRACT, OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH, }; -use crate::db::Database; #[serde_as] #[derive(Debug, Clone, Serialize)] @@ -58,45 +55,34 @@ impl Account { } // TODO: separate fund logic from this struct - implement FeeToken type - pub fn deploy_and_fund(&self, state: &mut dyn Database) -> StateResult<()> { - self.declare(state)?; + pub fn deploy_and_fund(&self, state: &dyn StateWriter) -> Result<()> { self.deploy(state)?; - self.fund(state); + self.fund(state)?; Ok(()) } - fn deploy(&self, state: &mut dyn Database) -> StateResult<()> { - let address = ContractAddress(patricia_key!(self.address)); + fn deploy(&self, state: &dyn StateWriter) -> Result<()> { + let address: ContractAddress = self.address.into(); // set the class hash at the account address - state.set_class_hash_at(address, ClassHash(self.class_hash.into()))?; + state.set_class_hash_of_contract(address, self.class_hash)?; // set the public key in the account contract - state.set_storage_at( + state.set_storage( address, get_storage_var_address("Account_public_key", &[]).unwrap(), - self.public_key.into(), - ); + self.public_key, + )?; // initialze account nonce - state.set_nonce(address, Nonce(1u128.into())); + state.set_nonce(address, 1u128.into())?; Ok(()) } - fn fund(&self, state: &mut dyn Database) { - state.set_storage_at( - ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)), - get_storage_var_address("ERC20_balances", &[self.address.into()]).unwrap(), - self.balance.into(), - ); - } - - fn declare(&self, state: &mut dyn Database) -> StateResult<()> { - let class_hash = ClassHash(self.class_hash.into()); - - if state.get_compiled_contract_class(&class_hash).is_ok() { - return Ok(()); - } - - state.set_contract_class(&class_hash, (*self.contract_class).clone())?; - state.set_compiled_class_hash(class_hash, CompiledClassHash(self.class_hash.into())) + fn fund(&self, state: &dyn StateWriter) -> Result<()> { + state.set_storage( + *FEE_TOKEN_ADDRESS, + get_storage_var_address("ERC20_balances", &[self.address]).unwrap(), + self.balance, + )?; + Ok(()) } } @@ -128,8 +114,8 @@ impl DevAccountGenerator { total, seed: [0u8; 32], balance: FieldElement::ZERO, - class_hash: (*DEFAULT_ACCOUNT_CONTRACT_CLASS_HASH).into(), - contract_class: Arc::new((*DEFAULT_ACCOUNT_CONTRACT).clone()), + class_hash: (*OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH), + contract_class: Arc::new((*OZ_V0_ACCOUNT_CONTRACT).clone()), } } diff --git a/crates/katana/core/src/backend/config.rs b/crates/katana/core/src/backend/config.rs index c6a7131fe0..78d32b31ad 100644 --- a/crates/katana/core/src/backend/config.rs +++ b/crates/katana/core/src/backend/config.rs @@ -1,15 +1,12 @@ use blockifier::block_context::BlockContext; use starknet_api::block::{BlockNumber, BlockTimestamp}; -use starknet_api::core::{ChainId, ContractAddress, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; +use starknet_api::core::ChainId; use url::Url; use crate::constants::{ DEFAULT_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_VALIDATE_MAX_STEPS, FEE_TOKEN_ADDRESS, SEQUENCER_ADDRESS, }; -use crate::db::serde::state::SerializableState; use crate::env::{get_default_vm_resource_fee_cost, BlockContextGenerator}; #[derive(Debug)] @@ -20,7 +17,6 @@ pub struct StarknetConfig { pub env: Environment, pub fork_rpc_url: Option, pub fork_block_number: Option, - pub init_state: Option, } impl StarknetConfig { @@ -29,8 +25,8 @@ impl StarknetConfig { block_number: BlockNumber::default(), chain_id: ChainId(self.env.chain_id.clone()), block_timestamp: BlockTimestamp::default(), - sequencer_address: ContractAddress(patricia_key!(*SEQUENCER_ADDRESS)), - fee_token_address: ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)), + sequencer_address: (*SEQUENCER_ADDRESS).into(), + fee_token_address: (*FEE_TOKEN_ADDRESS).into(), vm_resource_fee_cost: get_default_vm_resource_fee_cost().into(), gas_price: self.env.gas_price, validate_max_n_steps: self.env.validate_max_steps, @@ -47,7 +43,6 @@ impl StarknetConfig { impl Default for StarknetConfig { fn default() -> Self { Self { - init_state: None, seed: [0; 32], total_accounts: 10, disable_fee: false, diff --git a/crates/katana/core/src/backend/in_memory_db.rs b/crates/katana/core/src/backend/in_memory_db.rs deleted file mode 100644 index e43085063f..0000000000 --- a/crates/katana/core/src/backend/in_memory_db.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use anyhow::Result; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::CommitmentStateDiff; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::core::types::FlattenedSierraClass; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::patricia_key; -use starknet_api::state::StorageKey; - -use crate::constants::{ - ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, ERC20_DECIMALS_STORAGE_SLOT, - ERC20_NAME_STORAGE_SLOT, ERC20_SYMBOL_STORAGE_SLOT, FEE_TOKEN_ADDRESS, UDC_ADDRESS, - UDC_CLASS_HASH, UDC_CONTRACT, -}; -use crate::db::cached::{AsCachedDb, CachedDb, ClassRecord, MaybeAsCachedDb, StorageRecord}; -use crate::db::serde::state::{ - SerializableClassRecord, SerializableState, SerializableStorageRecord, -}; -use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; - -/// A in memory state database implementation with empty cache db. -#[derive(Clone, Debug)] -pub struct MemDb { - pub db: CachedDb<()>, -} - -impl MemDb { - pub fn new() -> Self { - Self { db: CachedDb::new(()) } - } -} - -impl Default for MemDb { - fn default() -> Self { - let mut state = Self::new(); - deploy_fee_contract(&mut state); - deploy_universal_deployer_contract(&mut state); - state - } -} - -impl State for MemDb { - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - let current_nonce = self.get_nonce_at(contract_address)?; - let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; - let next_nonce_val = 1_u64 + current_nonce_as_u64; - let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); - self.db.storage.entry(contract_address).or_default().nonce = next_nonce; - Ok(()) - } - - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.db.storage.entry(contract_address).or_default().storage.insert(key, value); - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - if contract_address == ContractAddress::default() { - return Err(StateError::OutOfRangeContractAddress); - } - self.db.contracts.insert(contract_address, class_hash); - Ok(()) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - if !self.db.classes.contains_key(&class_hash) { - return Err(StateError::UndeclaredClassHash(class_hash)); - } - self.db.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); - Ok(()) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - let compiled_hash = CompiledClassHash(class_hash.0); - self.db.classes.insert(*class_hash, ClassRecord { class: contract_class, compiled_hash }); - Ok(()) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - unreachable!("to_state_diff should not be called on MemDb") - } -} - -impl StateReader for MemDb { - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - let value = self - .db - .storage - .get(&contract_address) - .and_then(|r| r.storage.get(&key)) - .copied() - .unwrap_or_default(); - Ok(value) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - let nonce = self.db.storage.get(&contract_address).map(|r| r.nonce).unwrap_or_default(); - Ok(nonce) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.db - .classes - .get(class_hash) - .map(|r| r.class.clone()) - .ok_or(StateError::UndeclaredClassHash(*class_hash)) - } - - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - Ok(self.db.contracts.get(&contract_address).cloned().unwrap_or_default()) - } - - fn get_compiled_class_hash( - &mut self, - class_hash: ClassHash, - ) -> StateResult { - self.db - .classes - .get(&class_hash) - .map(|r| r.compiled_hash) - .ok_or(StateError::UndeclaredClassHash(class_hash)) - } -} - -impl StateExtRef for MemDb { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - if let ContractClass::V0(_) = self.get_compiled_contract_class(class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - - self.db - .sierra_classes - .get(class_hash) - .cloned() - .ok_or(StateError::StateReadError("Missing Sierra class".to_string())) - } -} - -impl StateExt for MemDb { - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - // check the class hash must not be a legacy contract - if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - self.db.sierra_classes.insert(class_hash, sierra_class); - Ok(()) - } -} - -impl AsStateRefDb for MemDb { - fn as_ref_db(&self) -> StateRefDb { - StateRefDb::new(MemDb { db: self.db.clone() }) - } -} - -impl MaybeAsCachedDb for MemDb { - fn maybe_as_cached_db(&self) -> Option { - Some(CachedDb { - db: (), - classes: self.db.classes.clone(), - storage: self.db.storage.clone(), - contracts: self.db.contracts.clone(), - sierra_classes: self.db.sierra_classes.clone(), - }) - } -} - -impl Database for MemDb { - fn dump_state(&self) -> Result { - let mut serializable = SerializableState::default(); - - self.db.storage.iter().for_each(|(addr, storage)| { - let mut record = SerializableStorageRecord { - storage: BTreeMap::new(), - nonce: storage.nonce.0.into(), - }; - - storage.storage.iter().for_each(|(key, value)| { - record.storage.insert((*key.0.key()).into(), (*value).into()); - }); - - serializable.storage.insert((*addr.0.key()).into(), record); - }); - - self.db.classes.iter().for_each(|(class_hash, class_record)| { - serializable.classes.insert( - class_hash.0.into(), - SerializableClassRecord { - class: class_record.class.clone().into(), - compiled_hash: class_record.compiled_hash.0.into(), - }, - ); - }); - - self.db.contracts.iter().for_each(|(address, class_hash)| { - serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); - }); - - self.db.sierra_classes.iter().for_each(|(class_hash, class)| { - serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); - }); - - Ok(serializable) - } - - fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { - self.db.storage.entry(addr).or_default().nonce = nonce; - } -} - -fn deploy_fee_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)); - let hash = ClassHash(*ERC20_CONTRACT_CLASS_HASH); - let compiled_hash = CompiledClassHash(*ERC20_CONTRACT_CLASS_HASH); - - state.db.classes.insert(hash, ClassRecord { class: (*ERC20_CONTRACT).clone(), compiled_hash }); - state.db.contracts.insert(address, hash); - state.db.storage.insert( - address, - StorageRecord { - nonce: Nonce(1_u128.into()), - storage: [ - (*ERC20_NAME_STORAGE_SLOT, 0x4574686572_u128.into()), - (*ERC20_SYMBOL_STORAGE_SLOT, 0x455448_u128.into()), - (*ERC20_DECIMALS_STORAGE_SLOT, 18_u128.into()), - ] - .into_iter() - .collect(), - }, - ); -} - -fn deploy_universal_deployer_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*UDC_ADDRESS)); - let hash = ClassHash(*UDC_CLASS_HASH); - let compiled_hash = CompiledClassHash(*UDC_CLASS_HASH); - - state.db.classes.insert(hash, ClassRecord { class: (*UDC_CONTRACT).clone(), compiled_hash }); - state.db.contracts.insert(address, hash); - state - .db - .storage - .insert(address, StorageRecord { nonce: Nonce(1_u128.into()), storage: HashMap::new() }); -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use starknet_api::core::{ClassHash, PatriciaKey}; - use starknet_api::stark_felt; - - use super::*; - use crate::backend::in_memory_db::MemDb; - use crate::constants::UDC_CONTRACT; - // use crate::db::cached::CachedStateWrapper; - use crate::execution::ExecutionOutcome; - - #[test] - fn dump_and_load_state() { - let mut state = MemDb::new(); - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - state.set_contract_class(&class_hash, (*UDC_CONTRACT).clone()).unwrap(); - state.set_compiled_class_hash(class_hash, CompiledClassHash(class_hash.0)).unwrap(); - state.set_class_hash_at(address, class_hash).unwrap(); - state.set_storage_at(address, storage_key, storage_val); - - let dump = state.dump_state().expect("should dump state"); - - let mut new_state = MemDb::new(); - new_state.load_state(dump).expect("should load state"); - - assert_eq!(new_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(new_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(new_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(new_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } - - #[test] - fn apply_state_update() { - let mut old_state = MemDb::new(); - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - let execution_outcome = ExecutionOutcome { - state_diff: CommitmentStateDiff { - address_to_class_hash: [(address, class_hash)].into(), - address_to_nonce: [].into(), - storage_updates: [(address, [(storage_key, storage_val)].into())].into(), - class_hash_to_compiled_class_hash: [(class_hash, CompiledClassHash(class_hash.0))] - .into(), - }, - transactions: vec![], - declared_classes: HashMap::from([(class_hash, (*UDC_CONTRACT).clone())]), - declared_sierra_classes: HashMap::new(), - }; - - assert_matches!( - old_state.get_compiled_contract_class(&class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_matches!( - old_state.get_compiled_class_hash(class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), ClassHash::default()); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), StarkFelt::default()); - - execution_outcome.apply_to(&mut old_state); - - assert_eq!(old_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(old_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } -} diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 745fe78a06..4c3d431d74 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -1,74 +1,47 @@ -use std::io::Write; use std::sync::Arc; -use anyhow::Result; -use blockifier::execution::entry_point::{ - CallEntryPoint, CallInfo, EntryPointExecutionContext, ExecutionResources, +use blockifier::block_context::BlockContext; +use katana_primitives::block::{ + Block, BlockHashOrNumber, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, }; -use blockifier::execution::errors::EntryPointExecutionError; -use blockifier::fee::fee_utils::{calculate_l1_gas_by_vm_usage, extract_l1_gas_and_vm_usage}; -use blockifier::state::cached_state::{CachedState, MutRefState}; -use blockifier::state::state_api::StateReader; -use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::objects::AccountTransactionContext; -use flate2::write::GzEncoder; -use flate2::Compression; +use katana_primitives::contract::ContractAddress; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::StateUpdatesWithDeclaredClasses; +use katana_primitives::transaction::TxWithHash; +use katana_primitives::FieldElement; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::block::{BlockHashProvider, BlockWriter}; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; use parking_lot::RwLock; -use starknet::core::types::{ - BlockId, BlockTag, FeeEstimate, MaybePendingBlockWithTxHashes, TransactionFinalityStatus, -}; +use starknet::core::types::{BlockId, MaybePendingBlockWithTxHashes}; use starknet::core::utils::parse_cairo_short_string; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; use starknet_api::block::{BlockNumber, BlockTimestamp}; -use starknet_api::core::{ChainId, ContractAddress, EntryPointSelector, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; -use starknet_api::transaction::Calldata; -use tokio::sync::RwLock as AsyncRwLock; -use tracing::{info, trace, warn}; +use starknet_api::core::ChainId; +use tracing::{info, trace}; pub mod config; pub mod contract; -pub mod in_memory_db; pub mod storage; use self::config::StarknetConfig; -use self::storage::block::{Block, PartialHeader}; -use self::storage::transaction::{IncludedTransaction, Transaction}; -use self::storage::{Blockchain, InMemoryBlockStates, Storage}; +use self::storage::Blockchain; use crate::accounts::{Account, DevAccountGenerator}; -use crate::backend::in_memory_db::MemDb; -use crate::backend::storage::transaction::KnownTransaction; use crate::constants::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; -use crate::db::cached::CachedStateWrapper; -use crate::db::serde::state::SerializableState; -use crate::db::{Database, StateRefDb}; use crate::env::{BlockContextGenerator, Env}; -use crate::execution::{ExecutionOutcome, MaybeInvalidExecutedTransaction, TransactionExecutor}; -use crate::fork::db::ForkedDb; -use crate::sequencer_error::SequencerError; use crate::service::block_producer::MinedBlockOutcome; -use crate::utils::{convert_state_diff_to_rpc_state_diff, get_current_timestamp}; - -pub struct ExternalFunctionCall { - pub calldata: Calldata, - pub contract_address: ContractAddress, - pub entry_point_selector: EntryPointSelector, -} +use crate::utils::get_current_timestamp; pub struct Backend { /// The config used to generate the backend. pub config: RwLock, /// stores all block related data in memory pub blockchain: Blockchain, - /// Historic states of previous blocks - pub states: AsyncRwLock, /// The chain environment values. pub env: Arc>, pub block_context_generator: RwLock, - /// The latest state. - pub state: Arc>, /// Prefunded dev accounts pub accounts: Vec, } @@ -80,219 +53,115 @@ impl Backend { let accounts = DevAccountGenerator::new(config.total_accounts) .with_seed(config.seed) - .with_balance((*DEFAULT_PREFUNDED_ACCOUNT_BALANCE).into()) + .with_balance(*DEFAULT_PREFUNDED_ACCOUNT_BALANCE) .generate(); - let (state, storage): (Arc>, Arc>) = - if let Some(forked_url) = config.fork_rpc_url.clone() { - let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(forked_url.clone()))); + let provider: Box = if let Some(forked_url) = + &config.fork_rpc_url + { + let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(forked_url.clone()))); + let forked_chain_id = provider.chain_id().await.unwrap(); - let forked_chain_id = provider.chain_id().await.unwrap(); - let forked_block_id = config - .fork_block_number - .map(BlockId::Number) - .unwrap_or(BlockId::Tag(BlockTag::Latest)); - - let block = provider.get_block_with_tx_hashes(forked_block_id).await.unwrap(); + let forked_block_num = if let Some(num) = config.fork_block_number { + num + } else { + provider + .block_number() + .await + .expect("failed to fetch block number from forked network") + }; - let MaybePendingBlockWithTxHashes::Block(block) = block else { - panic!("block to be forked is a pending block") - }; + let block = + provider.get_block_with_tx_hashes(BlockId::Number(forked_block_num)).await.unwrap(); + let MaybePendingBlockWithTxHashes::Block(block) = block else { + panic!("block to be forked is a pending block") + }; - block_context.block_number = BlockNumber(block.block_number); - block_context.block_timestamp = BlockTimestamp(block.timestamp); - block_context.sequencer_address = - ContractAddress(patricia_key!(block.sequencer_address)); - block_context.chain_id = - ChainId(parse_cairo_short_string(&forked_chain_id).unwrap()); + block_context.block_number = BlockNumber(block.block_number); + block_context.block_timestamp = BlockTimestamp(block.timestamp); + block_context.sequencer_address = ContractAddress(block.sequencer_address).into(); + block_context.chain_id = ChainId(parse_cairo_short_string(&forked_chain_id).unwrap()); - let state = ForkedDb::new(Arc::clone(&provider), forked_block_id); + trace!( + target: "backend", + "forking chain `{}` at block {} from {}", + parse_cairo_short_string(&forked_chain_id).unwrap(), + block.block_number, + forked_url + ); - trace!( - target: "backend", - "forking chain `{}` at block {} from {}", - parse_cairo_short_string(&forked_chain_id).unwrap(), - block.block_number, - forked_url - ); + Box::new(ForkedProvider::new(provider, BlockHashOrNumber::Num(forked_block_num))) + } else { + Box::new(InMemoryProvider::new()) + }; - ( - Arc::new(AsyncRwLock::new(state)), - Arc::new(RwLock::new(Storage::new_forked( - block.block_number, - block.block_hash, - ))), - ) - } else { - ( - Arc::new(AsyncRwLock::new(MemDb::default())), - Arc::new(RwLock::new(Storage::new(&block_context))), - ) - }; + let blockchain = Blockchain::new_with_genesis(provider, &block_context).unwrap(); + let env = Env { block: block_context }; for acc in &accounts { - acc.deploy_and_fund(&mut *state.write().await) + acc.deploy_and_fund(blockchain.provider()) .expect("should be able to deploy and fund dev account"); } - if let Some(ref init_state) = config.init_state { - state - .write() - .await - .load_state(init_state.clone()) - .expect("failed to load initial state"); - info!(target: "backend", "Successfully loaded initial state"); - } - - let blockchain = Blockchain::new(storage); - let states = InMemoryBlockStates::default(); - let env = Env { block: block_context }; - Self { - state, - env: Arc::new(RwLock::new(env)), - config: RwLock::new(config), - states: AsyncRwLock::new(states), + accounts, blockchain, + config: RwLock::new(config), + env: Arc::new(RwLock::new(env)), block_context_generator: RwLock::new(block_context_generator), - accounts, - } - } - - /// Get the current state in a serializable format. - pub async fn serialize_state(&self) -> Result { - self.state.read().await.dump_state().map_err(|_| SequencerError::StateSerialization) - } - - pub async fn dump_state(&self) -> Result, SequencerError> { - let serializable_state = self.serialize_state().await?; - - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder - .write_all(&serde_json::to_vec(&serializable_state).unwrap_or_default()) - .map_err(|_| SequencerError::DataUnavailable)?; - - Ok(encoder.finish().unwrap_or_default()) - } - - pub fn estimate_fee( - &self, - transactions: Vec, - state: StateRefDb, - ) -> Result, TransactionExecutionError> { - let mut state = CachedStateWrapper::new(state); - let block_context = self.env.read().block.clone(); - - let mut estimations = Vec::with_capacity(transactions.len()); - - let results = TransactionExecutor::new(&mut state, &block_context, false, transactions) - .with_error_log() - .execute(); - - for res in results { - let exec_info = res?; - - if exec_info.revert_error.is_some() { - // TEMP: change this once `Reverted` transaction error is no longer `String`. - return Err(TransactionExecutionError::ExecutionError( - EntryPointExecutionError::ExecutionFailed { error_data: vec![] }, - )); - } - - let (l1_gas_usage, vm_resources) = - extract_l1_gas_and_vm_usage(&exec_info.actual_resources); - let l1_gas_by_vm_usage = calculate_l1_gas_by_vm_usage(&block_context, &vm_resources)?; - let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; - - let gas_price = block_context.gas_price as u64; - - estimations.push(FeeEstimate { - gas_consumed: total_l1_gas_usage.ceil() as u64, - gas_price, - overall_fee: total_l1_gas_usage.ceil() as u64 * gas_price, - }) } - - Ok(estimations) } /// Mines a new block based on the provided execution outcome. /// This method should only be called by the /// [IntervalBlockProducer](crate::service::block_producer::IntervalBlockProducer) when the node /// is running in `interval` mining mode. - pub async fn mine_pending_block( + pub fn mine_pending_block( &self, - execution_outcome: ExecutionOutcome, - ) -> (MinedBlockOutcome, StateRefDb) { - let outcome = self.do_mine_block(execution_outcome).await; - let new_state = self.state.read().await.as_ref_db(); + tx_receipt_pairs: Vec<(TxWithHash, Receipt)>, + state_updates: StateUpdatesWithDeclaredClasses, + ) -> (MinedBlockOutcome, Box) { + let block_context = self.env.read().block.clone(); + let outcome = self.do_mine_block(block_context, tx_receipt_pairs, state_updates); + let new_state = StateFactoryProvider::latest(&self.blockchain.provider()).unwrap(); (outcome, new_state) } - /// Updates the block context and mines an empty block. - pub async fn mine_empty_block(&self) -> MinedBlockOutcome { - self.update_block_context(); - self.do_mine_block(ExecutionOutcome::default()).await - } + pub fn do_mine_block( + &self, + block_context: BlockContext, + tx_receipt_pairs: Vec<(TxWithHash, Receipt)>, + state_updates: StateUpdatesWithDeclaredClasses, + ) -> MinedBlockOutcome { + let (txs, receipts): (Vec, Vec) = tx_receipt_pairs.into_iter().unzip(); - pub async fn do_mine_block(&self, execution_outcome: ExecutionOutcome) -> MinedBlockOutcome { - // lock the state for the entire block mining process - let mut state = self.state.write().await; + let prev_hash = BlockHashProvider::latest_hash(self.blockchain.provider()).unwrap(); let partial_header = PartialHeader { - gas_price: self.env.read().block.gas_price, - number: self.env.read().block.block_number.0, - timestamp: self.env.read().block.block_timestamp.0, - parent_hash: self.blockchain.storage.read().latest_hash, - sequencer_address: (*self.env.read().block.sequencer_address.0.key()).into(), + parent_hash: prev_hash, + gas_price: block_context.gas_price, + number: block_context.block_number.0, + timestamp: block_context.block_timestamp.0, + sequencer_address: block_context.sequencer_address.into(), }; - let (valid_txs, outputs): (Vec<_>, Vec<_>) = execution_outcome - .transactions - .iter() - .filter_map(|tx| match tx { - MaybeInvalidExecutedTransaction::Valid(tx) => Some((tx.clone(), tx.output.clone())), - _ => None, - }) - .unzip(); - - let block = Block::new(partial_header, valid_txs, outputs); + let tx_count = txs.len(); + let block_number = partial_header.number; + let header = Header::new(partial_header, FieldElement::ZERO); + let block = Block { header, body: txs }.seal(); + let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }; - let block_number = block.header.number; - let tx_count = block.transactions.len(); - let block_hash = block.header.hash(); - - execution_outcome.transactions.iter().for_each(|tx| { - let (hash, tx) = match tx { - MaybeInvalidExecutedTransaction::Valid(tx) => ( - tx.inner.hash(), - KnownTransaction::Included(IncludedTransaction { - block_number, - block_hash, - transaction: tx.clone(), - finality_status: TransactionFinalityStatus::AcceptedOnL2, - }), - ), - - MaybeInvalidExecutedTransaction::Invalid(tx) => { - (tx.inner.hash(), KnownTransaction::Rejected(tx.clone())) - } - }; - - self.blockchain.storage.write().transactions.insert(hash, tx); - }); - - // store block and the state diff - let state_diff = convert_state_diff_to_rpc_state_diff(execution_outcome.state_diff.clone()); - self.blockchain.append_block(block_hash, block.clone(), state_diff); - // apply the pending state to the current state - execution_outcome.apply_to(&mut *state); - // store the current state - self.states.write().await.insert(block_hash, state.as_ref_db()); + BlockWriter::insert_block_with_states_and_receipts( + self.blockchain.provider(), + block, + state_updates, + receipts, + ) + .unwrap(); info!(target: "backend", "⛏️ Block {block_number} mined with {tx_count} transactions"); - MinedBlockOutcome { block_number, transactions: execution_outcome.transactions } + MinedBlockOutcome { block_number } } pub fn update_block_context(&self) { @@ -313,51 +182,10 @@ impl Backend { block_context.block_timestamp = BlockTimestamp(timestamp); } - pub fn call( - &self, - call: ExternalFunctionCall, - state: impl StateReader, - ) -> Result { - let mut state = CachedState::new(state); - let mut state = CachedState::new(MutRefState::new(&mut state)); - - let call = CallEntryPoint { - calldata: call.calldata, - storage_address: call.contract_address, - entry_point_selector: call.entry_point_selector, - initial_gas: 1000000000, - ..Default::default() - }; - - let res = call.execute( - &mut state, - &mut ExecutionResources::default(), - &mut EntryPointExecutionContext::new( - self.env.read().block.clone(), - AccountTransactionContext::default(), - 1000000000, - ), - ); - - if let Err(err) = &res { - warn!(target: "backend", "Call error: {err:?}"); - } - - res - } - - pub async fn create_empty_block(&self) -> Block { - let parent_hash = self.blockchain.storage.read().latest_hash; - let block_context = &self.env.read().block; - - let partial_header = PartialHeader { - parent_hash, - gas_price: block_context.gas_price, - number: block_context.block_number.0, - timestamp: block_context.block_timestamp.0, - sequencer_address: (*block_context.sequencer_address.0.key()).into(), - }; - - Block::new(partial_header, vec![], vec![]) + /// Updates the block context and mines an empty block. + pub fn mine_empty_block(&self) -> MinedBlockOutcome { + self.update_block_context(); + let block_context = self.env.read().block.clone(); + self.do_mine_block(block_context, Default::default(), Default::default()) } } diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs new file mode 100644 index 0000000000..f6b32b1c6e --- /dev/null +++ b/crates/katana/core/src/backend/storage.rs @@ -0,0 +1,92 @@ +use anyhow::Result; +use blockifier::block_context::BlockContext; +use katana_primitives::block::{ + Block, FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, +}; +use katana_primitives::state::StateUpdatesWithDeclaredClasses; +use katana_provider::traits::block::{BlockProvider, BlockWriter}; +use katana_provider::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use katana_provider::traits::state::{StateFactoryProvider, StateRootProvider, StateWriter}; +use katana_provider::traits::state_update::StateUpdateProvider; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionsProviderExt, +}; +use katana_provider::BlockchainProvider; + +use crate::constants::SEQUENCER_ADDRESS; +use crate::utils::get_genesis_states_for_testing; + +pub trait Database: + BlockProvider + + BlockWriter + + TransactionProvider + + TransactionStatusProvider + + TransactionsProviderExt + + ReceiptProvider + + StateUpdateProvider + + StateRootProvider + + ContractInfoProvider + + StateWriter + + ContractClassWriter + + StateFactoryProvider + + 'static + + Send + + Sync +{ +} + +impl Database for T where + T: BlockProvider + + BlockWriter + + TransactionProvider + + TransactionStatusProvider + + TransactionsProviderExt + + ReceiptProvider + + StateUpdateProvider + + ContractInfoProvider + + StateRootProvider + + StateWriter + + ContractClassWriter + + StateFactoryProvider + + 'static + + Send + + Sync +{ +} + +pub struct Blockchain { + inner: BlockchainProvider>, +} + +impl Blockchain { + pub fn new(provider: impl Database) -> Self { + Self { inner: BlockchainProvider::new(Box::new(provider)) } + } + + pub fn provider(&self) -> &BlockchainProvider> { + &self.inner + } + + pub fn new_with_genesis(provider: impl Database, block_context: &BlockContext) -> Result { + let header = PartialHeader { + gas_price: block_context.gas_price, + number: 0, + parent_hash: 0u8.into(), + timestamp: block_context.block_timestamp.0, + sequencer_address: *SEQUENCER_ADDRESS, + }; + + let block = Block { header: Header::new(header, 0u8.into()), body: vec![] }.seal(); + Self::new_with_block_and_state(provider, block, get_genesis_states_for_testing()) + } + + fn new_with_block_and_state( + provider: impl Database, + block: SealedBlock, + states: StateUpdatesWithDeclaredClasses, + ) -> Result { + let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL1 }; + BlockWriter::insert_block_with_states_and_receipts(&provider, block, states, vec![])?; + Ok(Self::new(provider)) + } +} diff --git a/crates/katana/core/src/backend/storage/block.rs b/crates/katana/core/src/backend/storage/block.rs deleted file mode 100644 index 4d16aadc0c..0000000000 --- a/crates/katana/core/src/backend/storage/block.rs +++ /dev/null @@ -1,221 +0,0 @@ -use std::sync::Arc; - -use starknet::core::crypto::compute_hash_on_elements; -use starknet::core::types::{ - BlockStatus as RpcBlockStatus, BlockWithTxHashes, BlockWithTxs, FieldElement, - MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, PendingBlockWithTxHashes, - PendingBlockWithTxs, -}; - -use super::transaction::TransactionOutput; -use crate::execution::ExecutedTransaction; -use crate::utils::transaction::api_to_rpc_transaction; - -#[derive(Debug, Clone, Copy)] -pub enum BlockStatus { - Rejected, - AcceptedOnL2, - AcceptedOnL1, -} - -#[derive(Debug, Clone)] -pub struct PartialHeader { - pub parent_hash: FieldElement, - pub number: u64, - pub gas_price: u128, - pub timestamp: u64, - pub sequencer_address: FieldElement, -} - -#[derive(Debug, Clone)] -pub struct Header { - pub parent_hash: FieldElement, - pub number: u64, - pub gas_price: u128, - pub timestamp: u64, - pub state_root: FieldElement, - pub sequencer_address: FieldElement, -} - -impl Header { - pub fn new(partial_header: PartialHeader, state_root: FieldElement) -> Self { - Self { - state_root, - number: partial_header.number, - gas_price: partial_header.gas_price, - timestamp: partial_header.timestamp, - parent_hash: partial_header.parent_hash, - sequencer_address: partial_header.sequencer_address, - } - } - - pub fn hash(&self) -> FieldElement { - compute_hash_on_elements(&vec![ - self.number.into(), // block number - self.state_root, // state root - self.sequencer_address, // sequencer address - self.timestamp.into(), // block timestamp - FieldElement::ZERO, // transaction commitment - FieldElement::ZERO, // event commitment - FieldElement::ZERO, // protocol version - FieldElement::ZERO, // extra data - self.parent_hash, // parent hash - ]) - } -} - -#[derive(Debug, Clone)] -pub enum ExecutedBlock { - Pending(PartialBlock), - Included(Block), -} - -impl ExecutedBlock { - pub fn transaction_count(&self) -> usize { - match self { - Self::Pending(block) => block.transactions.len(), - Self::Included(block) => block.transactions.len(), - } - } - - pub fn transactions(&self) -> &[Arc] { - match self { - Self::Pending(block) => &block.transactions, - Self::Included(block) => &block.transactions, - } - } -} - -#[derive(Debug, Clone)] -pub struct PartialBlock { - pub header: PartialHeader, - pub transactions: Vec>, - pub outputs: Vec, -} - -#[derive(Debug, Clone)] -pub struct Block { - pub header: Header, - pub status: BlockStatus, - pub transactions: Vec>, - pub outputs: Vec, -} - -impl Block { - pub fn new( - partial_header: PartialHeader, - transactions: Vec>, - outputs: Vec, - ) -> Self { - // TODO: compute state root - let state_root = FieldElement::ZERO; - - Self { - header: Header::new(partial_header, state_root), - status: BlockStatus::AcceptedOnL2, - transactions, - outputs, - } - } -} - -impl From for ExecutedBlock { - fn from(value: PartialBlock) -> Self { - Self::Pending(value) - } -} - -impl From for ExecutedBlock { - fn from(value: Block) -> Self { - Self::Included(value) - } -} - -impl From for RpcBlockStatus { - fn from(value: BlockStatus) -> Self { - match value { - BlockStatus::Rejected => Self::Rejected, - BlockStatus::AcceptedOnL2 => Self::AcceptedOnL2, - BlockStatus::AcceptedOnL1 => Self::AcceptedOnL1, - } - } -} - -impl From for BlockWithTxs { - fn from(value: Block) -> Self { - Self { - status: value.status.into(), - block_hash: value.header.hash(), - block_number: value.header.number, - new_root: value.header.state_root, - timestamp: value.header.timestamp, - parent_hash: value.header.parent_hash, - sequencer_address: value.header.sequencer_address, - transactions: value - .transactions - .into_iter() - .map(|t| api_to_rpc_transaction(t.inner.clone().into())) - .collect(), - } - } -} - -impl From for MaybePendingBlockWithTxs { - fn from(value: ExecutedBlock) -> Self { - match value { - ExecutedBlock::Included(block) => MaybePendingBlockWithTxs::Block(BlockWithTxs { - status: block.status.into(), - block_hash: block.header.hash(), - block_number: block.header.number, - new_root: block.header.state_root, - timestamp: block.header.timestamp, - parent_hash: block.header.parent_hash, - sequencer_address: block.header.sequencer_address, - transactions: block - .transactions - .into_iter() - .map(|t| api_to_rpc_transaction(t.inner.clone().into())) - .collect(), - }), - ExecutedBlock::Pending(block) => { - MaybePendingBlockWithTxs::PendingBlock(PendingBlockWithTxs { - timestamp: block.header.timestamp, - parent_hash: block.header.parent_hash, - sequencer_address: block.header.sequencer_address, - transactions: block - .transactions - .into_iter() - .map(|t| api_to_rpc_transaction(t.inner.clone().into())) - .collect(), - }) - } - } - } -} - -impl From for MaybePendingBlockWithTxHashes { - fn from(value: ExecutedBlock) -> Self { - match value { - ExecutedBlock::Included(block) => { - MaybePendingBlockWithTxHashes::Block(BlockWithTxHashes { - status: block.status.into(), - block_hash: block.header.hash(), - block_number: block.header.number, - new_root: block.header.state_root, - timestamp: block.header.timestamp, - parent_hash: block.header.parent_hash, - sequencer_address: block.header.sequencer_address, - transactions: block.transactions.into_iter().map(|t| t.inner.hash()).collect(), - }) - } - ExecutedBlock::Pending(block) => { - MaybePendingBlockWithTxHashes::PendingBlock(PendingBlockWithTxHashes { - timestamp: block.header.timestamp, - parent_hash: block.header.parent_hash, - sequencer_address: block.header.sequencer_address, - transactions: block.transactions.into_iter().map(|t| t.inner.hash()).collect(), - }) - } - } - } -} diff --git a/crates/katana/core/src/backend/storage/mod.rs b/crates/katana/core/src/backend/storage/mod.rs deleted file mode 100644 index a166103da3..0000000000 --- a/crates/katana/core/src/backend/storage/mod.rs +++ /dev/null @@ -1,227 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::sync::Arc; - -use blockifier::block_context::BlockContext; -use parking_lot::RwLock; -use starknet::core::types::{BlockId, BlockTag, FieldElement, StateDiff, StateUpdate}; - -use self::block::Block; -use self::transaction::KnownTransaction; -use crate::backend::storage::block::PartialHeader; -use crate::db::StateRefDb; - -pub mod block; -pub mod transaction; - -const DEFAULT_HISTORY_LIMIT: usize = 500; -const MIN_HISTORY_LIMIT: usize = 10; - -/// Represents the complete state of a single block -pub struct InMemoryBlockStates { - /// The states at a certain block - states: HashMap, - /// How many states to store at most - in_memory_limit: usize, - /// minimum amount of states we keep in memory - min_in_memory_limit: usize, - /// all states present, used to enforce `in_memory_limit` - present: VecDeque, -} - -impl InMemoryBlockStates { - pub fn new(limit: usize) -> Self { - Self { - states: Default::default(), - in_memory_limit: limit, - min_in_memory_limit: limit.min(MIN_HISTORY_LIMIT), - present: Default::default(), - } - } - - /// Returns the state for the given `hash` if present - pub fn get(&self, hash: &FieldElement) -> Option<&StateRefDb> { - self.states.get(hash) - } - - /// Inserts a new (hash -> state) pair - /// - /// When the configured limit for the number of states that can be stored in memory is reached, - /// the oldest state is removed. - /// - /// Since we keep a snapshot of the entire state as history, the size of the state will increase - /// with the transactions processed. To counter this, we gradually decrease the cache limit with - /// the number of states/blocks until we reached the `min_limit`. - pub fn insert(&mut self, hash: FieldElement, state: StateRefDb) { - if self.present.len() >= self.in_memory_limit { - // once we hit the max limit we gradually decrease it - self.in_memory_limit = - self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit); - } - - self.enforce_limits(); - self.states.insert(hash, state); - self.present.push_back(hash); - } - - /// Enforces configured limits - fn enforce_limits(&mut self) { - // enforce memory limits - while self.present.len() >= self.in_memory_limit { - // evict the oldest block in memory - if let Some(hash) = self.present.pop_front() { - self.states.remove(&hash); - } - } - } -} - -impl Default for InMemoryBlockStates { - fn default() -> Self { - // enough in memory to store `DEFAULT_HISTORY_LIMIT` blocks in memory - Self::new(DEFAULT_HISTORY_LIMIT) - } -} - -#[derive(Debug, Default)] -pub struct Storage { - /// Mapping from block hash -> block - pub blocks: HashMap, - /// Mapping from block number -> block hash - pub hashes: HashMap, - /// Mapping from block number -> state update - pub state_update: HashMap, - /// The latest block hash - pub latest_hash: FieldElement, - /// The latest block number - pub latest_number: u64, - /// Mapping of all known transactions from its transaction hash - pub transactions: HashMap, -} - -impl Storage { - /// Creates a new blockchain from a genesis block - pub fn new(block_context: &BlockContext) -> Self { - let partial_header = PartialHeader { - parent_hash: FieldElement::ZERO, - gas_price: block_context.gas_price, - number: block_context.block_number.0, - timestamp: block_context.block_timestamp.0, - sequencer_address: (*block_context.sequencer_address.0.key()).into(), - }; - - // Create a dummy genesis block - let genesis_block = Block::new(partial_header, vec![], vec![]); - let genesis_hash = genesis_block.header.hash(); - let genesis_number = 0u64; - - Self { - blocks: HashMap::from([(genesis_hash, genesis_block)]), - hashes: HashMap::from([(genesis_number, genesis_hash)]), - latest_hash: genesis_hash, - latest_number: genesis_number, - state_update: HashMap::default(), - transactions: HashMap::default(), - } - } - - /// Creates a new blockchain from a forked network - pub fn new_forked(latest_number: u64, latest_hash: FieldElement) -> Self { - Self { - latest_hash, - latest_number, - blocks: HashMap::default(), - hashes: HashMap::from([(latest_number, latest_hash)]), - state_update: HashMap::default(), - transactions: HashMap::default(), - } - } - - pub fn block_by_number(&self, number: u64) -> Option<&Block> { - self.hashes.get(&number).and_then(|hash| self.blocks.get(hash)) - } -} - -pub struct Blockchain { - pub storage: Arc>, -} - -impl Blockchain { - pub fn new(storage: Arc>) -> Self { - Self { storage } - } - - pub fn new_forked(latest_number: u64, latest_hash: FieldElement) -> Self { - Self::new(Arc::new(RwLock::new(Storage::new_forked(latest_number, latest_hash)))) - } - - /// Returns the block hash based on the block id - pub fn block_hash(&self, block: BlockId) -> Option { - match block { - BlockId::Tag(BlockTag::Pending) => None, - BlockId::Tag(BlockTag::Latest) => Some(self.storage.read().latest_hash), - BlockId::Hash(hash) => Some(hash), - BlockId::Number(num) => self.storage.read().hashes.get(&num).copied(), - } - } - - pub fn total_blocks(&self) -> usize { - self.storage.read().blocks.len() - } - - /// Appends a new block to the chain and store the state diff. - pub fn append_block(&self, hash: FieldElement, block: Block, state_diff: StateDiff) { - let number = block.header.number; - let mut storage = self.storage.write(); - - assert_eq!(storage.latest_number + 1, number); - - let old_root = storage - .blocks - .get(&storage.latest_hash) - .map(|b| b.header.state_root) - .unwrap_or_default(); - - let state_update = StateUpdate { - block_hash: hash, - new_root: block.header.state_root, - old_root, - state_diff, - }; - - storage.latest_hash = hash; - storage.latest_number = number; - storage.blocks.insert(hash, block); - storage.hashes.insert(number, hash); - storage.state_update.insert(hash, state_update); - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::*; - use crate::backend::in_memory_db::MemDb; - - #[test] - fn remove_old_state_when_limit_is_reached() { - let mut in_memory_state = InMemoryBlockStates::new(2); - - in_memory_state - .insert(FieldElement::from_str("0x1").unwrap(), StateRefDb::new(MemDb::new())); - in_memory_state - .insert(FieldElement::from_str("0x2").unwrap(), StateRefDb::new(MemDb::new())); - - assert!(in_memory_state.states.get(&FieldElement::from_str("0x1").unwrap()).is_some()); - assert!(in_memory_state.states.get(&FieldElement::from_str("0x2").unwrap()).is_some()); - assert_eq!(in_memory_state.present.len(), 2); - - in_memory_state - .insert(FieldElement::from_str("0x3").unwrap(), StateRefDb::new(MemDb::new())); - - assert_eq!(in_memory_state.present.len(), 2); - assert!(in_memory_state.states.get(&FieldElement::from_str("0x1").unwrap()).is_none()); - assert!(in_memory_state.states.get(&FieldElement::from_str("0x2").unwrap()).is_some()); - assert!(in_memory_state.states.get(&FieldElement::from_str("0x3").unwrap()).is_some()); - } -} diff --git a/crates/katana/core/src/backend/storage/transaction.rs b/crates/katana/core/src/backend/storage/transaction.rs deleted file mode 100644 index dbdf27a88c..0000000000 --- a/crates/katana/core/src/backend/storage/transaction.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::sync::Arc; - -use blockifier::execution::contract_class::ContractClass; -use blockifier::transaction::account_transaction::AccountTransaction; -use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; -use blockifier::transaction::transactions::{ - DeclareTransaction as ExecutionDeclareTransaction, - DeployAccountTransaction as ExecutionDeployAccountTransaction, - L1HandlerTransaction as ExecutionL1HandlerTransaction, -}; -use starknet::core::types::{ - DeclareTransactionReceipt, DeployAccountTransactionReceipt, Event, FieldElement, - FlattenedSierraClass, InvokeTransactionReceipt, L1HandlerTransactionReceipt, MsgToL1, - PendingDeclareTransactionReceipt, PendingDeployAccountTransactionReceipt, - PendingInvokeTransactionReceipt, PendingL1HandlerTransactionReceipt, - PendingTransactionReceipt as RpcPendingTransactionReceipt, Transaction as RpcTransaction, - TransactionFinalityStatus, TransactionReceipt as RpcTransactionReceipt, -}; -use starknet_api::core::{ContractAddress, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; -use starknet_api::transaction::{ - DeclareTransaction as ApiDeclareTransaction, - DeployAccountTransaction as ApiDeployAccountTransaction, Fee, - InvokeTransaction as ApiInvokeTransaction, L1HandlerTransaction as ApiL1HandlerTransaction, - Transaction as ApiTransaction, -}; - -use crate::execution::ExecutedTransaction; -use crate::utils::transaction::api_to_rpc_transaction; - -/// Represents all transactions that are known to the sequencer. -#[derive(Debug, Clone)] -pub enum KnownTransaction { - Pending(PendingTransaction), - Included(IncludedTransaction), - Rejected(Arc), -} - -impl KnownTransaction { - pub fn is_rejected(&self) -> bool { - matches!(self, KnownTransaction::Rejected(_)) - } - - pub fn is_pending(&self) -> bool { - matches!(self, KnownTransaction::Pending(_)) - } - - pub fn is_included(&self) -> bool { - matches!(self, KnownTransaction::Included(_)) - } -} - -#[derive(Debug, Clone)] -pub struct PendingTransaction(pub Arc); - -/// A transaction that is known to be included in a block. Which also includes -/// reverted transactions and transactions that are currently in the `pending` block. -#[derive(Debug, Clone)] -pub struct IncludedTransaction { - pub block_number: u64, - pub block_hash: FieldElement, - pub transaction: Arc, - pub finality_status: TransactionFinalityStatus, -} - -/// A transaction that is known to be rejected by the sequencer i.e., -/// transaction that didn't pass the validation logic. -#[derive(Debug, Clone)] -pub struct RejectedTransaction { - pub inner: Transaction, - pub execution_error: String, -} - -#[derive(Debug, Clone)] -pub struct TransactionOutput { - pub actual_fee: u128, - pub events: Vec, - pub messages_sent: Vec, -} - -#[derive(Debug, Clone)] -pub enum Transaction { - Invoke(InvokeTransaction), - Declare(DeclareTransaction), - DeployAccount(DeployAccountTransaction), - L1Handler(L1HandlerTransaction), -} - -impl Transaction { - pub fn hash(&self) -> FieldElement { - match self { - Transaction::Invoke(tx) => tx.0.transaction_hash().0.into(), - Transaction::Declare(tx) => tx.inner.transaction_hash().0.into(), - Transaction::L1Handler(tx) => tx.inner.transaction_hash.0.into(), - Transaction::DeployAccount(tx) => tx.inner.transaction_hash.0.into(), - } - } -} - -#[derive(Debug, Clone)] -pub struct InvokeTransaction(pub ApiInvokeTransaction); - -#[derive(Debug, Clone)] -pub struct DeclareTransaction { - pub inner: ApiDeclareTransaction, - pub compiled_class: ContractClass, - pub sierra_class: Option, -} - -#[derive(Debug, Clone)] -pub struct DeployAccountTransaction { - pub inner: ApiDeployAccountTransaction, - pub contract_address: FieldElement, -} - -#[derive(Debug, Clone)] -pub struct L1HandlerTransaction { - pub inner: ApiL1HandlerTransaction, - pub paid_l1_fee: u128, -} - -impl IncludedTransaction { - pub fn receipt(&self) -> RpcTransactionReceipt { - match &self.transaction.inner { - Transaction::Invoke(_) => RpcTransactionReceipt::Invoke(InvokeTransactionReceipt { - block_hash: self.block_hash, - block_number: self.block_number, - finality_status: self.finality_status, - events: self.transaction.output.events.clone(), - execution_result: self.transaction.execution_result(), - messages_sent: self.transaction.output.messages_sent.clone(), - transaction_hash: self.transaction.inner.hash(), - actual_fee: self.transaction.execution_info.actual_fee.0.into(), - }), - - Transaction::Declare(_) => RpcTransactionReceipt::Declare(DeclareTransactionReceipt { - block_hash: self.block_hash, - block_number: self.block_number, - finality_status: self.finality_status, - events: self.transaction.output.events.clone(), - transaction_hash: self.transaction.inner.hash(), - execution_result: self.transaction.execution_result(), - messages_sent: self.transaction.output.messages_sent.clone(), - actual_fee: self.transaction.execution_info.actual_fee.0.into(), - }), - - Transaction::DeployAccount(tx) => { - RpcTransactionReceipt::DeployAccount(DeployAccountTransactionReceipt { - block_hash: self.block_hash, - block_number: self.block_number, - contract_address: tx.contract_address, - finality_status: self.finality_status, - events: self.transaction.output.events.clone(), - transaction_hash: self.transaction.inner.hash(), - execution_result: self.transaction.execution_result(), - messages_sent: self.transaction.output.messages_sent.clone(), - actual_fee: self.transaction.execution_info.actual_fee.0.into(), - }) - } - - Transaction::L1Handler(_) => { - RpcTransactionReceipt::L1Handler(L1HandlerTransactionReceipt { - block_hash: self.block_hash, - block_number: self.block_number, - finality_status: self.finality_status, - events: self.transaction.output.events.clone(), - transaction_hash: self.transaction.inner.hash(), - execution_result: self.transaction.execution_result(), - messages_sent: self.transaction.output.messages_sent.clone(), - actual_fee: self.transaction.execution_info.actual_fee.0.into(), - }) - } - } - } -} - -impl PendingTransaction { - pub fn receipt(&self) -> RpcPendingTransactionReceipt { - match &self.0.inner { - Transaction::Invoke(_) => { - RpcPendingTransactionReceipt::Invoke(PendingInvokeTransactionReceipt { - events: self.0.output.events.clone(), - transaction_hash: self.0.inner.hash(), - execution_result: self.0.execution_result(), - messages_sent: self.0.output.messages_sent.clone(), - actual_fee: self.0.execution_info.actual_fee.0.into(), - }) - } - - Transaction::Declare(_) => { - RpcPendingTransactionReceipt::Declare(PendingDeclareTransactionReceipt { - events: self.0.output.events.clone(), - transaction_hash: self.0.inner.hash(), - execution_result: self.0.execution_result(), - messages_sent: self.0.output.messages_sent.clone(), - actual_fee: self.0.execution_info.actual_fee.0.into(), - }) - } - - Transaction::DeployAccount(_) => RpcPendingTransactionReceipt::DeployAccount( - PendingDeployAccountTransactionReceipt { - events: self.0.output.events.clone(), - transaction_hash: self.0.inner.hash(), - execution_result: self.0.execution_result(), - messages_sent: self.0.output.messages_sent.clone(), - actual_fee: self.0.execution_info.actual_fee.0.into(), - }, - ), - - Transaction::L1Handler(_) => { - RpcPendingTransactionReceipt::L1Handler(PendingL1HandlerTransactionReceipt { - events: self.0.output.events.clone(), - transaction_hash: self.0.inner.hash(), - execution_result: self.0.execution_result(), - messages_sent: self.0.output.messages_sent.clone(), - actual_fee: self.0.execution_info.actual_fee.0.into(), - }) - } - } - } -} - -impl From for KnownTransaction { - fn from(transaction: PendingTransaction) -> Self { - KnownTransaction::Pending(transaction) - } -} - -impl From for KnownTransaction { - fn from(transaction: IncludedTransaction) -> Self { - KnownTransaction::Included(transaction) - } -} - -impl From for KnownTransaction { - fn from(transaction: RejectedTransaction) -> Self { - KnownTransaction::Rejected(Arc::new(transaction)) - } -} - -impl From for RpcTransaction { - fn from(transaction: KnownTransaction) -> Self { - match transaction { - KnownTransaction::Pending(tx) => api_to_rpc_transaction(tx.0.inner.clone().into()), - KnownTransaction::Rejected(tx) => api_to_rpc_transaction(tx.inner.clone().into()), - KnownTransaction::Included(tx) => { - api_to_rpc_transaction(tx.transaction.inner.clone().into()) - } - } - } -} - -impl From for ApiTransaction { - fn from(value: Transaction) -> Self { - match value { - Transaction::Invoke(tx) => ApiTransaction::Invoke(tx.0), - Transaction::Declare(tx) => ApiTransaction::Declare(tx.inner), - Transaction::L1Handler(tx) => ApiTransaction::L1Handler(tx.inner), - Transaction::DeployAccount(tx) => ApiTransaction::DeployAccount(tx.inner), - } - } -} - -impl From for ExecutionTransaction { - fn from(value: Transaction) -> Self { - match value { - Transaction::Invoke(tx) => { - ExecutionTransaction::AccountTransaction(AccountTransaction::Invoke(tx.0)) - } - - Transaction::Declare(tx) => { - ExecutionTransaction::AccountTransaction(AccountTransaction::Declare( - ExecutionDeclareTransaction::new(tx.inner, tx.compiled_class) - .expect("declare tx must have valid compiled class"), - )) - } - - Transaction::DeployAccount(tx) => ExecutionTransaction::AccountTransaction( - AccountTransaction::DeployAccount(ExecutionDeployAccountTransaction { - tx: tx.inner, - contract_address: ContractAddress(patricia_key!(tx.contract_address)), - }), - ), - - Transaction::L1Handler(tx) => { - ExecutionTransaction::L1HandlerTransaction(ExecutionL1HandlerTransaction { - tx: tx.inner, - paid_fee_on_l1: Fee(tx.paid_l1_fee), - }) - } - } - } -} diff --git a/crates/katana/core/src/constants.rs b/crates/katana/core/src/constants.rs index 6e8da0982a..16522dc652 100644 --- a/crates/katana/core/src/constants.rs +++ b/crates/katana/core/src/constants.rs @@ -1,41 +1,49 @@ -use blockifier::execution::contract_class::ContractClass; +use katana_primitives::contract::{ + CompiledContractClass, CompiledContractClassV0, ContractAddress, StorageKey, +}; +use katana_primitives::FieldElement; use lazy_static::lazy_static; -use starknet_api::hash::StarkFelt; -use starknet_api::stark_felt; -use starknet_api::state::StorageKey; - -use crate::utils::contract::get_contract_class; +use starknet::macros::felt; pub const DEFAULT_GAS_PRICE: u128 = 100 * u128::pow(10, 9); // Given in units of wei. pub const DEFAULT_INVOKE_MAX_STEPS: u32 = 1_000_000; pub const DEFAULT_VALIDATE_MAX_STEPS: u32 = 1_000_000; +fn parse_legacy_contract_class(content: impl AsRef) -> CompiledContractClass { + let class: CompiledContractClassV0 = serde_json::from_str(content.as_ref()).unwrap(); + CompiledContractClass::V0(class) +} + lazy_static! { // Predefined contract addresses - pub static ref SEQUENCER_ADDRESS: StarkFelt = stark_felt!("0x69420"); - pub static ref UDC_ADDRESS: StarkFelt = stark_felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"); - pub static ref FEE_TOKEN_ADDRESS: StarkFelt = stark_felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"); + pub static ref SEQUENCER_ADDRESS: ContractAddress = ContractAddress(felt!("0x1")); + pub static ref UDC_ADDRESS: ContractAddress = ContractAddress(felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf")); + pub static ref FEE_TOKEN_ADDRESS: ContractAddress = ContractAddress(felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")); // Predefined class hashes - pub static ref DEFAULT_ACCOUNT_CONTRACT_CLASS_HASH: StarkFelt = stark_felt!("0x04d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f"); - pub static ref ERC20_CONTRACT_CLASS_HASH: StarkFelt = stark_felt!("0x02a8846878b6ad1f54f6ba46f5f40e11cee755c677f130b2c4b60566c9003f1f"); - pub static ref UDC_CLASS_HASH: StarkFelt = stark_felt!("0x07b3e05f48f0c69e4a65ce5e076a66271a527aff2c34ce1083ec6e1526997a69"); + pub static ref OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH: FieldElement = felt!("0x04d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f"); + pub static ref ERC20_CONTRACT_CLASS_HASH: FieldElement = felt!("0x02a8846878b6ad1f54f6ba46f5f40e11cee755c677f130b2c4b60566c9003f1f"); + pub static ref UDC_CLASS_HASH: FieldElement = felt!("0x07b3e05f48f0c69e4a65ce5e076a66271a527aff2c34ce1083ec6e1526997a69"); + + pub static ref OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH: FieldElement = felt!("0x04d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f"); + pub static ref ERC20_CONTRACT_COMPILED_CLASS_HASH: FieldElement = felt!("0x02a8846878b6ad1f54f6ba46f5f40e11cee755c677f130b2c4b60566c9003f1f"); + pub static ref UDC_COMPILED_CLASS_HASH: FieldElement = felt!("0x07b3e05f48f0c69e4a65ce5e076a66271a527aff2c34ce1083ec6e1526997a69"); // Predefined contract classes - pub static ref ERC20_CONTRACT: ContractClass = get_contract_class(include_str!("../contracts/compiled/erc20.json")); - pub static ref UDC_CONTRACT: ContractClass = get_contract_class(include_str!("../contracts/compiled/universal_deployer.json")); - pub static ref DEFAULT_ACCOUNT_CONTRACT: ContractClass = get_contract_class(include_str!("../contracts/compiled/account.json")); + pub static ref ERC20_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/erc20.json")); + pub static ref UDC_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/universal_deployer.json")); + pub static ref OZ_V0_ACCOUNT_CONTRACT: CompiledContractClass = parse_legacy_contract_class(include_str!("../contracts/compiled/account.json")); - pub static ref DEFAULT_PREFUNDED_ACCOUNT_BALANCE: StarkFelt = stark_felt!("0x3635c9adc5dea00000"); // 10^21 + pub static ref DEFAULT_PREFUNDED_ACCOUNT_BALANCE: FieldElement = felt!("0x3635c9adc5dea00000"); // 10^21 // Storage slots - pub static ref ERC20_NAME_STORAGE_SLOT: StorageKey = stark_felt!("0x0341c1bdfd89f69748aa00b5742b03adbffd79b8e80cab5c50d91cd8c2a79be1").try_into().unwrap(); - pub static ref ERC20_SYMBOL_STORAGE_SLOT: StorageKey = stark_felt!("0x00b6ce5410fca59d078ee9b2a4371a9d684c530d697c64fbef0ae6d5e8f0ac72").try_into().unwrap(); - pub static ref ERC20_DECIMALS_STORAGE_SLOT: StorageKey = stark_felt!("0x01f0d4aa99431d246bac9b8e48c33e888245b15e9678f64f9bdfc8823dc8f979").try_into().unwrap(); + pub static ref ERC20_NAME_STORAGE_SLOT: StorageKey = felt!("0x0341c1bdfd89f69748aa00b5742b03adbffd79b8e80cab5c50d91cd8c2a79be1"); + pub static ref ERC20_SYMBOL_STORAGE_SLOT: StorageKey = felt!("0x00b6ce5410fca59d078ee9b2a4371a9d684c530d697c64fbef0ae6d5e8f0ac72"); + pub static ref ERC20_DECIMALS_STORAGE_SLOT: StorageKey = felt!("0x01f0d4aa99431d246bac9b8e48c33e888245b15e9678f64f9bdfc8823dc8f979"); } diff --git a/crates/katana/core/src/db/cached.rs b/crates/katana/core/src/db/cached.rs deleted file mode 100644 index 164caa5992..0000000000 --- a/crates/katana/core/src/db/cached.rs +++ /dev/null @@ -1,597 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::{CachedState, CommitmentStateDiff}; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::core::types::FlattenedSierraClass; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; -use tokio::sync::RwLock as AsyncRwLock; -use tracing::trace; - -use super::{AsStateRefDb, StateExt, StateExtRef, StateRefDb}; - -#[derive(Clone, Debug, Default)] -pub struct StorageRecord { - pub nonce: Nonce, - pub storage: HashMap, -} - -#[derive(Clone, Debug)] -pub struct ClassRecord { - /// The compiled contract class. - pub class: ContractClass, - /// The hash of a compiled Sierra class (if the class is a Sierra class, otherwise - /// for legacy contract, it is the same as the class hash). - pub compiled_hash: CompiledClassHash, -} - -/// A cached state database which fallbacks to an inner database if the data -/// is not found in the cache. -/// -/// The data that has been fetched from the inner database is stored in the cache. -#[derive(Clone, Debug)] -pub struct CachedDb { - /// A map of class hash to its class definition. - pub classes: HashMap, - /// A map of contract address to its class hash. - pub contracts: HashMap, - /// A map of contract address to the contract information. - pub storage: HashMap, - /// A map of class hash to its Sierra class definition (if any). - pub sierra_classes: HashMap, - /// Inner database to fallback to when the data is not found in the cache. - pub db: Db, -} - -impl CachedDb { - /// Construct a new [CachedDb] with an inner database. - pub fn new(db: Db) -> Self { - Self { - db, - classes: HashMap::new(), - storage: HashMap::new(), - contracts: HashMap::new(), - sierra_classes: HashMap::new(), - } - } -} - -impl State for CachedDb -where - Db: StateExtRef, -{ - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.storage.entry(contract_address).or_default().storage.insert(key, value); - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - if contract_address == ContractAddress::default() { - return Err(StateError::OutOfRangeContractAddress); - } - self.contracts.insert(contract_address, class_hash); - Ok(()) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - self.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); - Ok(()) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - self.classes.insert( - *class_hash, - ClassRecord { class: contract_class, compiled_hash: CompiledClassHash(class_hash.0) }, - ); - Ok(()) - } - - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - let current_nonce = if let Ok(nonce) = self.get_nonce_at(contract_address) { - nonce - } else { - self.db.get_nonce_at(contract_address)? - }; - - let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; - let next_nonce_val = 1_u64 + current_nonce_as_u64; - let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); - self.storage.entry(contract_address).or_default().nonce = next_nonce; - Ok(()) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - unreachable!("to_state_diff should not be called on CachedDb") - } -} - -impl StateReader for CachedDb -where - Db: StateExtRef, -{ - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - if let Some(value) = self.storage.get(&contract_address).and_then(|r| r.storage.get(&key)) { - return Ok(*value); - } - - trace!(target: "cacheddb", "cache miss for storage at address {} index {}", contract_address.0.key(), key.0.key()); - - match self.db.get_storage_at(contract_address, key) { - Ok(value) => { - trace!(target: "cacheddb", "caching storage at address {} index {}", contract_address.0.key(), key.0.key()); - self.set_storage_at(contract_address, key, value); - Ok(value) - } - Err(err) => Err(err), - } - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - if let Some(nonce) = self.storage.get(&contract_address).map(|r| r.nonce) { - return Ok(nonce); - } - - trace!(target: "cached_db", "cache miss for nonce at {}", contract_address.0.key()); - - match self.db.get_nonce_at(contract_address) { - Ok(nonce) => { - trace!(target: "cached_db", "caching nonce at {}", contract_address.0.key()); - self.storage.entry(contract_address).or_default().nonce = nonce; - Ok(nonce) - } - Err(err) => Err(err), - } - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - if let Some(class) = self.classes.get(class_hash).map(|r| r.class.clone()) { - return Ok(class); - } - - trace!(target: "cached_db", "cache miss for compiled contract class {class_hash}"); - - match self.db.get_compiled_contract_class(class_hash) { - Ok(class) => { - trace!(target: "cached_db", "caching compiled contract class {class_hash}"); - self.set_contract_class(class_hash, class.clone())?; - Ok(class) - } - Err(err) => Err(err), - } - } - - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - if let Some(class_hash) = self.contracts.get(&contract_address).cloned() { - return Ok(class_hash); - } - - trace!(target: "cached_db", "cache miss for class hash at address {}", contract_address.0.key()); - - match self.db.get_class_hash_at(contract_address) { - Ok(class_hash) => { - trace!(target: "cached_db", "caching class hash at address {}", contract_address.0.key()); - self.set_class_hash_at(contract_address, class_hash)?; - Ok(class_hash) - } - Err(err) => Err(err), - } - } - - fn get_compiled_class_hash( - &mut self, - class_hash: ClassHash, - ) -> StateResult { - if let Some(hash) = self.classes.get(&class_hash).map(|r| r.compiled_hash) { - return Ok(hash); - } - - trace!(target: "cached_db", "cache miss for compiled class hash {class_hash}"); - - match self.db.get_compiled_class_hash(class_hash) { - Ok(hash) => { - trace!(target: "cached_db", "caching compiled class hash {class_hash}",); - self.set_compiled_class_hash(class_hash, hash)?; - Ok(hash) - } - Err(err) => Err(err), - } - } -} - -impl StateExt for CachedDb -where - Db: StateExtRef, -{ - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - // check the class hash must not be a legacy contract - if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - self.sierra_classes.insert(class_hash, sierra_class); - Ok(()) - } -} - -impl StateExtRef for CachedDb -where - Db: StateExtRef, -{ - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - if let Some(class) = self.sierra_classes.get(class_hash).cloned() { - return Ok(class); - } - - trace!(target: "cached_db", "cache miss for sierra class {class_hash}"); - - match self.db.get_sierra_class(class_hash) { - Ok(class) => { - trace!(target: "cached_db", "caching sierra class {class_hash}"); - self.set_sierra_class(*class_hash, class.clone())?; - Ok(class) - } - Err(err) => Err(err), - } - } -} - -pub type AsCachedDb = CachedDb<()>; - -/// A helper trait for getting a [CachedDb] from a database implementation. -/// -/// **THIS IS JUST A TEMPORARY SOLUTION THEREFORE NOT MUCH THOUGHT HAS BEEN PUT INTO IT. DO NOT RELY -/// ON THIS TRAIT AS IT MAY BE REMOVED IN THE FUTURE**. -pub trait MaybeAsCachedDb { - /// Returns the inner [CachedDb] if it exists. Otherwise `None`. - fn maybe_as_cached_db(&self) -> Option; -} - -/// A wrapper type for [CachedState] which also allow storing the Sierra classes. -/// -/// The inner fields are wrapped in [Arc] and an async [RwLock](tokio::sync::RwLock) as to allow for -/// asynchronous access to the state. -/// -/// Example is when it is being referred to as a [StateRefDb] when the 'pending' state is being -/// requested while the block producer also have access to it in order to execute transactions and -/// produce blocks. -#[derive(Debug, Clone)] -pub struct CachedStateWrapper { - inner: Arc>>, - sierra_class: Arc>>, -} - -impl CachedStateWrapper -where - Db: StateExtRef, -{ - pub fn new(db: Db) -> Self { - Self { - sierra_class: Default::default(), - inner: Arc::new(AsyncRwLock::new(CachedState::new(db))), - } - } - - pub fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, CachedState> { - tokio::task::block_in_place(|| self.inner.blocking_write()) - } - - pub fn sierra_class( - &self, - ) -> tokio::sync::RwLockReadGuard<'_, HashMap> { - tokio::task::block_in_place(|| self.sierra_class.blocking_read()) - } - - pub fn sierra_class_mut( - &self, - ) -> tokio::sync::RwLockWriteGuard<'_, HashMap> { - tokio::task::block_in_place(|| self.sierra_class.blocking_write()) - } -} - -impl State for CachedStateWrapper -where - Db: StateExtRef, -{ - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - self.inner_mut().increment_nonce(contract_address) - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - self.inner_mut().set_class_hash_at(contract_address, class_hash) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - self.inner_mut().set_compiled_class_hash(class_hash, compiled_class_hash) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - self.inner_mut().set_contract_class(class_hash, contract_class) - } - - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.inner_mut().set_storage_at(contract_address, key, value) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - self.inner_mut().to_state_diff() - } -} - -impl StateExt for CachedStateWrapper -where - Db: StateExtRef, -{ - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - self.sierra_class_mut().insert(class_hash, sierra_class); - Ok(()) - } -} - -impl StateReader for CachedStateWrapper -where - Db: StateExtRef, -{ - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.inner_mut().get_class_hash_at(contract_address) - } - - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - self.inner_mut().get_compiled_class_hash(class_hash) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.inner_mut().get_compiled_contract_class(class_hash) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.inner_mut().get_nonce_at(contract_address) - } - - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - self.inner_mut().get_storage_at(contract_address, key) - } -} - -impl StateExtRef for CachedStateWrapper -where - Db: StateExtRef, -{ - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - if let Ok(class) = self.inner_mut().state.get_sierra_class(class_hash) { - return Ok(class); - } - - self.sierra_class() - .get(class_hash) - .cloned() - .ok_or(StateError::StateReadError("missing sierra class".to_string())) - } -} - -impl AsStateRefDb for CachedStateWrapper -where - Db: StateExtRef + Clone + Send + Sync + 'static, -{ - fn as_ref_db(&self) -> StateRefDb { - StateRefDb::new(self.clone()) - } -} - -/// Unit tests ported from `blockifier`. -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use blockifier::state::cached_state::CachedState; - use starknet_api::core::PatriciaKey; - use starknet_api::hash::StarkHash; - use starknet_api::{patricia_key, stark_felt}; - - use super::*; - use crate::backend::in_memory_db::MemDb; - use crate::db::Database; - - #[test] - fn get_inner_cached_db_if_exist() { - let mut db: Box = Box::new(MemDb::new()); - - let contract_address = ContractAddress(patricia_key!("123")); - let storage_key = StorageKey(patricia_key!("456")); - let storage_value = stark_felt!("0x1"); - - db.set_storage_at(contract_address, storage_key, storage_value); - - let has_value = db.maybe_as_cached_db().and_then(|db| { - db.storage - .get(&contract_address) - .and_then(|record| record.storage.get(&storage_key)) - .copied() - }); - - assert_matches!(has_value, Some(value) if value == storage_value); - } - - #[test] - fn get_uninitialized_storage_value() { - let mut state = CachedState::new(CachedDb::new(MemDb::new())); - let contract_address = ContractAddress(patricia_key!("0x1")); - let key = StorageKey(patricia_key!("0x10")); - assert_eq!(state.get_storage_at(contract_address, key).unwrap(), StarkFelt::default()); - } - - #[test] - fn get_and_set_storage_value() { - let contract_address0 = ContractAddress(patricia_key!("0x100")); - let contract_address1 = ContractAddress(patricia_key!("0x200")); - let key0 = StorageKey(patricia_key!("0x10")); - let key1 = StorageKey(patricia_key!("0x20")); - let storage_val0 = stark_felt!("0x1"); - let storage_val1 = stark_felt!("0x5"); - - let mut state = CachedState::new(CachedDb { - contracts: HashMap::from([ - (contract_address0, ClassHash(0_u32.into())), - (contract_address1, ClassHash(0_u32.into())), - ]), - storage: HashMap::from([ - ( - contract_address0, - StorageRecord { - nonce: Nonce(0_u32.into()), - storage: HashMap::from([(key0, storage_val0)]), - }, - ), - ( - contract_address1, - StorageRecord { - nonce: Nonce(0_u32.into()), - storage: HashMap::from([(key1, storage_val1)]), - }, - ), - ]), - classes: HashMap::new(), - sierra_classes: HashMap::new(), - db: MemDb::new(), - }); - - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), storage_val0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); - - let modified_storage_value0 = stark_felt!("0xA"); - state.set_storage_at(contract_address0, key0, modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); - - let modified_storage_value1 = stark_felt!("0x7"); - state.set_storage_at(contract_address1, key1, modified_storage_value1); - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), modified_storage_value1); - } - - #[test] - fn get_uninitialized_value() { - let mut state = CachedState::new(CachedDb::new(MemDb::new())); - let contract_address = ContractAddress(patricia_key!("0x1")); - assert_eq!(state.get_nonce_at(contract_address).unwrap(), Nonce::default()); - } - - #[test] - fn get_uninitialized_class_hash_value() { - let mut state = CachedState::new(CachedDb::new(MemDb::new())); - let valid_contract_address = ContractAddress(patricia_key!("0x1")); - assert_eq!(state.get_class_hash_at(valid_contract_address).unwrap(), ClassHash::default()); - } - - #[test] - fn cannot_set_class_hash_to_uninitialized_contract() { - let mut state = CachedState::new(CachedDb::new(MemDb::new())); - let uninitialized_contract_address = ContractAddress::default(); - let class_hash = ClassHash(stark_felt!("0x100")); - assert_matches!( - state.set_class_hash_at(uninitialized_contract_address, class_hash).unwrap_err(), - StateError::OutOfRangeContractAddress - ); - } - - #[test] - fn get_and_increment_nonce() { - let contract_address1 = ContractAddress(patricia_key!("0x100")); - let contract_address2 = ContractAddress(patricia_key!("0x200")); - let initial_nonce = Nonce(stark_felt!("0x1")); - - let mut state = CachedState::new(CachedDb { - contracts: HashMap::from([ - (contract_address1, ClassHash(0_u32.into())), - (contract_address2, ClassHash(0_u32.into())), - ]), - storage: HashMap::from([ - ( - contract_address1, - StorageRecord { nonce: initial_nonce, storage: HashMap::new() }, - ), - ( - contract_address2, - StorageRecord { nonce: initial_nonce, storage: HashMap::new() }, - ), - ]), - classes: HashMap::new(), - sierra_classes: HashMap::new(), - db: MemDb::new(), - }); - - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), initial_nonce); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address1).is_ok()); - let nonce1_plus_one = Nonce(stark_felt!("0x2")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_one); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address1).is_ok()); - let nonce1_plus_two = Nonce(stark_felt!("0x3")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address2).is_ok()); - let nonce2_plus_one = Nonce(stark_felt!("0x2")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), nonce2_plus_one); - } -} diff --git a/crates/katana/core/src/db/mod.rs b/crates/katana/core/src/db/mod.rs deleted file mode 100644 index de0334f7cf..0000000000 --- a/crates/katana/core/src/db/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::fmt; -use std::sync::Arc; - -use anyhow::Result; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use parking_lot::Mutex; -use starknet::core::types::FlattenedSierraClass; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; -use starknet_api::state::StorageKey; - -use self::cached::MaybeAsCachedDb; -use self::serde::state::SerializableState; - -pub mod cached; -pub mod serde; - -/// An extension of the [StateReader] trait, to allow fetching Sierra class from the state. -pub trait StateExtRef: StateReader + fmt::Debug { - /// Returns the Sierra class for the given class hash. - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult; -} - -/// An extension of the [State] trait, to allow storing Sierra classes. -pub trait StateExt: State + StateExtRef { - /// Set the Sierra class for the given class hash. - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()>; -} - -/// A trait which represents a state database. -pub trait Database: StateExt + AsStateRefDb + Send + Sync + MaybeAsCachedDb { - /// Set the exact nonce value for the given contract address. - fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce); - - /// Returns the serialized version of the state. - fn dump_state(&self) -> Result; - - /// Load the serialized state into the current state. - fn load_state(&mut self, state: SerializableState) -> Result<()> { - for (addr, record) in state.storage { - let address = ContractAddress(patricia_key!(addr)); - - record.storage.iter().for_each(|(key, value)| { - self.set_storage_at(address, StorageKey(patricia_key!(*key)), (*value).into()); - }); - - self.set_nonce(address, Nonce(record.nonce.into())); - } - - for (address, class_hash) in state.contracts { - self.set_class_hash_at( - ContractAddress(patricia_key!(address)), - ClassHash(class_hash.into()), - )?; - } - - for (hash, record) in state.classes { - let hash = ClassHash(hash.into()); - let compiled_hash = CompiledClassHash(record.compiled_hash.into()); - - self.set_contract_class(&hash, record.class.clone().try_into()?)?; - self.set_compiled_class_hash(hash, compiled_hash)?; - } - - for (hash, sierra_class) in state.sierra_classes { - let hash = ClassHash(hash.into()); - self.set_sierra_class(hash, sierra_class.clone())?; - } - - Ok(()) - } -} - -pub trait AsStateRefDb { - /// Returns the current state as a read only state - fn as_ref_db(&self) -> StateRefDb; -} - -/// A type which represents a state at a cetain point. This state type is only meant to be read -/// from. -/// -/// It implements [Clone] so that it can be cloned into a -/// [CachedState](blockifier::state::cached_state::CachedState) for executing transactions -/// based on this state, as [CachedState](blockifier::state::cached_state::CachedState) requires the -/// ownership of the inner [StateReader] that it wraps. -/// -/// The inner type is wrapped inside a [Mutex] to allow interior mutability due to the fact -/// that the [StateReader] trait requires mutable access to the type that implements it. -#[derive(Debug, Clone)] -pub struct StateRefDb(Arc>); - -impl StateRefDb { - pub fn new(state: T) -> Self - where - T: StateExtRef + Send + Sync + 'static, - { - Self(Arc::new(Mutex::new(state))) - } -} - -impl StateReader for StateRefDb { - fn get_storage_at(&mut self, addr: ContractAddress, key: StorageKey) -> StateResult { - self.0.lock().get_storage_at(addr, key) - } - - fn get_class_hash_at(&mut self, addr: ContractAddress) -> StateResult { - self.0.lock().get_class_hash_at(addr) - } - - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - self.0.lock().get_compiled_class_hash(class_hash) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.0.lock().get_nonce_at(contract_address) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.0.lock().get_compiled_contract_class(class_hash) - } -} - -impl StateExtRef for StateRefDb { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - self.0.lock().get_sierra_class(class_hash) - } -} diff --git a/crates/katana/core/src/db/serde/contract.rs b/crates/katana/core/src/db/serde/contract.rs deleted file mode 100644 index 0821f80661..0000000000 --- a/crates/katana/core/src/db/serde/contract.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use serde::{Deserialize, Serialize}; -use starknet_api::core::EntryPointSelector; -use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset, EntryPointType}; - -use super::program::SerializableProgram; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum SerializableContractClass { - V0(SerializableContractClassV0), - V1(SerializableContractClassV1), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableContractClassV0 { - pub program: SerializableProgram, - pub entry_points_by_type: HashMap>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableContractClassV1 { - pub program: SerializableProgram, - pub entry_points_by_type: HashMap>, - pub hints: HashMap>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPoint { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, -} - -impl From for SerializableEntryPoint { - fn from(value: EntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -impl From for EntryPoint { - fn from(value: SerializableEntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPointOffset(pub usize); - -impl From for SerializableEntryPointOffset { - fn from(value: EntryPointOffset) -> Self { - Self(value.0) - } -} - -impl From for EntryPointOffset { - fn from(value: SerializableEntryPointOffset) -> Self { - Self(value.0) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableEntryPointV1 { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, - pub builtins: Vec, -} - -impl From for blockifier::execution::contract_class::EntryPointV1 { - fn from(value: SerializableEntryPointV1) -> Self { - blockifier::execution::contract_class::EntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -impl From for SerializableEntryPointV1 { - fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { - SerializableEntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -impl TryFrom for blockifier::execution::contract_class::ContractClass { - type Error = anyhow::Error; - - fn try_from(value: SerializableContractClass) -> Result { - Ok(match value { - SerializableContractClass::V0(v0) => { - blockifier::execution::contract_class::ContractClass::V0( - blockifier::execution::contract_class::ContractClassV0(Arc::new( - blockifier::execution::contract_class::ContractClassV0Inner { - program: v0.program.into(), - entry_points_by_type: v0 - .entry_points_by_type - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }, - )), - ) - } - SerializableContractClass::V1(v1) => { - blockifier::execution::contract_class::ContractClass::V1( - blockifier::execution::contract_class::ContractClassV1(Arc::new( - blockifier::execution::contract_class::ContractClassV1Inner { - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::from_slice(&v).unwrap())) - .collect(), - program: v1.program.into(), - entry_points_by_type: v1 - .entry_points_by_type - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - }, - )), - ) - } - }) - } -} - -impl From for SerializableContractClass { - fn from(value: blockifier::execution::contract_class::ContractClass) -> Self { - match value { - blockifier::execution::contract_class::ContractClass::V0(v0) => { - SerializableContractClass::V0(SerializableContractClassV0 { - program: v0.program.clone().into(), - entry_points_by_type: v0 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }) - } - blockifier::execution::contract_class::ContractClass::V1(v1) => { - SerializableContractClass::V1(SerializableContractClassV1 { - program: v1.program.clone().into(), - entry_points_by_type: v1 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::to_vec(&v).unwrap())) - .collect(), - }) - } - } - } -} - -#[cfg(test)] -mod tests { - use blockifier::execution::contract_class::ContractClass; - use starknet::core::types::contract::SierraClass; - - use super::*; - use crate::constants::UDC_CONTRACT; - use crate::utils::contract::rpc_to_inner_class; - - #[test] - fn serialize_and_deserialize_legacy_contract() { - let original_contract = UDC_CONTRACT.clone(); - - let serializable_contract: SerializableContractClass = original_contract.clone().into(); - assert!(matches!(serializable_contract, SerializableContractClass::V0(_))); - - let bytes = serde_json::to_vec(&serializable_contract).unwrap(); - let serializable_contract: SerializableContractClass = - serde_json::from_slice(&bytes).unwrap(); - - let contract: ContractClass = serializable_contract.try_into().expect("should deserialize"); - assert_eq!(contract, original_contract); - } - - #[test] - fn serialize_and_deserialize_contract() { - let class = serde_json::from_str::(include_str!( - "../../../contracts/compiled/cairo1_contract.json" - )) - .expect("should deserialize sierra class") - .flatten() - .expect("should flatten"); - - let (_, original_contract) = - rpc_to_inner_class(&class).expect("should convert from flattened to contract class"); - - let serializable_contract: SerializableContractClass = original_contract.clone().into(); - assert!(matches!(serializable_contract, SerializableContractClass::V1(_))); - - let bytes = serde_json::to_vec(&serializable_contract).unwrap(); - let serializable_contract: SerializableContractClass = - serde_json::from_slice(&bytes).unwrap(); - - let contract: ContractClass = serializable_contract.try_into().expect("should deserialize"); - assert_eq!(contract, original_contract); - } -} diff --git a/crates/katana/core/src/db/serde/mod.rs b/crates/katana/core/src/db/serde/mod.rs deleted file mode 100644 index 1743dad5a9..0000000000 --- a/crates/katana/core/src/db/serde/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod contract; -pub mod program; -pub mod state; diff --git a/crates/katana/core/src/db/serde/program.rs b/crates/katana/core/src/db/serde/program.rs deleted file mode 100644 index 3762487101..0000000000 --- a/crates/katana/core/src/db/serde/program.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use cairo_vm::felt::Felt252; -use cairo_vm::hint_processor::hint_processor_definition::HintReference; -use cairo_vm::serde::deserialize_program::{ - ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, - InstructionLocation, Member, OffsetValue, -}; -use cairo_vm::types::program::{Program, SharedProgramData}; -use cairo_vm::types::relocatable::MaybeRelocatable; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableProgram { - pub shared_program_data: SerializableSharedProgramData, - pub constants: HashMap, - pub builtins: Vec, -} - -impl From for SerializableProgram { - fn from(value: Program) -> Self { - Self { - shared_program_data: value.shared_program_data.as_ref().clone().into(), - constants: value.constants, - builtins: value.builtins, - } - } -} - -impl From for Program { - fn from(value: SerializableProgram) -> Self { - Self { - shared_program_data: Arc::new(value.shared_program_data.into()), - constants: value.constants, - builtins: value.builtins, - } - } -} - -// Fields of `SerializableProgramData` must not rely on `deserialize_any` -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableSharedProgramData { - pub data: Vec, - pub hints: HashMap>, - pub main: Option, - pub start: Option, - pub end: Option, - pub error_message_attributes: Vec, - pub instruction_locations: Option>, - pub identifiers: HashMap, - pub reference_manager: Vec, -} - -impl From for SerializableSharedProgramData { - fn from(value: SharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -impl From for SharedProgramData { - fn from(value: SerializableSharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintParams { - pub code: String, - pub accessible_scopes: Vec, - pub flow_tracking_data: SerializableFlowTrackingData, -} - -impl From for SerializableHintParams { - fn from(value: HintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -impl From for HintParams { - fn from(value: SerializableHintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableIdentifier { - pub pc: Option, - pub type_: Option, - pub value: Option, - pub full_name: Option, - pub members: Option>, - pub cairo_type: Option, -} - -impl From for SerializableIdentifier { - fn from(value: Identifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} - -impl From for Identifier { - fn from(value: SerializableIdentifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintReference { - pub offset1: OffsetValue, - pub offset2: OffsetValue, - pub dereference: bool, - pub ap_tracking_data: Option, - pub cairo_type: Option, -} - -impl From for SerializableHintReference { - fn from(value: HintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } -} - -impl From for HintReference { - fn from(value: SerializableHintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableAttribute { - pub name: String, - pub start_pc: usize, - pub end_pc: usize, - pub value: String, - pub flow_tracking_data: Option, -} - -impl From for SerializableAttribute { - fn from(value: Attribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } - } -} - -impl From for Attribute { - fn from(value: SerializableAttribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableFlowTrackingData { - pub ap_tracking: ApTracking, - pub reference_ids: HashMap, -} - -impl From for SerializableFlowTrackingData { - fn from(value: FlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } - } -} - -impl From for FlowTrackingData { - fn from(value: SerializableFlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } - } -} diff --git a/crates/katana/core/src/db/serde/state.rs b/crates/katana/core/src/db/serde/state.rs deleted file mode 100644 index 9fa0a68795..0000000000 --- a/crates/katana/core/src/db/serde/state.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::BTreeMap; -use std::fs; -use std::io::{self, Read}; -use std::path::Path; - -use ::serde::{Deserialize, Serialize}; -use flate2::read::GzDecoder; -use starknet::core::types::{FieldElement, FlattenedSierraClass}; - -use crate::db::serde::contract::SerializableContractClass; - -#[derive(Clone, Debug, Serialize, Deserialize, Default)] -pub struct SerializableState { - /// Contract address to its class hash - pub contracts: BTreeMap, - /// Address to storage record. - pub storage: BTreeMap, - /// Class hash to class record. - pub classes: BTreeMap, - /// Class hash to sierra class. - pub sierra_classes: BTreeMap, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SerializableClassRecord { - /// The compiled class hash. - pub compiled_hash: FieldElement, - /// The compiled class. - pub class: SerializableContractClass, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SerializableStorageRecord { - pub nonce: FieldElement, - pub storage: BTreeMap, -} - -impl SerializableState { - /// Loads the serialized state from the given path - pub fn load(path: impl AsRef) -> Result { - let path = path.as_ref(); - let buf = if path.is_dir() { fs::read(path.join("state.bin"))? } else { fs::read(path)? }; - - let mut decoder = GzDecoder::new(&buf[..]); - let mut decoded_data: Vec = Vec::new(); - - let state = serde_json::from_slice(if decoder.header().is_some() { - decoder.read_to_end(decoded_data.as_mut())?; - &decoded_data - } else { - &buf - })?; - - Ok(state) - } - - /// This is used as the clap `value_parser` implementation - pub fn parse(path: &str) -> Result { - Self::load(path).map_err(|err| err.to_string()) - } -} diff --git a/crates/katana/core/src/env.rs b/crates/katana/core/src/env.rs index 2af8cc563f..951e65d2d9 100644 --- a/crates/katana/core/src/env.rs +++ b/crates/katana/core/src/env.rs @@ -7,9 +7,7 @@ use cairo_vm::vm::runners::builtin_runner::{ SEGMENT_ARENA_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, }; use starknet_api::block::{BlockNumber, BlockTimestamp}; -use starknet_api::core::{ChainId, ContractAddress, PatriciaKey}; -use starknet_api::hash::StarkHash; -use starknet_api::patricia_key; +use starknet_api::core::ChainId; use crate::constants::{DEFAULT_GAS_PRICE, FEE_TOKEN_ADDRESS, SEQUENCER_ADDRESS}; @@ -34,8 +32,8 @@ impl Default for Env { chain_id: ChainId("KATANA".to_string()), block_number: BlockNumber::default(), block_timestamp: BlockTimestamp::default(), - sequencer_address: ContractAddress(patricia_key!(*SEQUENCER_ADDRESS)), - fee_token_address: ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)), + sequencer_address: (*SEQUENCER_ADDRESS).into(), + fee_token_address: (*FEE_TOKEN_ADDRESS).into(), vm_resource_fee_cost: get_default_vm_resource_fee_cost().into(), gas_price: DEFAULT_GAS_PRICE, invoke_tx_max_n_steps: 1_000_000, diff --git a/crates/katana/core/src/execution.rs b/crates/katana/core/src/execution.rs deleted file mode 100644 index e11f5a314d..0000000000 --- a/crates/katana/core/src/execution.rs +++ /dev/null @@ -1,407 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use blockifier::block_context::BlockContext; -use blockifier::execution::contract_class::ContractClass; -use blockifier::execution::entry_point::CallInfo; -use blockifier::state::cached_state::CommitmentStateDiff; -use blockifier::state::state_api::{State, StateReader}; -use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; -use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; -use blockifier::transaction::transactions::ExecutableTransaction; -use convert_case::{Case, Casing}; -use parking_lot::RwLock; -use starknet::core::types::{Event, ExecutionResult, FieldElement, FlattenedSierraClass, MsgToL1}; -use starknet_api::core::ClassHash; -use tracing::{trace, warn}; - -use crate::backend::storage::transaction::{ - DeclareTransaction, RejectedTransaction, Transaction, TransactionOutput, -}; -use crate::db::cached::CachedStateWrapper; -use crate::db::{Database, StateExt, StateRefDb}; -use crate::utils::transaction::warn_message_transaction_error_exec_error; - -/// The outcome that after executing a list of transactions. -pub struct ExecutionOutcome { - // states - pub state_diff: CommitmentStateDiff, - pub declared_classes: HashMap, - pub declared_sierra_classes: HashMap, - // transactions - pub transactions: Vec, -} - -impl ExecutionOutcome { - /// Apply the execution outcome to the given database. - pub fn apply_to(&self, db: &mut dyn Database) { - let ExecutionOutcome { state_diff, declared_classes, declared_sierra_classes, .. } = self; - - // update contract storages - state_diff.storage_updates.iter().for_each(|(contract_address, storages)| { - storages.iter().for_each(|(key, value)| { - db.set_storage_at(*contract_address, *key, *value); - }) - }); - - // update declared contracts - // apply newly declared classses - for (class_hash, compiled_class_hash) in &state_diff.class_hash_to_compiled_class_hash { - let contract_class = - declared_classes.get(class_hash).expect("contract class should exist").clone(); - - let is_sierra = matches!(contract_class, ContractClass::V1(_)); - - db.set_contract_class(class_hash, contract_class).unwrap(); - db.set_compiled_class_hash(*class_hash, *compiled_class_hash).unwrap(); - - if is_sierra { - if let Some(class) = declared_sierra_classes.get(class_hash).cloned() { - db.set_sierra_class(*class_hash, class).unwrap(); - } else { - panic!("sierra class definition is missing") - } - } - } - - // update deployed contracts - state_diff.address_to_class_hash.iter().for_each(|(contract_address, class_hash)| { - db.set_class_hash_at(*contract_address, *class_hash).unwrap() - }); - - // update accounts nonce - state_diff.address_to_nonce.iter().for_each(|(contract_address, nonce)| { - db.set_nonce(*contract_address, *nonce); - }); - } -} - -impl Default for ExecutionOutcome { - fn default() -> Self { - let state_diff = CommitmentStateDiff { - storage_updates: Default::default(), - address_to_nonce: Default::default(), - address_to_class_hash: Default::default(), - class_hash_to_compiled_class_hash: Default::default(), - }; - - Self { - state_diff, - transactions: Default::default(), - declared_classes: Default::default(), - declared_sierra_classes: Default::default(), - } - } -} - -/// The result of a transaction execution. -pub type TxExecutionResult = Result; - -/// A transaction executor. -/// -/// The transactions will be executed in an iterator fashion, sequentially, in the -/// exact order they are provided to the executor. The execution is done within its implementation -/// of the [`Iterator`] trait. -pub struct TransactionExecutor<'a> { - /// A flag to enable/disable fee charging. - charge_fee: bool, - /// The block context the transactions will be executed on. - block_context: &'a BlockContext, - /// The transactions to be executed (in the exact order they are in the iterator). - transactions: std::vec::IntoIter, - /// The state the transactions will be executed on. - state: &'a mut CachedStateWrapper, - - // logs flags - error_log: bool, - events_log: bool, - resources_log: bool, -} - -impl<'a> TransactionExecutor<'a> { - pub fn new( - state: &'a mut CachedStateWrapper, - block_context: &'a BlockContext, - charge_fee: bool, - transactions: Vec, - ) -> Self { - Self { - state, - charge_fee, - block_context, - error_log: false, - events_log: false, - resources_log: false, - transactions: transactions.into_iter(), - } - } - - pub fn with_events_log(self) -> Self { - Self { events_log: true, ..self } - } - - pub fn with_error_log(self) -> Self { - Self { error_log: true, ..self } - } - - pub fn with_resources_log(self) -> Self { - Self { resources_log: true, ..self } - } - - /// A method to conveniently execute all the transactions and return their results. - pub fn execute(self) -> Vec { - self.collect() - } -} - -impl<'a> Iterator for TransactionExecutor<'a> { - type Item = TxExecutionResult; - fn next(&mut self) -> Option { - self.transactions.next().map(|tx| { - let sierra = if let Transaction::Declare(DeclareTransaction { - sierra_class: Some(sierra_class), - inner, - .. - }) = &tx - { - Some((inner.class_hash(), sierra_class.clone())) - } else { - None - }; - - let res = match tx.into() { - ExecutionTransaction::AccountTransaction(tx) => { - tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) - } - ExecutionTransaction::L1HandlerTransaction(tx) => { - tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) - } - }; - - match res { - Ok(exec_info) => { - if let Some((class_hash, sierra_class)) = sierra { - self.state - .set_sierra_class(class_hash, sierra_class) - .expect("failed to set sierra class"); - } - - if self.error_log { - if let Some(err) = &exec_info.revert_error { - let formatted_err = format!("{:?}", err).replace("\\n", "\n"); - warn!(target: "executor", "Transaction execution error: {formatted_err}"); - } - } - - if self.resources_log { - trace!( - target: "executor", - "Transaction resource usage: {}", - pretty_print_resources(&exec_info.actual_resources) - ); - } - - if self.events_log { - trace_events(&events_from_exec_info(&exec_info)); - } - - Ok(exec_info) - } - - Err(err) => { - if self.error_log { - warn_message_transaction_error_exec_error(&err); - } - - Err(err) - } - } - }) - } -} - -/// An enum which represents a transaction that has been executed and may or may not be valid. -#[derive(Clone)] -pub enum MaybeInvalidExecutedTransaction { - Valid(Arc), - Invalid(Arc), -} - -pub struct PendingState { - pub state: RwLock>, - /// The transactions that have been executed. - pub executed_transactions: RwLock>, -} - -#[derive(Debug)] -pub struct ExecutedTransaction { - pub inner: Transaction, - pub output: TransactionOutput, - pub execution_info: TransactionExecutionInfo, -} - -impl ExecutedTransaction { - pub fn new(transaction: Transaction, execution_info: TransactionExecutionInfo) -> Self { - let actual_fee = execution_info.actual_fee.0; - let events = events_from_exec_info(&execution_info); - let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info); - - Self { - execution_info, - inner: transaction, - output: TransactionOutput { actual_fee, events, messages_sent }, - } - } - - pub fn execution_result(&self) -> ExecutionResult { - if let Some(ref revert_err) = self.execution_info.revert_error { - ExecutionResult::Reverted { reason: revert_err.clone() } - } else { - ExecutionResult::Succeeded - } - } -} - -pub fn events_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { - let mut events: Vec = vec![]; - - fn get_events_recursively(call_info: &CallInfo) -> Vec { - let mut events: Vec = vec![]; - - events.extend(call_info.execution.events.iter().map(|e| Event { - from_address: (*call_info.call.storage_address.0.key()).into(), - data: e.event.data.0.iter().map(|d| (*d).into()).collect(), - keys: e.event.keys.iter().map(|k| k.0.into()).collect(), - })); - - call_info.inner_calls.iter().for_each(|call| { - events.extend(get_events_recursively(call)); - }); - - events - } - - if let Some(ref call) = execution_info.validate_call_info { - events.extend(get_events_recursively(call)); - } - - if let Some(ref call) = execution_info.execute_call_info { - events.extend(get_events_recursively(call)); - } - - if let Some(ref call) = execution_info.fee_transfer_call_info { - events.extend(get_events_recursively(call)); - } - - events -} - -pub fn l2_to_l1_messages_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { - let mut messages = vec![]; - - fn get_messages_recursively(info: &CallInfo) -> Vec { - let mut messages = vec![]; - - messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MsgToL1 { - to_address: - FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), - from_address: (*info.call.caller_address.0.key()).into(), - payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), - })); - - info.inner_calls.iter().for_each(|call| { - messages.extend(get_messages_recursively(call)); - }); - - messages - } - - if let Some(ref info) = execution_info.validate_call_info { - messages.extend(get_messages_recursively(info)); - } - - if let Some(ref info) = execution_info.execute_call_info { - messages.extend(get_messages_recursively(info)); - } - - if let Some(ref info) = execution_info.fee_transfer_call_info { - messages.extend(get_messages_recursively(info)); - } - - messages -} - -fn pretty_print_resources(resources: &ResourcesMapping) -> String { - let mut mapped_strings: Vec<_> = resources - .0 - .iter() - .filter_map(|(k, v)| match k.as_str() { - "l1_gas_usage" => Some(format!("L1 Gas: {}", v)), - "range_check_builtin" => Some(format!("Range Checks: {}", v)), - "ecdsa_builtin" => Some(format!("ECDSA: {}", v)), - "n_steps" => None, - "pedersen_builtin" => Some(format!("Pedersen: {}", v)), - "bitwise_builtin" => Some(format!("Bitwise: {}", v)), - "keccak_builtin" => Some(format!("Keccak: {}", v)), - _ => Some(format!("{}: {}", k.to_case(Case::Title), v)), - }) - .collect::>(); - - // Sort the strings alphabetically - mapped_strings.sort(); - - // Prepend "Steps" if it exists, so it is always first - if let Some(steps) = resources.0.get("n_steps") { - mapped_strings.insert(0, format!("Steps: {}", steps)); - } - - mapped_strings.join(" | ") -} - -fn trace_events(events: &[Event]) { - for e in events { - let formatted_keys = - e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); - - trace!(target: "executor", "Event emitted keys=[{}]", formatted_keys); - } -} - -pub fn create_execution_outcome( - state: &mut CachedStateWrapper, - transactions: Vec<(Transaction, Result)>, -) -> ExecutionOutcome { - let transactions = transactions - .into_iter() - .map(|(tx, res)| match res { - Ok(exec_info) => MaybeInvalidExecutedTransaction::Valid(Arc::new( - ExecutedTransaction::new(tx, exec_info), - )), - - Err(err) => MaybeInvalidExecutedTransaction::Invalid(Arc::new(RejectedTransaction { - inner: tx, - execution_error: err.to_string(), - })), - }) - .collect::>(); - - let state_diff = state.to_state_diff(); - let declared_classes = state_diff - .class_hash_to_compiled_class_hash - .iter() - .map(|(class_hash, _)| { - let contract_class = state - .get_compiled_contract_class(class_hash) - .expect("contract class must exist in state if declared"); - (*class_hash, contract_class) - }) - .collect::>(); - - ExecutionOutcome { - state_diff, - transactions, - declared_classes, - declared_sierra_classes: state.sierra_class().clone(), - } -} diff --git a/crates/katana/core/src/fork/backend.rs b/crates/katana/core/src/fork/backend.rs deleted file mode 100644 index 6f6676afce..0000000000 --- a/crates/katana/core/src/fork/backend.rs +++ /dev/null @@ -1,475 +0,0 @@ -use std::collections::VecDeque; -use std::pin::Pin; -use std::sync::mpsc::{channel as oneshot, Sender as OneshotSender}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::thread; - -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::{StateReader, StateResult}; -use futures::channel::mpsc::{channel, Receiver, Sender, TrySendError}; -use futures::stream::Stream; -use futures::{Future, FutureExt}; -use parking_lot::RwLock; -use starknet::core::types::{BlockId, FieldElement, FlattenedSierraClass, StarknetError}; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::{ - JsonRpcClient, MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; -use tracing::trace; - -use crate::db::cached::CachedDb; -use crate::db::StateExtRef; -use crate::utils::contract::{ - compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, -}; - -type GetNonceResult = Result; -type GetStorageResult = Result; -type GetClassHashAtResult = Result; -type GetClassAtResult = Result; - -#[derive(Debug, thiserror::Error)] -pub enum ForkedBackendError { - #[error(transparent)] - Send(TrySendError), - #[error("Compute class hash error: {0}")] - ComputeClassHashError(String), - #[error(transparent)] - Provider(ProviderError), -} - -pub enum BackendRequest { - GetClassAt(ClassHash, OneshotSender), - GetNonce(ContractAddress, OneshotSender), - GetClassHashAt(ContractAddress, OneshotSender), - GetStorage(ContractAddress, StorageKey, OneshotSender), -} - -type BackendRequestFuture = Pin + Send>>; - -/// A thread-safe handler for the shared forked backend. This handler is responsible for receiving -/// requests from all instances of the [ForkedBackend], process them, and returns the results back -/// to the request sender. -pub struct BackendHandler { - provider: Arc>, - /// Requests that are currently being poll. - pending_requests: Vec, - /// Requests that are queued to be polled. - queued_requests: VecDeque, - /// A channel for receiving requests from the [ForkedBackend]'s. - incoming: Receiver, - /// Pinned block id for all requests. - block: BlockId, -} - -impl BackendHandler { - /// This function is responsible for transforming the incoming request - /// into a future that will be polled until completion by the `BackendHandler`. - /// - /// Each request is accompanied by the sender-half of a oneshot channel that will be used - /// to send the result back to the [ForkedBackend] which sent the requests. - fn handle_requests(&mut self, request: BackendRequest) { - let block = self.block; - let provider = self.provider.clone(); - - match request { - BackendRequest::GetNonce(contract_address, sender) => { - let fut = Box::pin(async move { - let contract_address: FieldElement = (*contract_address.0.key()).into(); - - let res = provider - .get_nonce(block, contract_address) - .await - .map(|n| Nonce(n.into())) - .map_err(ForkedBackendError::Provider); - - sender.send(res).expect("failed to send nonce result") - }); - - self.pending_requests.push(fut); - } - - BackendRequest::GetStorage(contract_address, key, sender) => { - let fut = Box::pin(async move { - let contract_address: FieldElement = (*contract_address.0.key()).into(); - let key: FieldElement = (*key.0.key()).into(); - - let res = provider - .get_storage_at(contract_address, key, block) - .await - .map(|f| f.into()) - .map_err(ForkedBackendError::Provider); - - sender.send(res).expect("failed to send storage result") - }); - - self.pending_requests.push(fut); - } - - BackendRequest::GetClassHashAt(contract_address, sender) => { - let fut = Box::pin(async move { - let contract_address: FieldElement = (*contract_address.0.key()).into(); - - let res = provider - .get_class_hash_at(block, contract_address) - .await - .map(|f| ClassHash(f.into())) - .map_err(ForkedBackendError::Provider); - - sender.send(res).expect("failed to send class hash result") - }); - - self.pending_requests.push(fut); - } - - BackendRequest::GetClassAt(class_hash, sender) => { - let fut = Box::pin(async move { - let class_hash: FieldElement = class_hash.0.into(); - - let res = provider - .get_class(block, class_hash) - .await - .map_err(ForkedBackendError::Provider); - - sender.send(res).expect("failed to send class result") - }); - - self.pending_requests.push(fut); - } - } - } -} - -impl Future for BackendHandler { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let pin = self.get_mut(); - loop { - // convert all queued requests into futures to be polled - while let Some(req) = pin.queued_requests.pop_front() { - pin.handle_requests(req); - } - - loop { - match Pin::new(&mut pin.incoming).poll_next(cx) { - Poll::Ready(Some(req)) => { - pin.queued_requests.push_back(req); - } - // Resolve if stream is exhausted. - Poll::Ready(None) => { - return Poll::Ready(()); - } - Poll::Pending => { - break; - } - } - } - - // poll all pending requests - for n in (0..pin.pending_requests.len()).rev() { - let mut fut = pin.pending_requests.swap_remove(n); - // poll the future and if the future is still pending, push it back to the - // pending requests so that it will be polled again - if fut.poll_unpin(cx).is_pending() { - pin.pending_requests.push(fut); - } - } - - // if no queued requests, then yield - if pin.queued_requests.is_empty() { - return Poll::Pending; - } - } - } -} - -#[derive(Debug, Clone)] -pub struct SharedBackend { - cache: Arc>>, -} - -impl SharedBackend { - pub fn new_with_backend_thread( - provider: Arc>, - block: BlockId, - ) -> Self { - let backend = ForkedBackend::spawn_thread(provider, block); - Self { cache: Arc::new(RwLock::new(CachedDb::new(backend))) } - } -} - -impl StateReader for SharedBackend { - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.cache.write().get_class_hash_at(contract_address) - } - - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - self.cache.write().get_compiled_class_hash(class_hash) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.cache.write().get_compiled_contract_class(class_hash) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.cache.write().get_nonce_at(contract_address) - } - - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - self.cache.write().get_storage_at(contract_address, key) - } -} - -impl StateExtRef for SharedBackend { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - self.cache.write().get_sierra_class(class_hash) - } -} - -/// An interface for interacting with a forked backend handler. This interface will be cloned into -/// multiple instances of the [ForkedBackend] and will be used to send requests to the handler. -#[derive(Debug, Clone)] -pub struct ForkedBackend { - handler: Sender, -} - -impl ForkedBackend { - pub fn spawn_thread(provider: Arc>, block: BlockId) -> Self { - let (backend, handler) = Self::new(provider, block); - - thread::Builder::new() - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to create fork backend thread tokio runtime"); - - rt.block_on(handler); - }) - .expect("failed to spawn fork backend thread"); - - trace!(target: "forked_backend", "fork backend thread spawned"); - - backend - } - - pub fn new( - provider: Arc>, - block: BlockId, - ) -> (Self, BackendHandler) { - let (sender, rx) = channel(1); - let handler = BackendHandler { - incoming: rx, - provider, - block, - queued_requests: VecDeque::new(), - pending_requests: Vec::new(), - }; - (Self { handler: sender }, handler) - } - - pub fn do_get_nonce( - &mut self, - contract_address: ContractAddress, - ) -> Result { - trace!(target: "forked_backend", "request nonce for contract address {}", contract_address.0.key()); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.handler - .try_send(BackendRequest::GetNonce(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive nonce result") - }) - } - - pub fn do_get_storage( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> Result { - trace!(target: "forked_backend", "request storage for address {} at key {}", contract_address.0.key(), key.0.key()); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.handler - .try_send(BackendRequest::GetStorage(contract_address, key, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive storage result") - }) - } - - pub fn do_get_class_hash_at( - &mut self, - contract_address: ContractAddress, - ) -> Result { - trace!(target: "forked_backend", "request class hash at address {}", contract_address.0.key()); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.handler - .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive class hash result") - }) - } - - pub fn do_get_class_at( - &mut self, - class_hash: ClassHash, - ) -> Result { - trace!(target: "forked_backend", "request class at hash {}", class_hash.0); - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot(); - self.handler - .try_send(BackendRequest::GetClassAt(class_hash, sender)) - .map_err(ForkedBackendError::Send)?; - rx.recv().expect("failed to receive class result") - }) - } - - pub fn do_get_compiled_class_hash( - &mut self, - class_hash: ClassHash, - ) -> Result { - trace!(target: "forked_backend", "request compiled class hash at class {}", class_hash.0); - let class = self.do_get_class_at(class_hash)?; - // if its a legacy class, then we just return back the class hash - // else if sierra class, then we have to compile it and compute the compiled class hash. - match class { - starknet::core::types::ContractClass::Legacy(_) => Ok(CompiledClassHash(class_hash.0)), - - starknet::core::types::ContractClass::Sierra(sierra_class) => { - tokio::task::block_in_place(|| { - compiled_class_hash_from_flattened_sierra_class(&sierra_class) - }) - .map(|f| CompiledClassHash(f.into())) - .map_err(|e| ForkedBackendError::ComputeClassHashError(e.to_string())) - } - } - } -} - -impl StateReader for ForkedBackend { - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - match self.do_get_compiled_class_hash(class_hash) { - Ok(compiled_class_hash) => Ok(compiled_class_hash), - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), - .. - }, - ))) => Err(StateError::UndeclaredClassHash(class_hash)), - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - match self.do_get_class_at(*class_hash) { - Ok(class) => match class { - starknet::core::types::ContractClass::Legacy(legacy_class) => { - legacy_rpc_to_inner_class(&legacy_class) - .map(|(_, class)| class) - .map_err(|e| StateError::StateReadError(e.to_string())) - } - - starknet::core::types::ContractClass::Sierra(sierra_class) => { - rpc_to_inner_class(&sierra_class) - .map(|(_, class)| class) - .map_err(|e| StateError::StateReadError(e.to_string())) - } - }, - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), - .. - }, - ))) => Err(StateError::UndeclaredClassHash(*class_hash)), - - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } - - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - match self.do_get_storage(contract_address, key) { - Ok(value) => Ok(value), - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }, - ))) => Ok(StarkFelt::default()), - - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - match self.do_get_nonce(contract_address) { - Ok(nonce) => Ok(nonce), - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }, - ))) => Ok(Nonce::default()), - - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } - - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - match self.do_get_class_hash_at(contract_address) { - Ok(class_hash) => Ok(class_hash), - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }, - ))) => Ok(ClassHash::default()), - - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } -} - -impl StateExtRef for ForkedBackend { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - match self.do_get_class_at(*class_hash) { - Ok(starknet::core::types::ContractClass::Sierra(sierra_class)) => Ok(sierra_class), - - Ok(_) => Err(StateError::StateReadError("Class hash is not a Sierra class".into())), - - Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), - .. - }, - ))) => Err(StateError::UndeclaredClassHash(*class_hash)), - - Err(e) => Err(StateError::StateReadError(e.to_string())), - } - } -} diff --git a/crates/katana/core/src/fork/db.rs b/crates/katana/core/src/fork/db.rs deleted file mode 100644 index f7d55bb77a..0000000000 --- a/crates/katana/core/src/fork/db.rs +++ /dev/null @@ -1,287 +0,0 @@ -use std::collections::BTreeMap; -use std::sync::Arc; - -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::CommitmentStateDiff; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::core::types::{BlockId, FlattenedSierraClass}; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::JsonRpcClient; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; - -use super::backend::SharedBackend; -use crate::db::cached::{AsCachedDb, CachedDb, MaybeAsCachedDb}; -use crate::db::serde::state::{ - SerializableClassRecord, SerializableState, SerializableStorageRecord, -}; -use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; - -/// A state database implementation that forks from a network. -/// -/// It will try to find the requested data in the cache, and if it's not there, it will fetch it -/// from the forked network. The fetched data will be stored in the cache so that the next time the -/// same data is requested, it will be fetched from the cache instead of fetching it from the forked -/// network again. -/// -/// The forked database provider should be locked to a particular block. -#[derive(Debug, Clone)] -pub struct ForkedDb { - /// Shared cache of the forked database. This will be shared across all instances of the - /// `ForkedDb` when it is cloned into a [StateRefDb] using the [AsStateRefDb] trait. - /// - /// So if one instance fetches data from the forked network, the - /// other instances will be able to use the cached data instead of fetching it again. - db: CachedDb, -} - -impl ForkedDb { - /// Construct a new `ForkedDb` from a `Provider` of the network to fork from at a particular - /// `block`. - pub fn new(provider: Arc>, block: BlockId) -> Self { - Self { db: CachedDb::new(SharedBackend::new_with_backend_thread(provider, block)) } - } - - #[cfg(test)] - pub fn new_from_backend(db: CachedDb) -> Self { - Self { db } - } -} - -impl State for ForkedDb { - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.db.set_storage_at(contract_address, key, value); - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - self.db.set_class_hash_at(contract_address, class_hash) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - self.db.set_compiled_class_hash(class_hash, compiled_class_hash) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - self.db.to_state_diff() - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - self.db.set_contract_class(class_hash, contract_class) - } - - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - self.db.increment_nonce(contract_address) - } -} - -impl StateReader for ForkedDb { - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - self.db.get_nonce_at(contract_address) - } - - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - self.db.get_storage_at(contract_address, key) - } - - fn get_class_hash_at( - &mut self, - contract_address: ContractAddress, - ) -> StateResult { - self.db.get_class_hash_at(contract_address) - } - - fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { - self.db.get_compiled_class_hash(class_hash) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.db.get_compiled_contract_class(class_hash) - } -} - -impl StateExtRef for ForkedDb { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - self.db.get_sierra_class(class_hash) - } -} - -impl StateExt for ForkedDb { - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - self.db.set_sierra_class(class_hash, sierra_class) - } -} - -impl AsStateRefDb for ForkedDb { - fn as_ref_db(&self) -> StateRefDb { - StateRefDb::new(self.clone()) - } -} - -impl MaybeAsCachedDb for ForkedDb { - fn maybe_as_cached_db(&self) -> Option { - Some(CachedDb { - db: (), - classes: self.db.classes.clone(), - storage: self.db.storage.clone(), - contracts: self.db.contracts.clone(), - sierra_classes: self.db.sierra_classes.clone(), - }) - } -} - -impl Database for ForkedDb { - fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { - self.db.storage.entry(addr).or_default().nonce = nonce; - } - - fn dump_state(&self) -> anyhow::Result { - let mut serializable = SerializableState::default(); - - self.db.storage.iter().for_each(|(addr, storage)| { - let mut record = SerializableStorageRecord { - storage: BTreeMap::new(), - nonce: storage.nonce.0.into(), - }; - - storage.storage.iter().for_each(|(key, value)| { - record.storage.insert((*key.0.key()).into(), (*value).into()); - }); - - serializable.storage.insert((*addr.0.key()).into(), record); - }); - - self.db.classes.iter().for_each(|(class_hash, class_record)| { - serializable.classes.insert( - class_hash.0.into(), - SerializableClassRecord { - class: class_record.class.clone().into(), - compiled_hash: class_record.compiled_hash.0.into(), - }, - ); - }); - - self.db.contracts.iter().for_each(|(address, class_hash)| { - serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); - }); - - self.db.sierra_classes.iter().for_each(|(class_hash, class)| { - serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); - }); - - Ok(serializable) - } -} - -#[cfg(test)] -mod tests { - use starknet::core::types::BlockTag; - use starknet::providers::jsonrpc::HttpTransport; - use starknet::providers::JsonRpcClient; - use starknet_api::core::PatriciaKey; - use starknet_api::hash::StarkHash; - use starknet_api::{patricia_key, stark_felt}; - use url::Url; - - use super::*; - use crate::constants::UDC_CONTRACT; - - const FORKED_ENDPOINT: &str = - "https://starknet-goerli.infura.io/v3/369ce5ac40614952af936e4d64e40474"; - - #[tokio::test] - async fn fetch_from_cache_if_exist() { - let address = ContractAddress(patricia_key!(0x1u32)); - let class_hash = ClassHash(stark_felt!(0x88u32)); - - let expected_nonce = Nonce(stark_felt!(44u32)); - let expected_storage_key = StorageKey(patricia_key!(0x2u32)); - let expected_storage_value = stark_felt!(55u32); - let expected_compiled_class_hash = CompiledClassHash(class_hash.0); - let expected_contract_class = (*UDC_CONTRACT).clone(); - - let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); - let mut cache = CachedDb::new(SharedBackend::new_with_backend_thread( - Arc::new(provider), - BlockId::Tag(BlockTag::Latest), - )); - - cache.storage.entry(address).or_default().nonce = expected_nonce; - cache.set_storage_at(address, expected_storage_key, expected_storage_value); - cache.set_contract_class(&class_hash, expected_contract_class.clone()).unwrap(); - cache.set_compiled_class_hash(class_hash, expected_compiled_class_hash).unwrap(); - - let mut db = ForkedDb::new_from_backend(cache); - - let nonce = db.get_nonce_at(address).unwrap(); - let storage_value = db.get_storage_at(address, expected_storage_key).unwrap(); - let contract_class = db.get_compiled_contract_class(&class_hash).unwrap(); - let compiled_class_hash = db.get_compiled_class_hash(class_hash).unwrap(); - - assert_eq!(nonce, expected_nonce); - assert_eq!(storage_value, expected_storage_value); - assert_eq!(contract_class, expected_contract_class); - assert_eq!(compiled_class_hash, expected_compiled_class_hash) - } - - #[tokio::test(flavor = "multi_thread")] - #[ignore] - async fn fetch_from_provider_if_not_in_cache() { - let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); - let mut db = ForkedDb::new(Arc::new(provider), BlockId::Tag(BlockTag::Latest)); - - let address = ContractAddress(patricia_key!( - "0x02b92ec12cA1e308f320e99364d4dd8fcc9efDAc574F836C8908de937C289974" - )); - let storage_key = StorageKey(patricia_key!( - "0x3b459c3fadecdb1a501f2fdeec06fd735cb2d93ea59779177a0981660a85352" - )); - - let class_hash = db.get_class_hash_at(address).unwrap(); - let class = db.get_compiled_contract_class(&class_hash).unwrap(); - let storage_value = db.get_storage_at(address, storage_key).unwrap(); - - let expected_class_hash = ClassHash(stark_felt!( - "0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003" - )); - - assert_eq!(class_hash, expected_class_hash); - - let class_hash_in_cache = *db.db.contracts.get(&address).unwrap(); - let class_in_cache = db.db.classes.get(&class_hash).unwrap().class.clone(); - let storage_value_in_cache = - *db.db.storage.get(&address).unwrap().storage.get(&storage_key).unwrap(); - - assert_eq!(class_in_cache, class, "class must be stored in cache"); - assert_eq!(class_hash_in_cache, expected_class_hash, "class hash must be stored in cache"); - assert_eq!(storage_value_in_cache, storage_value, "storage value must be stored in cache"); - } -} diff --git a/crates/katana/core/src/fork/mod.rs b/crates/katana/core/src/fork/mod.rs deleted file mode 100644 index b1a4440c59..0000000000 --- a/crates/katana/core/src/fork/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod backend; -pub mod db; diff --git a/crates/katana/core/src/lib.rs b/crates/katana/core/src/lib.rs index c80c58031f..35710188ac 100644 --- a/crates/katana/core/src/lib.rs +++ b/crates/katana/core/src/lib.rs @@ -1,10 +1,7 @@ pub mod accounts; pub mod backend; pub mod constants; -pub mod db; pub mod env; -pub mod execution; -pub mod fork; pub mod pool; pub mod sequencer; pub mod service; diff --git a/crates/katana/core/src/pool.rs b/crates/katana/core/src/pool.rs index 173091862d..ce0419ad12 100644 --- a/crates/katana/core/src/pool.rs +++ b/crates/katana/core/src/pool.rs @@ -1,15 +1,14 @@ // Code adapted from Foundry's Anvil use futures::channel::mpsc::{channel, Receiver, Sender}; +use katana_primitives::transaction::ExecutableTxWithHash; use parking_lot::RwLock; use starknet::core::types::FieldElement; use tracing::{info, warn}; -use crate::backend::storage::transaction::Transaction; - #[derive(Debug, Default)] pub struct TransactionPool { - transactions: RwLock>, + transactions: RwLock>, transaction_listeners: RwLock>>, } @@ -20,8 +19,8 @@ impl TransactionPool { } impl TransactionPool { - pub fn add_transaction(&self, transaction: Transaction) { - let hash = transaction.hash(); + pub fn add_transaction(&self, transaction: ExecutableTxWithHash) { + let hash = transaction.hash; self.transactions.write().push(transaction); info!(target: "txpool", "Transaction received | Hash: {hash:#x}"); @@ -38,7 +37,7 @@ impl TransactionPool { } /// Get all the transaction from the pool and clear it. - pub fn get_transactions(&self) -> Vec { + pub fn get_transactions(&self) -> Vec { let mut txs = self.transactions.write(); let transactions = txs.clone(); txs.clear(); diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 16d01783d8..bf04269eea 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -4,26 +4,30 @@ use std::slice::Iter; use std::sync::Arc; use anyhow::Result; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::state_api::{State, StateReader}; -use starknet::core::types::{ - BlockId, BlockTag, EmittedEvent, Event, EventsPage, FeeEstimate, FieldElement, - MaybePendingTransactionReceipt, StateUpdate, +use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError}; +use katana_executor::blockifier::state::StateRefDb; +use katana_executor::blockifier::utils::EntryPointCall; +use katana_executor::blockifier::PendingState; +use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber}; +use katana_primitives::contract::{ + ClassHash, CompiledContractClass, ContractAddress, Nonce, StorageKey, StorageValue, }; -use starknet_api::core::{ChainId, ClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; +use katana_primitives::transaction::{ExecutableTxWithHash, TxHash, TxWithHash}; +use katana_primitives::FieldElement; +use katana_provider::traits::block::{ + BlockHashProvider, BlockIdReader, BlockNumberProvider, BlockProvider, +}; +use katana_provider::traits::contract::ContractClassProvider; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionsProviderExt, +}; +use starknet::core::types::{BlockTag, EmittedEvent, Event, EventsPage, FeeEstimate}; +use starknet_api::core::ChainId; use crate::backend::config::StarknetConfig; use crate::backend::contract::StarknetContract; -use crate::backend::storage::block::{ExecutedBlock, PartialBlock, PartialHeader}; -use crate::backend::storage::transaction::{ - DeclareTransaction, DeployAccountTransaction, InvokeTransaction, KnownTransaction, - PendingTransaction, Transaction, -}; -use crate::backend::{Backend, ExternalFunctionCall}; -use crate::db::{AsStateRefDb, StateExtRef, StateRefDb}; -use crate::execution::{MaybeInvalidExecutedTransaction, PendingState}; +use crate::backend::Backend; use crate::pool::TransactionPool; use crate::sequencer_error::SequencerError; use crate::service::block_producer::{BlockProducer, BlockProducerMode}; @@ -57,15 +61,14 @@ impl KatanaSequencer { let pool = Arc::new(TransactionPool::new()); let miner = TransactionMiner::new(pool.add_listener()); + let state = StateFactoryProvider::latest(backend.blockchain.provider()) + .map(StateRefDb::new) + .unwrap(); let block_producer = if let Some(block_time) = config.block_time { - BlockProducer::interval( - Arc::clone(&backend), - backend.state.read().await.as_ref_db(), - block_time, - ) + BlockProducer::interval(Arc::clone(&backend), state, block_time) } else if config.no_mining { - BlockProducer::on_demand(Arc::clone(&backend), backend.state.read().await.as_ref_db()) + BlockProducer::on_demand(Arc::clone(&backend), state) } else { BlockProducer::instant(Arc::clone(&backend)) }; @@ -96,18 +99,6 @@ impl KatanaSequencer { } } - async fn verify_contract_exists( - &self, - block_id: &BlockId, - contract_address: &ContractAddress, - ) -> bool { - self.state(block_id) - .await - .unwrap() - .get_class_hash_at(*contract_address) - .is_ok_and(|c| c != ClassHash::default()) - } - pub fn block_producer(&self) -> &BlockProducer { &self.block_producer } @@ -116,261 +107,207 @@ impl KatanaSequencer { &self.backend } - pub async fn state(&self, block_id: &BlockId) -> SequencerResult { + pub fn state(&self, block_id: &BlockIdOrTag) -> SequencerResult> { + let provider = self.backend.blockchain.provider(); + match block_id { - BlockId::Tag(BlockTag::Latest) => Ok(self.backend.state.read().await.as_ref_db()), + BlockIdOrTag::Tag(BlockTag::Latest) => { + let state = StateFactoryProvider::latest(provider)?; + Ok(state) + } - BlockId::Tag(BlockTag::Pending) => { + BlockIdOrTag::Tag(BlockTag::Pending) => { if let Some(state) = self.pending_state() { - Ok(state.state.read().as_ref_db()) + Ok(Box::new(state.state.clone())) } else { - Ok(self.backend.state.read().await.as_ref_db()) + let state = StateFactoryProvider::latest(provider)?; + Ok(state) } } - _ => { - if let Some(hash) = self.backend.blockchain.block_hash(*block_id) { - self.backend - .states - .read() - .await - .get(&hash) - .cloned() - .ok_or(SequencerError::StateNotFound(*block_id)) - } else { - Err(SequencerError::BlockNotFound(*block_id)) - } + BlockIdOrTag::Hash(hash) => { + StateFactoryProvider::historical(provider, BlockHashOrNumber::Hash(*hash))? + .ok_or(SequencerError::BlockNotFound(*block_id)) } - } - } - - pub async fn add_deploy_account_transaction( - &self, - transaction: DeployAccountTransaction, - ) -> (FieldElement, FieldElement) { - let transaction_hash = transaction.inner.transaction_hash.0.into(); - let contract_address = transaction.contract_address; - self.pool.add_transaction(Transaction::DeployAccount(transaction)); - - (transaction_hash, contract_address) - } - - pub fn add_declare_transaction(&self, transaction: DeclareTransaction) { - self.pool.add_transaction(Transaction::Declare(transaction)) + BlockIdOrTag::Number(num) => { + StateFactoryProvider::historical(provider, BlockHashOrNumber::Num(*num))? + .ok_or(SequencerError::BlockNotFound(*block_id)) + } + } } - pub fn add_invoke_transaction(&self, transaction: InvokeTransaction) { - self.pool.add_transaction(Transaction::Invoke(transaction)) + pub fn add_transaction_to_pool(&self, tx: ExecutableTxWithHash) { + self.pool.add_transaction(tx); } - pub async fn estimate_fee( + pub fn estimate_fee( &self, - transactions: Vec, - block_id: BlockId, + transactions: Vec, + block_id: BlockIdOrTag, ) -> SequencerResult> { - let state = self.state(&block_id).await?; - self.backend.estimate_fee(transactions, state).map_err(SequencerError::TransactionExecution) + let state = self.state(&block_id)?; + let block_context = self.backend.env.read().block.clone(); + katana_executor::blockifier::utils::estimate_fee( + transactions.into_iter(), + block_context, + state, + ) + .map_err(SequencerError::TransactionExecution) } - pub async fn block_hash_and_number(&self) -> (FieldElement, u64) { - let hash = self.backend.blockchain.storage.read().latest_hash; - let number = self.backend.blockchain.storage.read().latest_number; - (hash, number) + pub fn block_hash_and_number(&self) -> SequencerResult<(BlockHash, BlockNumber)> { + let provider = self.backend.blockchain.provider(); + let hash = BlockHashProvider::latest_hash(provider)?; + let number = BlockNumberProvider::latest_number(provider)?; + Ok((hash, number)) } - pub async fn class_hash_at( + pub fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: ContractAddress, - ) -> SequencerResult { - if !self.verify_contract_exists(&block_id, &contract_address).await { - return Err(SequencerError::ContractNotFound(contract_address)); - } - - let mut state = self.state(&block_id).await?; - state.get_class_hash_at(contract_address).map_err(SequencerError::State) + ) -> SequencerResult> { + let state = self.state(&block_id)?; + let class_hash = StateProvider::class_hash_of_contract(&state, contract_address)?; + Ok(class_hash) } - pub async fn class( + pub fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: ClassHash, - ) -> SequencerResult { - let mut state = self.state(&block_id).await?; + ) -> SequencerResult> { + let state = self.state(&block_id)?; - if let ContractClass::V0(c) = - state.get_compiled_contract_class(&class_hash).map_err(SequencerError::State)? - { - Ok(StarknetContract::Legacy(c)) - } else { - state - .get_sierra_class(&class_hash) - .map(StarknetContract::Sierra) - .map_err(SequencerError::State) + let Some(class) = ContractClassProvider::class(&state, class_hash)? else { + return Ok(None); + }; + + match class { + CompiledContractClass::V0(class) => Ok(Some(StarknetContract::Legacy(class))), + CompiledContractClass::V1(_) => { + let class = ContractClassProvider::sierra_class(&state, class_hash)? + .map(StarknetContract::Sierra); + Ok(class) + } } } - pub async fn storage_at( + pub fn storage_at( &self, contract_address: ContractAddress, storage_key: StorageKey, - block_id: BlockId, - ) -> SequencerResult { - if !self.verify_contract_exists(&block_id, &contract_address).await { + block_id: BlockIdOrTag, + ) -> SequencerResult { + let state = self.state(&block_id)?; + let Some(value) = StateProvider::storage(&state, contract_address, storage_key)? else { return Err(SequencerError::ContractNotFound(contract_address)); - } - - let mut state = self.state(&block_id).await?; - state.get_storage_at(contract_address, storage_key).map_err(SequencerError::State) + }; + Ok(value) } - pub async fn chain_id(&self) -> ChainId { + pub fn chain_id(&self) -> ChainId { self.backend.env.read().block.chain_id.clone() } - pub async fn block_number(&self) -> u64 { - self.backend.blockchain.storage.read().latest_number + pub fn block_number(&self) -> BlockNumber { + BlockNumberProvider::latest_number(&self.backend.blockchain.provider()).unwrap() } - pub async fn block(&self, block_id: BlockId) -> Option { - let block_id = match block_id { - BlockId::Tag(BlockTag::Pending) if self.block_producer.is_instant_mining() => { - BlockId::Tag(BlockTag::Latest) + pub fn block_tx_count(&self, block_id: BlockIdOrTag) -> SequencerResult> { + let provider = self.backend.blockchain.provider(); + + let count = match block_id { + BlockIdOrTag::Tag(BlockTag::Pending) => match self.pending_state() { + Some(state) => Some(state.executed_txs.read().len() as u64), + None => { + let hash = BlockHashProvider::latest_hash(provider)?; + TransactionProvider::transaction_count_by_block(provider, hash.into())? + } + }, + + BlockIdOrTag::Tag(BlockTag::Latest) => { + let num = BlockNumberProvider::latest_number(provider)?; + TransactionProvider::transaction_count_by_block(provider, num.into())? } - _ => block_id, - }; - match block_id { - BlockId::Tag(BlockTag::Pending) => { - let state = self.pending_state().expect("pending state should exist"); - - let block_context = self.backend.env.read().block.clone(); - let latest_hash = self.backend.blockchain.storage.read().latest_hash; - - let header = PartialHeader { - parent_hash: latest_hash, - gas_price: block_context.gas_price, - number: block_context.block_number.0, - timestamp: block_context.block_timestamp.0, - sequencer_address: (*block_context.sequencer_address.0.key()).into(), - }; - - let (transactions, outputs) = { - state - .executed_transactions - .read() - .iter() - .filter_map(|tx| match tx { - MaybeInvalidExecutedTransaction::Valid(tx) => { - Some((tx.clone(), tx.output.clone())) - } - _ => None, - }) - .unzip() - }; - - Some(ExecutedBlock::Pending(PartialBlock { header, transactions, outputs })) + BlockIdOrTag::Number(num) => { + TransactionProvider::transaction_count_by_block(provider, num.into())? } - _ => { - let hash = self.backend.blockchain.block_hash(block_id)?; - self.backend.blockchain.storage.read().blocks.get(&hash).map(|b| b.clone().into()) + BlockIdOrTag::Hash(hash) => { + TransactionProvider::transaction_count_by_block(provider, hash.into())? } - } + }; + + Ok(count) } pub async fn nonce_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: ContractAddress, - ) -> SequencerResult { - if !self.verify_contract_exists(&block_id, &contract_address).await { - return Err(SequencerError::ContractNotFound(contract_address)); - } - - let mut state = self.state(&block_id).await?; - state.get_nonce_at(contract_address).map_err(SequencerError::State) + ) -> SequencerResult> { + let state = self.state(&block_id)?; + let nonce = StateProvider::nonce(&state, contract_address)?; + Ok(nonce) } - pub async fn call( + pub fn call( &self, - block_id: BlockId, - function_call: ExternalFunctionCall, - ) -> SequencerResult> { - if !self.verify_contract_exists(&block_id, &function_call.contract_address).await { - return Err(SequencerError::ContractNotFound(function_call.contract_address)); - } - - let state = self.state(&block_id).await?; - - self.backend - .call(function_call, state) - .map_err(SequencerError::EntryPointExecution) - .map(|execution_info| execution_info.execution.retdata.0) + request: EntryPointCall, + block_id: BlockIdOrTag, + ) -> SequencerResult> { + let state = self.state(&block_id)?; + let block_context = self.backend.env.read().block.clone(); + + let retdata = katana_executor::blockifier::utils::call(request, block_context, state) + .map_err(|e| match e { + EntryPointExecutionError::PreExecutionError( + PreExecutionError::UninitializedStorageAddress(addr), + ) => SequencerError::ContractNotFound(addr.into()), + + _ => SequencerError::EntryPointExecution(e), + })?; + + Ok(retdata) } - pub async fn transaction_receipt( - &self, - hash: &FieldElement, - ) -> Option { - let transaction = self.transaction(hash).await?; - - match transaction { - KnownTransaction::Rejected(_) => None, - KnownTransaction::Pending(tx) => { - Some(MaybePendingTransactionReceipt::PendingReceipt(tx.receipt())) - } - KnownTransaction::Included(tx) => { - Some(MaybePendingTransactionReceipt::Receipt(tx.receipt())) - } - } - } + pub fn transaction(&self, hash: &TxHash) -> SequencerResult> { + let tx = + TransactionProvider::transaction_by_hash(self.backend.blockchain.provider(), *hash)?; + + let tx @ Some(_) = tx else { + return Ok(self.pending_state().as_ref().and_then(|state| { + state + .executed_txs + .read() + .iter() + .find_map(|tx| if tx.0.hash == *hash { Some(tx.0.clone()) } else { None }) + })); + }; - pub async fn transaction(&self, hash: &FieldElement) -> Option { - let tx = self.backend.blockchain.storage.read().transactions.get(hash).cloned(); - match tx { - Some(tx) => Some(tx), - // If the requested transaction is not available in the storage then - // check if it is available in the pending block. - None => self.pending_state().as_ref().and_then(|state| { - state.executed_transactions.read().iter().find_map(|tx| match tx { - MaybeInvalidExecutedTransaction::Valid(tx) if tx.inner.hash() == *hash => { - Some(PendingTransaction(tx.clone()).into()) - } - MaybeInvalidExecutedTransaction::Invalid(tx) if tx.inner.hash() == *hash => { - Some(tx.as_ref().clone().into()) - } - _ => None, - }) - }), - } + Ok(tx) } pub async fn events( &self, - from_block: BlockId, - to_block: BlockId, - address: Option, + from_block: BlockIdOrTag, + to_block: BlockIdOrTag, + address: Option, keys: Option>>, continuation_token: Option, chunk_size: u64, ) -> SequencerResult { + let provider = self.backend.blockchain.provider(); let mut current_block = 0; let (mut from_block, to_block) = { - let storage = &self.backend.blockchain; - - let from = storage - .block_hash(from_block) - .and_then(|hash| storage.storage.read().blocks.get(&hash).map(|b| b.header.number)) - .ok_or(SequencerError::BlockNotFound(from_block))?; - - let to = storage - .block_hash(to_block) - .and_then(|hash| storage.storage.read().blocks.get(&hash).map(|b| b.header.number)) + let from = BlockIdReader::convert_block_id(provider, from_block)? + .ok_or(SequencerError::BlockNotFound(to_block))?; + let to = BlockIdReader::convert_block_id(provider, to_block)? .ok_or(SequencerError::BlockNotFound(to_block))?; - (from, to) }; @@ -385,39 +322,30 @@ impl KatanaSequencer { let mut filtered_events = Vec::with_capacity(chunk_size as usize); for i in from_block..=to_block { - let block = self - .backend - .blockchain - .storage - .read() - .block_by_number(i) - .cloned() - .ok_or(SequencerError::BlockNotFound(BlockId::Number(i)))?; - - // to get the current block hash we need to get the parent hash of the next block - // if the current block is the latest block then we use the latest hash - let block_hash = self - .backend - .blockchain - .storage - .read() - .block_by_number(i + 1) - .map(|b| b.header.parent_hash) - .unwrap_or(self.backend.blockchain.storage.read().latest_hash); - - let block_number = i; - - let txn_n = block.transactions.len(); + let block_hash = BlockHashProvider::block_hash_by_num(provider, i)? + .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; + + let receipts = ReceiptProvider::receipts_by_block(provider, BlockHashOrNumber::Num(i))? + .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; + + let tx_range = BlockProvider::block_body_indices(provider, BlockHashOrNumber::Num(i))? + .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; + let tx_hashes = + TransactionsProviderExt::transaction_hashes_in_range(provider, tx_range.into())?; + + let txn_n = receipts.len(); if (txn_n as u64) < continuation_token.txn_n { return Err(SequencerError::ContinuationToken( ContinuationTokenError::InvalidToken, )); } - for (txn_output, txn) in - block.outputs.iter().zip(block.transactions).skip(continuation_token.txn_n as usize) + for (tx_hash, events) in tx_hashes + .into_iter() + .zip(receipts.iter().map(|r| r.events())) + .skip(continuation_token.txn_n as usize) { - let txn_events_len: usize = txn_output.events.len(); + let txn_events_len: usize = events.len(); // check if continuation_token.event_n is correct match (txn_events_len as u64).cmp(&continuation_token.event_n) { @@ -435,7 +363,7 @@ impl KatanaSequencer { } // skip events - let txn_events = txn_output.events.iter().skip(continuation_token.event_n as usize); + let txn_events = events.iter().skip(continuation_token.event_n as usize); let (new_filtered_events, continuation_index) = filter_events_by_params( txn_events, @@ -449,8 +377,8 @@ impl KatanaSequencer { keys: e.keys.clone(), data: e.data.clone(), block_hash, - block_number, - transaction_hash: txn.inner.hash(), + block_number: i, + transaction_hash: tx_hash, })); if filtered_events.len() >= chunk_size as usize { @@ -478,68 +406,46 @@ impl KatanaSequencer { Ok(EventsPage { events: filtered_events, continuation_token: None }) } - pub async fn state_update(&self, block_id: BlockId) -> SequencerResult { - let block_number = self - .backend - .blockchain - .block_hash(block_id) - .ok_or(SequencerError::BlockNotFound(block_id))?; - - self.backend - .blockchain - .storage - .read() - .state_update - .get(&block_number) - .cloned() - .ok_or(SequencerError::StateUpdateNotFound(block_id)) - } - - pub async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { + pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { + if self.has_pending_transactions() { return Err(SequencerError::PendingTransactions); } self.backend().block_context_generator.write().next_block_start_time = timestamp; Ok(()) } - pub async fn increase_next_block_timestamp( - &self, - timestamp: u64, - ) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { + pub fn increase_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { + if self.has_pending_transactions() { return Err(SequencerError::PendingTransactions); } self.backend().block_context_generator.write().block_timestamp_offset += timestamp as i64; Ok(()) } - pub async fn has_pending_transactions(&self) -> bool { + pub fn has_pending_transactions(&self) -> bool { if let Some(ref pending) = self.pending_state() { - !pending.executed_transactions.read().is_empty() + !pending.executed_txs.read().is_empty() } else { false } } - pub async fn set_storage_at( - &self, - contract_address: ContractAddress, - storage_key: StorageKey, - value: StarkFelt, - ) -> Result<(), SequencerError> { - if let Some(ref pending) = self.pending_state() { - pending.state.write().set_storage_at(contract_address, storage_key, value); - } else { - self.backend().state.write().await.set_storage_at(contract_address, storage_key, value); - } - Ok(()) - } + // pub async fn set_storage_at( + // &self, + // contract_address: ContractAddress, + // storage_key: StorageKey, + // value: StorageValue, + // ) -> Result<(), SequencerError> { + // if let Some(ref pending) = self.pending_state() { + // StateWriter::set_storage(&pending.state, contract_address, storage_key, value)?; + // } + // Ok(()) + // } } fn filter_events_by_params( events: Skip>, - address: Option, + address: Option, filter_keys: Option>>, max_results: Option, ) -> (Vec, usize) { @@ -549,7 +455,7 @@ fn filter_events_by_params( // Iterate on block events. for event in events { index += 1; - if !address.map_or(true, |addr| addr == event.from_address) { + if !address.map_or(true, |addr| addr == event.from_address.into()) { continue; } diff --git a/crates/katana/core/src/sequencer_error.rs b/crates/katana/core/src/sequencer_error.rs index 67dae0e220..6b58f0540d 100644 --- a/crates/katana/core/src/sequencer_error.rs +++ b/crates/katana/core/src/sequencer_error.rs @@ -1,9 +1,9 @@ use blockifier::execution::errors::EntryPointExecutionError; use blockifier::state::errors::StateError; use blockifier::transaction::errors::TransactionExecutionError; -use starknet::core::types::BlockId; -use starknet_api::core::ContractAddress; -use starknet_api::transaction::TransactionHash; +use katana_primitives::block::BlockIdOrTag; +use katana_primitives::contract::ContractAddress; +use katana_primitives::transaction::TxHash; use starknet_api::StarknetApiError; use crate::utils::event::ContinuationTokenError; @@ -11,15 +11,15 @@ use crate::utils::event::ContinuationTokenError; #[derive(Debug, thiserror::Error)] pub enum SequencerError { #[error("Block {0:?} not found.")] - BlockNotFound(BlockId), + BlockNotFound(BlockIdOrTag), #[error("Contract address {0:?} not found.")] ContractNotFound(ContractAddress), #[error("State update for block {0:?} not found.")] - StateUpdateNotFound(BlockId), + StateUpdateNotFound(BlockIdOrTag), #[error("State for block {0:?} not found.")] - StateNotFound(BlockId), + StateNotFound(BlockIdOrTag), #[error("Transaction with {0} hash not found.")] - TxnNotFound(TransactionHash), + TxnNotFound(TxHash), #[error(transparent)] State(#[from] StateError), #[error(transparent)] @@ -42,4 +42,6 @@ pub enum SequencerError { DataUnavailable, #[error("Failed to decode state")] FailedToDecodeStateDump, + #[error(transparent)] + Other(#[from] anyhow::Error), } diff --git a/crates/katana/core/src/service/block_producer.rs b/crates/katana/core/src/service/block_producer.rs index 1f03d8c95a..af123eba45 100644 --- a/crates/katana/core/src/service/block_producer.rs +++ b/crates/katana/core/src/service/block_producer.rs @@ -1,29 +1,28 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use blockifier::state::state_api::{State, StateReader}; use futures::stream::{Stream, StreamExt}; use futures::FutureExt; +use katana_executor::blockifier::outcome::TxReceiptWithExecInfo; +use katana_executor::blockifier::state::{CachedStateWrapper, StateRefDb}; +use katana_executor::blockifier::utils::get_state_update_from_cached_state; +use katana_executor::blockifier::{PendingState, TransactionExecutor}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::StateUpdatesWithDeclaredClasses; +use katana_primitives::transaction::{ExecutableTxWithHash, TxWithHash}; +use katana_provider::traits::state::StateFactoryProvider; use parking_lot::RwLock; use tokio::time::{interval_at, Instant, Interval}; use tracing::trace; -use crate::backend::storage::transaction::{RejectedTransaction, Transaction}; use crate::backend::Backend; -use crate::db::cached::CachedStateWrapper; -use crate::db::StateRefDb; -use crate::execution::{ - create_execution_outcome, ExecutedTransaction, ExecutionOutcome, - MaybeInvalidExecutedTransaction, PendingState, TransactionExecutor, -}; pub struct MinedBlockOutcome { pub block_number: u64, - pub transactions: Vec, } type ServiceFuture = Pin + Send + Sync>>; @@ -70,7 +69,7 @@ impl BlockProducer { } } - pub(super) fn queue(&self, transactions: Vec) { + pub(super) fn queue(&self, transactions: Vec) { let mut mode = self.inner.write(); match &mut *mode { BlockProducerMode::Instant(producer) => producer.queued.push_back(transactions), @@ -93,12 +92,8 @@ impl BlockProducer { trace!(target: "miner", "force mining"); let mut mode = self.inner.write(); match &mut *mode { - BlockProducerMode::Instant(producer) => { - tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) - } - BlockProducerMode::Interval(producer) => { - tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) - } + BlockProducerMode::Instant(producer) => producer.force_mine(), + BlockProducerMode::Interval(producer) => producer.force_mine(), } } } @@ -139,7 +134,7 @@ pub struct IntervalBlockProducer { /// Single active future that mines a new block block_mining: Option, /// Backlog of sets of transactions ready to be mined - queued: VecDeque>, + queued: VecDeque>, /// The state of the pending block after executing all the transactions within the interval. state: Arc, /// This is to make sure that the block context is updated @@ -157,8 +152,8 @@ impl IntervalBlockProducer { }; let state = Arc::new(PendingState { - state: RwLock::new(CachedStateWrapper::new(db)), - executed_transactions: Default::default(), + state: Arc::new(CachedStateWrapper::new(db)), + executed_txs: Default::default(), }); Self { @@ -176,8 +171,8 @@ impl IntervalBlockProducer { /// keep hold of the pending state. pub fn new_no_mining(backend: Arc, db: StateRefDb) -> Self { let state = Arc::new(PendingState { - state: RwLock::new(CachedStateWrapper::new(db)), - executed_transactions: Default::default(), + state: Arc::new(CachedStateWrapper::new(db)), + executed_txs: Default::default(), }); Self { @@ -195,84 +190,62 @@ impl IntervalBlockProducer { } /// Force mine a new block. It will only able to mine if there is no ongoing mining process. - pub async fn force_mine(&self) { + pub fn force_mine(&self) { if self.block_mining.is_none() { let outcome = self.outcome(); - let _ = Self::do_mine(outcome, self.backend.clone(), self.state.clone()).await; + let _ = Self::do_mine(outcome, self.backend.clone(), self.state.clone()); } else { trace!(target: "miner", "unable to force mine while a mining process is running") } } - async fn do_mine( - execution_outcome: ExecutionOutcome, + fn do_mine( + state_updates: StateUpdatesWithDeclaredClasses, backend: Arc, pending_state: Arc, ) -> MinedBlockOutcome { trace!(target: "miner", "creating new block"); - let (outcome, new_state) = backend.mine_pending_block(execution_outcome).await; + + let tx_receipt_pairs = { + let mut txs_lock = pending_state.executed_txs.write(); + txs_lock.drain(..).map(|(tx, rct)| (tx, rct.receipt)).collect::>() + }; + + let (outcome, new_state) = backend.mine_pending_block(tx_receipt_pairs, state_updates); trace!(target: "miner", "created new block: {}", outcome.block_number); backend.update_block_context(); - // reset the state for the next block - pending_state.executed_transactions.write().clear(); - *pending_state.state.write() = CachedStateWrapper::new(new_state); + pending_state.state.reset_with_new_state(new_state.into()); outcome } - fn execute_transactions(&self, transactions: Vec) { - let transactions = { - let mut state = self.state.state.write(); + fn execute_transactions(&self, transactions: Vec) { + let txs = transactions.iter().map(TxWithHash::from); + let results = { TransactionExecutor::new( - &mut state, + &self.state.state, &self.backend.env.read().block, !self.backend.config.read().disable_fee, - transactions.clone(), + transactions.clone().into_iter(), ) .with_error_log() .with_events_log() .with_resources_log() - .zip(transactions) - .map(|(res, tx)| match res { - Ok(execution_info) => { - let executed_tx = ExecutedTransaction::new(tx, execution_info); - MaybeInvalidExecutedTransaction::Valid(Arc::new(executed_tx)) - } - Err(err) => { - let rejected_tx = - RejectedTransaction { inner: tx, execution_error: err.to_string() }; - MaybeInvalidExecutedTransaction::Invalid(Arc::new(rejected_tx)) - } + .zip(txs) + .filter_map(|(res, tx)| { + let Ok(info) = res else { return None }; + let receipt = TxReceiptWithExecInfo::from_tx_exec_result(&tx, info); + Some((tx, receipt)) }) .collect::>() }; - self.state.executed_transactions.write().extend(transactions); + self.state.executed_txs.write().extend(results); } - fn outcome(&self) -> ExecutionOutcome { - let state = &mut self.state.state.write(); - - let declared_sierra_classes = state.sierra_class().clone(); - let state_diff = state.to_state_diff(); - let declared_classes = state_diff - .class_hash_to_compiled_class_hash - .iter() - .map(|(class_hash, _)| { - let contract_class = state - .get_compiled_contract_class(class_hash) - .expect("contract class must exist in state if declared"); - (*class_hash, contract_class) - }) - .collect::>(); - - ExecutionOutcome { - state_diff, - declared_classes, - declared_sierra_classes, - transactions: self.state.executed_transactions.read().clone(), - } + fn outcome(&self) -> StateUpdatesWithDeclaredClasses { + get_state_update_from_cached_state(&self.state.state) } } @@ -290,11 +263,15 @@ impl Stream for IntervalBlockProducer { if let Some(interval) = &mut pin.interval { if interval.poll_tick(cx).is_ready() && pin.block_mining.is_none() { - pin.block_mining = Some(Box::pin(Self::do_mine( - pin.outcome(), - pin.backend.clone(), - pin.state.clone(), - ))); + let backend = pin.backend.clone(); + let outcome = pin.outcome(); + let state = pin.state.clone(); + + pin.block_mining = Some(Box::pin(async move { + tokio::task::spawn_blocking(|| Self::do_mine(outcome, backend, state)) + .await + .unwrap() + })); } } @@ -324,7 +301,7 @@ pub struct InstantBlockProducer { /// Single active future that mines a new block block_mining: Option, /// Backlog of sets of transactions ready to be mined - queued: VecDeque>, + queued: VecDeque>, } impl InstantBlockProducer { @@ -332,40 +309,55 @@ impl InstantBlockProducer { Self { backend, block_mining: None, queued: VecDeque::default() } } - pub async fn force_mine(&mut self) { + pub fn force_mine(&mut self) { if self.block_mining.is_none() { let txs = self.queued.pop_front().unwrap_or_default(); - let _ = Self::do_mine(self.backend.clone(), txs).await; + let _ = Self::do_mine(self.backend.clone(), txs); } else { trace!(target: "miner", "unable to force mine while a mining process is running") } } - async fn do_mine(backend: Arc, transactions: Vec) -> MinedBlockOutcome { + fn do_mine( + backend: Arc, + transactions: Vec, + ) -> MinedBlockOutcome { trace!(target: "miner", "creating new block"); backend.update_block_context(); - let mut state = CachedStateWrapper::new(backend.state.read().await.as_ref_db()); + let latest_state = StateFactoryProvider::latest(backend.blockchain.provider()) + .expect("able to get latest state"); + let state = CachedStateWrapper::new(latest_state.into()); let block_context = backend.env.read().block.clone(); - let results = TransactionExecutor::new( - &mut state, + let txs = transactions.iter().map(TxWithHash::from); + + let tx_receipt_pairs: Vec<(TxWithHash, Receipt)> = TransactionExecutor::new( + &state, &block_context, !backend.config.read().disable_fee, - transactions.clone(), + transactions.clone().into_iter(), ) .with_error_log() .with_events_log() .with_resources_log() - .execute(); + .zip(txs) + .filter_map(|(res, tx)| { + if let Ok(info) = res { + let receipt = TxReceiptWithExecInfo::from_tx_exec_result(&tx, info); + Some((tx, receipt.receipt)) + } else { + None + } + }) + .collect(); - let outcome = backend - .do_mine_block(create_execution_outcome( - &mut state, - transactions.into_iter().zip(results).collect(), - )) - .await; + let outcome = backend.do_mine_block( + block_context, + tx_receipt_pairs, + get_state_update_from_cached_state(&state), + ); trace!(target: "miner", "created new block: {}", outcome.block_number); @@ -382,7 +374,11 @@ impl Stream for InstantBlockProducer { if !pin.queued.is_empty() && pin.block_mining.is_none() { let transactions = pin.queued.pop_front().expect("not empty; qed"); - pin.block_mining = Some(Box::pin(Self::do_mine(pin.backend.clone(), transactions))); + let backend = pin.backend.clone(); + + pin.block_mining = Some(Box::pin(async move { + tokio::task::spawn_blocking(|| Self::do_mine(backend, transactions)).await.unwrap() + })); } // poll the mining future diff --git a/crates/katana/core/src/service/messaging/ethereum.rs b/crates/katana/core/src/service/messaging/ethereum.rs index d033bcd662..ee9e796099 100644 --- a/crates/katana/core/src/service/messaging/ethereum.rs +++ b/crates/katana/core/src/service/messaging/ethereum.rs @@ -8,19 +8,13 @@ use ethers::prelude::*; use ethers::providers::{Http, Provider}; use ethers::types::{Address, BlockNumber, Log}; use k256::ecdsa::SigningKey; +use katana_primitives::transaction::L1HandlerTx; +use katana_primitives::FieldElement; use sha3::{Digest, Keccak256}; -use starknet::core::types::{FieldElement, MsgToL1}; -use starknet_api::core::{ContractAddress, EntryPointSelector, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::stark_felt; -use starknet_api::transaction::{ - Calldata, L1HandlerTransaction as ApiL1HandlerTransaction, TransactionHash, TransactionVersion, -}; +use starknet::core::types::MsgToL1; use tracing::{debug, error, trace, warn}; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; -use crate::backend::storage::transaction::L1HandlerTransaction; -use crate::utils::transaction::compute_l1_handler_transaction_hash; abigen!( StarknetMessagingLocal, @@ -127,7 +121,7 @@ impl EthereumMessaging { #[async_trait] impl Messenger for EthereumMessaging { type MessageHash = U256; - type MessageTransaction = L1HandlerTransaction; + type MessageTransaction = L1HandlerTx; async fn gather_messages( &self, @@ -209,39 +203,30 @@ impl Messenger for EthereumMessaging { } } -fn l1_handler_tx_from_log( - log: Log, - chain_id: FieldElement, -) -> MessengerResult { +fn l1_handler_tx_from_log(log: Log, chain_id: FieldElement) -> MessengerResult { let parsed_log = ::decode_log(&log.into()).map_err(|e| { error!(target: LOG_TARGET, "Log parsing failed {e}"); Error::GatherError })?; - let from_address = stark_felt_from_address(parsed_log.from_address); - let contract_address = stark_felt_from_u256(parsed_log.to_address); - let selector = stark_felt_from_u256(parsed_log.selector); - let nonce = stark_felt_from_u256(parsed_log.nonce); - let paid_l1_fee: u128 = parsed_log.fee.try_into().expect("Fee does not fit into u128."); - - let mut calldata_vec = vec![from_address]; - calldata_vec.extend(parsed_log.payload.into_iter().map(stark_felt_from_u256)); - - let mut inner = ApiL1HandlerTransaction { - nonce: Nonce(nonce), - calldata: Calldata(calldata_vec.into()), - transaction_hash: TransactionHash::default(), - version: TransactionVersion(stark_felt!(0_u32)), - entry_point_selector: EntryPointSelector(selector), - contract_address: ContractAddress::try_from(contract_address).unwrap(), - }; - - inner.transaction_hash = - TransactionHash(compute_l1_handler_transaction_hash(inner.clone(), chain_id).into()); - - let tx = L1HandlerTransaction { paid_l1_fee, inner }; - - Ok(tx) + let from_address = felt_from_address(parsed_log.from_address); + let contract_address = felt_from_u256(parsed_log.to_address); + let entry_point_selector = felt_from_u256(parsed_log.selector); + let nonce = felt_from_u256(parsed_log.nonce); + let paid_fee_on_l1: u128 = parsed_log.fee.try_into().expect("Fee does not fit into u128."); + + let mut calldata = vec![from_address]; + calldata.extend(parsed_log.payload.into_iter().map(felt_from_u256)); + + Ok(L1HandlerTx { + nonce, + calldata, + chain_id, + paid_fee_on_l1, + entry_point_selector, + version: FieldElement::ZERO, + contract_address: contract_address.into(), + }) } /// With Ethereum, the messages are following the conventional starknet messaging. @@ -264,12 +249,12 @@ fn parse_messages(messages: &[MsgToL1]) -> Vec { .collect() } -fn stark_felt_from_u256(v: U256) -> StarkFelt { - stark_felt!(format!("{:#064x}", v).as_str()) +fn felt_from_u256(v: U256) -> FieldElement { + FieldElement::from_str(format!("{:#064x}", v).as_str()).unwrap() } -fn stark_felt_from_address(v: Address) -> StarkFelt { - stark_felt!(format!("{:#064x}", v).as_str()) +fn felt_from_address(v: Address) -> FieldElement { + FieldElement::from_str(format!("{:#064x}", v).as_str()).unwrap() } #[cfg(test)] @@ -279,69 +264,71 @@ mod tests { use super::*; - #[test] - fn l1_handler_tx_from_log_parse_ok() { - let from_address = "0x000000000000000000000000be3C44c09bc1a3566F3e1CA12e5AbA0fA4Ca72Be"; - let to_address = "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1"; - let selector = "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f"; - let nonce = 783082_u128; - let fee = 30000_u128; - - // Payload two values: [1, 2]. - let payload_buf = hex::decode("000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bf2ea0000000000000000000000000000000000000000000000000000000000007530000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002").unwrap(); - - let calldata: Vec = vec![ - FieldElement::from_hex_be(from_address).unwrap().into(), - FieldElement::ONE.into(), - FieldElement::TWO.into(), - ]; - - let transaction_hash: FieldElement = FieldElement::from_hex_be( - "0x6182c63599a9638272f1ce5b5cadabece9c81c2d2b8f88ab7a294472b8fce8b", - ) - .unwrap(); - - let log = Log { - address: H160::from_str("0xde29d060D45901Fb19ED6C6e959EB22d8626708e").unwrap(), - topics: vec![ - H256::from_str( - "0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b", - ) - .unwrap(), - H256::from_str(from_address).unwrap(), - H256::from_str(to_address).unwrap(), - H256::from_str(selector).unwrap(), - ], - data: payload_buf.into(), - ..Default::default() - }; - - let expected = L1HandlerTransaction { - inner: ApiL1HandlerTransaction { - transaction_hash: TransactionHash(transaction_hash.into()), - version: TransactionVersion(stark_felt!(0_u32)), - nonce: Nonce(FieldElement::from(nonce).into()), - contract_address: ContractAddress::try_from( - >::into( - FieldElement::from_hex_be(to_address).unwrap(), - ), - ) - .unwrap(), - entry_point_selector: EntryPointSelector( - FieldElement::from_hex_be(selector).unwrap().into(), - ), - calldata: Calldata(calldata.into()), - }, - paid_l1_fee: fee, - }; - - // SN_GOERLI. - let chain_id = starknet::macros::felt!("0x534e5f474f45524c49"); - let tx: L1HandlerTransaction = - l1_handler_tx_from_log(log, chain_id).expect("bad log format"); - - assert_eq!(tx.inner, expected.inner); - } + // #[test] + // fn l1_handler_tx_from_log_parse_ok() { + // let from_address = "0x000000000000000000000000be3C44c09bc1a3566F3e1CA12e5AbA0fA4Ca72Be"; + // let to_address = "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1"; + // let selector = "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f"; + // let nonce = 783082_u128; + // let fee = 30000_u128; + + // // Payload two values: [1, 2]. + // let payload_buf = + // hex::decode(" + // 000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bf2ea0000000000000000000000000000000000000000000000000000000000007530000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" + // ).unwrap(); + + // let calldata: Vec = vec![ + // FieldElement::from_hex_be(from_address).unwrap().into(), + // FieldElement::ONE.into(), + // FieldElement::TWO.into(), + // ]; + + // let transaction_hash: FieldElement = FieldElement::from_hex_be( + // "0x6182c63599a9638272f1ce5b5cadabece9c81c2d2b8f88ab7a294472b8fce8b", + // ) + // .unwrap(); + + // let log = Log { + // address: H160::from_str("0xde29d060D45901Fb19ED6C6e959EB22d8626708e").unwrap(), + // topics: vec![ + // H256::from_str( + // "0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b", + // ) + // .unwrap(), + // H256::from_str(from_address).unwrap(), + // H256::from_str(to_address).unwrap(), + // H256::from_str(selector).unwrap(), + // ], + // data: payload_buf.into(), + // ..Default::default() + // }; + + // let expected = L1HandlerTransaction { + // inner: ApiL1HandlerTransaction { + // transaction_hash: TransactionHash(transaction_hash.into()), + // version: TransactionVersion(stark_felt!(0_u32)), + // nonce: Nonce(FieldElement::from(nonce).into()), + // contract_address: ContractAddress::try_from( + // >::into( + // FieldElement::from_hex_be(to_address).unwrap(), + // ), + // ) + // .unwrap(), + // entry_point_selector: EntryPointSelector( + // FieldElement::from_hex_be(selector).unwrap().into(), + // ), + // calldata: Calldata(calldata.into()), + // }, + // paid_l1_fee: fee, + // }; + + // // SN_GOERLI. + // let chain_id = starknet::macros::felt!("0x534e5f474f45524c49"); + // let tx = l1_handler_tx_from_log(log, chain_id).expect("bad log format"); + + // assert_eq!(tx.inner, expected.inner); + // } #[test] fn parse_msg_to_l1() { diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 901999e437..76d6b48f52 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -5,11 +5,14 @@ use std::time::Duration; use ::starknet::core::types::{FieldElement, MsgToL1}; use futures::{Future, FutureExt, Stream}; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx, TxHash}; +use katana_provider::traits::block::BlockNumberProvider; +use katana_provider::traits::transaction::ReceiptProvider; use tokio::time::{interval_at, Instant, Interval}; use tracing::{error, info}; use super::{MessagingConfig, Messenger, MessengerMode, MessengerResult, LOG_TARGET}; -use crate::backend::storage::transaction::{L1HandlerTransaction, Transaction}; use crate::backend::Backend; use crate::pool::TransactionPool; @@ -86,8 +89,9 @@ impl MessagingService { let txs_count = txs.len(); txs.into_iter().for_each(|tx| { - trace_l1_handler_tx_exec(&tx); - pool.add_transaction(Transaction::L1Handler(tx)) + let hash = tx.calculate_hash(); + trace_l1_handler_tx_exec(hash, &tx); + pool.add_transaction(ExecutableTxWithHash { hash, transaction: tx.into() }) }); Ok((block_num, txs_count)) @@ -100,8 +104,9 @@ impl MessagingService { let txs_count = txs.len(); txs.into_iter().for_each(|tx| { - trace_l1_handler_tx_exec(&tx); - pool.add_transaction(Transaction::L1Handler(tx)) + let hash = tx.calculate_hash(); + trace_l1_handler_tx_exec(hash, &tx); + pool.add_transaction(ExecutableTxWithHash { hash, transaction: tx.into() }) }); Ok((block_num, txs_count)) @@ -114,16 +119,12 @@ impl MessagingService { backend: Arc, messenger: Arc, ) -> MessengerResult> { - let Some(messages) = backend - .blockchain - .storage - .read() - .block_by_number(block_num) - .map(|block| &block.outputs) - .map(|outputs| { - outputs.iter().flat_map(|o| o.messages_sent.clone()).collect::>() - }) - else { + let Some(messages) = ReceiptProvider::receipts_by_block( + backend.blockchain.provider(), + BlockHashOrNumber::Num(block_num), + ) + .unwrap() + .map(|r| r.iter().flat_map(|r| r.messages_sent().to_vec()).collect::>()) else { return Ok(None); }; @@ -186,7 +187,8 @@ impl Stream for MessagingService { } if pin.msg_send_fut.is_none() { - let local_latest_block_num = pin.backend.blockchain.storage.read().latest_number; + let local_latest_block_num = + BlockNumberProvider::latest_number(pin.backend.blockchain.provider()).unwrap(); if pin.send_from_block <= local_latest_block_num { pin.msg_send_fut = Some(Box::pin(Self::send_messages( pin.send_from_block, @@ -312,23 +314,22 @@ fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { } } -fn trace_l1_handler_tx_exec(tx: &L1HandlerTransaction) { - let calldata_str: Vec = - tx.inner.calldata.0.iter().map(|f| format!("{:#x}", FieldElement::from(*f))).collect(); +fn trace_l1_handler_tx_exec(hash: TxHash, tx: &L1HandlerTx) { + let calldata_str: Vec<_> = tx.calldata.iter().map(|f| format!("{f:#x}")).collect(); #[rustfmt::skip] info!( target: LOG_TARGET, r"L1Handler transaction added to the pool: | tx_hash | {:#x} -| contract_address | {:#x} +| contract_address | {} | selector | {:#x} | calldata | [{}] ", - FieldElement::from(tx.inner.transaction_hash.0), - FieldElement::from(*tx.inner.contract_address.0.key()), - FieldElement::from(tx.inner.entry_point_selector.0), +hash, + tx.contract_address, + tx.entry_point_selector, calldata_str.join(", ") ); } diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 0b5d633c27..e20d52ecab 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; +use katana_primitives::transaction::L1HandlerTx; use starknet::accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter, FieldElement, MsgToL1}; use starknet::core::utils::starknet_keccak; @@ -10,18 +11,10 @@ use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; use starknet::signers::{LocalWallet, SigningKey}; -use starknet_api::core::{ContractAddress, EntryPointSelector, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::stark_felt; -use starknet_api::transaction::{ - Calldata, L1HandlerTransaction as ApiL1HandlerTransaction, TransactionHash, TransactionVersion, -}; use tracing::{debug, error, trace, warn}; use url::Url; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; -use crate::backend::storage::transaction::L1HandlerTransaction; -use crate::utils::transaction::compute_l1_handler_transaction_hash_felts; /// As messaging in starknet is only possible with EthAddress in the `to_address` /// field, we have to set magic value to understand what the user want to do. @@ -162,7 +155,7 @@ impl StarknetMessaging { #[async_trait] impl Messenger for StarknetMessaging { type MessageHash = FieldElement; - type MessageTransaction = L1HandlerTransaction; + type MessageTransaction = L1HandlerTx; async fn gather_messages( &self, @@ -193,7 +186,7 @@ impl Messenger for StarknetMessaging { chain_latest_block }; - let mut l1_handler_txs: Vec = vec![]; + let mut l1_handler_txs: Vec = vec![]; self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) .await @@ -308,14 +301,11 @@ fn parse_messages(messages: &[MsgToL1]) -> MessengerResult<(Vec, V Ok((hashes, calls)) } -fn l1_handler_tx_from_event( - event: &EmittedEvent, - chain_id: FieldElement, -) -> Result { +fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: FieldElement) -> Result { if event.keys[0] != selector!("MessageSentToAppchain") { debug!( target: LOG_TARGET, - "Event with key {:?} can't be converted into L1HandlerTransaction", event.keys[0], + "Event with key {:?} can't be converted into L1HandlerTx", event.keys[0], ); return Err(Error::GatherError.into()); } @@ -327,53 +317,33 @@ fn l1_handler_tx_from_event( // See contrat appchain_messaging.cairo for MessageSentToAppchain event. let from_address = event.keys[2]; let to_address = event.keys[3]; - let selector = event.data[0]; + let entry_point_selector = event.data[0]; let nonce = event.data[1]; - let version = 0_u32; // Skip the length of the serialized array for the payload which is data[2]. // Payload starts at data[3]. let mut calldata = vec![from_address]; calldata.extend(&event.data[3..]); - let tx_hash = compute_l1_handler_transaction_hash_felts( - version.into(), - to_address, - selector, - &calldata, - chain_id, + Ok(L1HandlerTx { nonce, - ); - - let calldata: Vec = calldata.iter().map(|f| StarkFelt::from(*f)).collect(); - let calldata = Calldata(calldata.into()); - - let tx = L1HandlerTransaction { - inner: ApiL1HandlerTransaction { - transaction_hash: TransactionHash(tx_hash.into()), - version: TransactionVersion(stark_felt!(version)), - nonce: Nonce(nonce.into()), - contract_address: ContractAddress::try_from(>::into( - to_address, - )) - .unwrap(), - entry_point_selector: EntryPointSelector(selector.into()), - calldata, - }, + calldata, + chain_id, // This is the min value paid on L1 for the message to be sent to L2. - paid_l1_fee: 30000_u128, - }; - - Ok(tx) + paid_fee_on_l1: 30000_u128, + entry_point_selector, + version: FieldElement::ZERO, + contract_address: to_address.into(), + }) } #[cfg(test)] mod tests { + use katana_primitives::utils::transaction::compute_l1_handler_tx_hash; use starknet::macros::felt; use super::*; - use crate::utils::transaction::stark_felt_to_field_element_array; #[test] fn parse_messages_msg() { @@ -437,12 +407,13 @@ mod tests { let selector = selector!("selector"); let chain_id = selector!("KATANA"); let nonce = FieldElement::ONE; - let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; - let transaction_hash: FieldElement = compute_l1_handler_transaction_hash_felts( + let calldata = vec![from_address, FieldElement::THREE]; + + let transaction_hash: FieldElement = compute_l1_handler_tx_hash( FieldElement::ZERO, to_address, selector, - &stark_felt_to_field_element_array(&calldata), + &calldata, chain_id, nonce, ); @@ -468,24 +439,19 @@ mod tests { transaction_hash, }; - let expected = L1HandlerTransaction { - inner: ApiL1HandlerTransaction { - transaction_hash: TransactionHash(transaction_hash.into()), - version: TransactionVersion(stark_felt!(0_u32)), - nonce: Nonce(nonce.into()), - contract_address: ContractAddress::try_from( - >::into(to_address), - ) - .unwrap(), - entry_point_selector: EntryPointSelector(selector.into()), - calldata: Calldata(calldata.into()), - }, - paid_l1_fee: 30000_u128, + let expected = L1HandlerTx { + nonce, + calldata, + chain_id, + paid_fee_on_l1: 30000_u128, + version: FieldElement::ZERO, + entry_point_selector: selector, + contract_address: to_address.into(), }; let tx = l1_handler_tx_from_event(&event, chain_id).unwrap(); - assert_eq!(tx.inner, expected.inner); + assert_eq!(tx, expected); } #[test] @@ -495,7 +461,7 @@ mod tests { let to_address = selector!("to_address"); let selector = selector!("selector"); let nonce = FieldElement::ONE; - let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; + let calldata = vec![from_address, FieldElement::THREE]; let transaction_hash = FieldElement::ZERO; let event = EmittedEvent { @@ -529,7 +495,7 @@ mod tests { let _to_address = selector!("to_address"); let _selector = selector!("selector"); let _nonce = FieldElement::ONE; - let _calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; + let _calldata = vec![from_address, FieldElement::THREE]; let transaction_hash = FieldElement::ZERO; let event = EmittedEvent { diff --git a/crates/katana/core/src/service/mod.rs b/crates/katana/core/src/service/mod.rs index 138fa73ab7..860b632c3e 100644 --- a/crates/katana/core/src/service/mod.rs +++ b/crates/katana/core/src/service/mod.rs @@ -1,5 +1,3 @@ -// Code adapted from Foundry's Anvil - //! background service use std::future::Future; @@ -9,11 +7,11 @@ use std::task::{Context, Poll}; use futures::channel::mpsc::Receiver; use futures::stream::{Fuse, Stream, StreamExt}; +use katana_primitives::transaction::ExecutableTxWithHash; use starknet::core::types::FieldElement; use tracing::trace; use self::block_producer::BlockProducer; -use crate::backend::storage::transaction::Transaction; use crate::pool::TransactionPool; pub mod block_producer; @@ -97,7 +95,7 @@ impl TransactionMiner { &mut self, pool: &Arc, cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { // drain the notification stream while let Poll::Ready(Some(_)) = Pin::new(&mut self.rx).poll_next(cx) { self.has_pending_txs = Some(true); diff --git a/crates/katana/core/src/utils/mod.rs b/crates/katana/core/src/utils/mod.rs index affc08d73f..61f6dc7442 100644 --- a/crates/katana/core/src/utils/mod.rs +++ b/crates/katana/core/src/utils/mod.rs @@ -1,11 +1,12 @@ pub mod contract; pub mod event; -pub mod transaction; +use std::collections::HashMap; use std::time::{Duration, SystemTime}; use anyhow::Result; use blockifier::state::cached_state::CommitmentStateDiff; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use starknet::core::types::{ ContractStorageDiffItem, DeclaredClassItem, DeployedContractItem, NonceUpdate, StateDiff, StorageEntry, @@ -13,6 +14,14 @@ use starknet::core::types::{ use starknet_api::hash::StarkFelt; use starknet_api::StarknetApiError; +use crate::constants::{ + ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, ERC20_CONTRACT_COMPILED_CLASS_HASH, + ERC20_DECIMALS_STORAGE_SLOT, ERC20_NAME_STORAGE_SLOT, ERC20_SYMBOL_STORAGE_SLOT, + FEE_TOKEN_ADDRESS, OZ_V0_ACCOUNT_CONTRACT, OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH, + OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, UDC_ADDRESS, UDC_CLASS_HASH, + UDC_COMPILED_CLASS_HASH, UDC_CONTRACT, +}; + pub fn get_current_timestamp() -> Duration { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -77,3 +86,47 @@ pub fn convert_state_diff_to_rpc_state_diff(state_diff: CommitmentStateDiff) -> .collect(), } } + +pub fn get_genesis_states_for_testing() -> StateUpdatesWithDeclaredClasses { + let nonce_updates = + HashMap::from([(*UDC_ADDRESS, 0u8.into()), (*FEE_TOKEN_ADDRESS, 0u8.into())]); + + let storage_updates = HashMap::from([( + *FEE_TOKEN_ADDRESS, + HashMap::from([ + (*ERC20_DECIMALS_STORAGE_SLOT, 18_u128.into()), + (*ERC20_SYMBOL_STORAGE_SLOT, 0x455448_u128.into()), + (*ERC20_NAME_STORAGE_SLOT, 0x4574686572_u128.into()), + ]), + )]); + + let contract_updates = HashMap::from([ + (*UDC_ADDRESS, *UDC_CLASS_HASH), + (*FEE_TOKEN_ADDRESS, *ERC20_CONTRACT_CLASS_HASH), + ]); + + let declared_classes = HashMap::from([ + (*UDC_CLASS_HASH, *UDC_COMPILED_CLASS_HASH), + (*ERC20_CONTRACT_CLASS_HASH, *ERC20_CONTRACT_COMPILED_CLASS_HASH), + (*OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH, *OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH), + ]); + + let declared_sierra_classes = HashMap::from([]); + + let declared_compiled_classes = HashMap::from([ + (*UDC_COMPILED_CLASS_HASH, (*UDC_CONTRACT).clone()), + (*ERC20_CONTRACT_COMPILED_CLASS_HASH, (*ERC20_CONTRACT).clone()), + (*OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, (*OZ_V0_ACCOUNT_CONTRACT).clone()), + ]); + + StateUpdatesWithDeclaredClasses { + declared_sierra_classes, + declared_compiled_classes, + state_updates: StateUpdates { + nonce_updates, + storage_updates, + contract_updates, + declared_classes, + }, + } +} diff --git a/crates/katana/core/src/utils/transaction.rs b/crates/katana/core/src/utils/transaction.rs deleted file mode 100644 index 5fecbe2205..0000000000 --- a/crates/katana/core/src/utils/transaction.rs +++ /dev/null @@ -1,594 +0,0 @@ -use std::sync::Arc; - -use blockifier::execution::contract_class::ContractClass; -use blockifier::execution::errors::EntryPointExecutionError; -use blockifier::transaction::account_transaction::AccountTransaction; -use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; -use starknet::core::crypto::compute_hash_on_elements; -use starknet::core::types::{ - BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, - BroadcastedInvokeTransaction, DeclareTransaction, DeclareTransactionV1, DeclareTransactionV2, - DeployAccountTransaction, DeployTransaction, FieldElement, InvokeTransaction, - InvokeTransactionV0, InvokeTransactionV1, L1HandlerTransaction, Transaction as RpcTransaction, -}; -use starknet::core::utils::{get_contract_address, parse_cairo_short_string}; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::transaction::{ - Calldata, ContractAddressSalt, DeclareTransaction as DeclareApiTransaction, - DeclareTransactionV0V1 as DeclareApiTransactionV0V1, - DeclareTransactionV2 as DeclareApiTransactionV2, - DeployAccountTransaction as DeployAccountApiTransaction, - DeployTransaction as DeployApiTransaction, Fee, InvokeTransaction as InvokeApiTransaction, - InvokeTransactionV1 as InvokeApiTransactionV1, L1HandlerTransaction as L1HandlerApiTransaction, - Transaction as ApiTransaction, TransactionHash, TransactionSignature, TransactionVersion, -}; -use starknet_api::{patricia_key, stark_felt}; - -use super::contract::rpc_to_inner_class; -use crate::utils::contract::legacy_rpc_to_inner_class; -use crate::utils::starkfelt_to_u128; - -/// 2^ 128 -const QUERY_VERSION_OFFSET: FieldElement = FieldElement::from_mont([ - 18446744073700081665, - 17407, - 18446744073709551584, - 576460752142434320, -]); - -/// Cairo string for "invoke" -const PREFIX_INVOKE: FieldElement = FieldElement::from_mont([ - 18443034532770911073, - 18446744073709551615, - 18446744073709551615, - 513398556346534256, -]); - -/// Cairo string for "declare" -const PREFIX_DECLARE: FieldElement = FieldElement::from_mont([ - 17542456862011667323, - 18446744073709551615, - 18446744073709551615, - 191557713328401194, -]); - -/// Cairo string for "deploy_account" -const PREFIX_DEPLOY_ACCOUNT: FieldElement = FieldElement::from_mont([ - 3350261884043292318, - 18443211694809419988, - 18446744073709551615, - 461298303000467581, -]); - -/// Cairo string for "l1_handler" -const PREFIX_L1_HANDLER: FieldElement = FieldElement::from_mont([ - 1365666230910873368, - 18446744073708665300, - 18446744073709551615, - 157895833347907735, -]); - -/// Compute the hash of a V1 DeployAccount transaction. -#[allow(clippy::too_many_arguments)] -pub fn compute_deploy_account_v1_transaction_hash( - contract_address: FieldElement, - constructor_calldata: &[FieldElement], - class_hash: FieldElement, - salt: FieldElement, - max_fee: FieldElement, - chain_id: FieldElement, - nonce: FieldElement, - is_query: bool, -) -> FieldElement { - let calldata_to_hash = [&[class_hash, salt], constructor_calldata].concat(); - - compute_hash_on_elements(&[ - PREFIX_DEPLOY_ACCOUNT, - if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ - contract_address, - FieldElement::ZERO, // entry_point_selector - compute_hash_on_elements(&calldata_to_hash), - max_fee, - chain_id, - nonce, - ]) -} - -/// Compute the hash of a V1 Declare transaction. -pub fn compute_declare_v1_transaction_hash( - sender_address: FieldElement, - class_hash: FieldElement, - max_fee: FieldElement, - chain_id: FieldElement, - nonce: FieldElement, - is_query: bool, -) -> FieldElement { - compute_hash_on_elements(&[ - PREFIX_DECLARE, - if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ - sender_address, - FieldElement::ZERO, // entry_point_selector - compute_hash_on_elements(&[class_hash]), - max_fee, - chain_id, - nonce, - ]) -} - -/// Compute the hash of a V2 Declare transaction. -pub fn compute_declare_v2_transaction_hash( - sender_address: FieldElement, - class_hash: FieldElement, - max_fee: FieldElement, - chain_id: FieldElement, - nonce: FieldElement, - compiled_class_hash: FieldElement, - is_query: bool, -) -> FieldElement { - compute_hash_on_elements(&[ - PREFIX_DECLARE, - if is_query { QUERY_VERSION_OFFSET + FieldElement::TWO } else { FieldElement::TWO }, /* version */ - sender_address, - FieldElement::ZERO, // entry_point_selector - compute_hash_on_elements(&[class_hash]), - max_fee, - chain_id, - nonce, - compiled_class_hash, - ]) -} - -/// Compute the hash of a V0 Invoke transaction. -pub fn compute_invoke_v0_transaction_hash( - contract_address: FieldElement, - entry_point_selector: FieldElement, - calldata: &[FieldElement], - max_fee: FieldElement, - chain_id: FieldElement, - is_query: bool, -) -> FieldElement { - compute_hash_on_elements(&[ - PREFIX_INVOKE, - if is_query { QUERY_VERSION_OFFSET + FieldElement::ZERO } else { FieldElement::ZERO }, /* version */ - contract_address, - entry_point_selector, // entry_point_selector - compute_hash_on_elements(calldata), - max_fee, - chain_id, - ]) -} - -/// Compute the hash of a V1 Invoke transaction. -pub fn compute_invoke_v1_transaction_hash( - sender_address: FieldElement, - calldata: &[FieldElement], - max_fee: FieldElement, - chain_id: FieldElement, - nonce: FieldElement, - is_query: bool, -) -> FieldElement { - compute_hash_on_elements(&[ - PREFIX_INVOKE, - if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ - sender_address, - FieldElement::ZERO, // entry_point_selector - compute_hash_on_elements(calldata), - max_fee, - chain_id, - nonce, - ]) -} - -/// Computes the hash of a L1 handler transaction -/// from `L1HandlerApiTransaction`. -pub fn compute_l1_handler_transaction_hash( - tx: L1HandlerApiTransaction, - chain_id: FieldElement, -) -> FieldElement { - let tx = api_l1_handler_to_rpc_transaction(tx); - let version: FieldElement = tx.version.into(); - - assert_eq!(version, FieldElement::ZERO, "L1 handler transaction only supports version 0"); - - compute_l1_handler_transaction_hash_felts( - version, - tx.contract_address, - tx.entry_point_selector, - &tx.calldata, - chain_id, - tx.nonce.into(), - ) -} - -/// Computes the hash of a L1 handler transaction -/// from the fields involved in the computation, -/// as felts values. -pub fn compute_l1_handler_transaction_hash_felts( - version: FieldElement, - contract_address: FieldElement, - entry_point_selector: FieldElement, - calldata: &[FieldElement], - chain_id: FieldElement, - nonce: FieldElement, -) -> FieldElement { - // No fee on L2 for L1 handler transaction. - let fee = FieldElement::ZERO; - - compute_hash_on_elements(&[ - PREFIX_L1_HANDLER, - version, - contract_address, - entry_point_selector, - compute_hash_on_elements(calldata), - fee, - chain_id, - nonce, - ]) -} - -/// Convert [StarkFelt] array to [FieldElement] array. -#[inline] -pub fn stark_felt_to_field_element_array(arr: &[StarkFelt]) -> Vec { - arr.iter().map(|e| (*e).into()).collect() -} - -/// Convert [starknet_api::transaction::Transaction] transaction to JSON-RPC compatible transaction, -/// [starknet::core::types::Transaction]. -/// `starknet_api` transaction types are used when executing the transaction using `blockifier`. -pub fn api_to_rpc_transaction(transaction: ApiTransaction) -> RpcTransaction { - match transaction { - ApiTransaction::Invoke(invoke) => { - RpcTransaction::Invoke(api_invoke_to_rpc_transaction(invoke)) - } - ApiTransaction::Declare(declare) => { - RpcTransaction::Declare(api_declare_to_rpc_transaction(declare)) - } - ApiTransaction::DeployAccount(deploy) => { - RpcTransaction::DeployAccount(api_deploy_account_to_rpc_transaction(deploy)) - } - ApiTransaction::L1Handler(l1handler) => { - RpcTransaction::L1Handler(api_l1_handler_to_rpc_transaction(l1handler)) - } - ApiTransaction::Deploy(deploy) => { - RpcTransaction::Deploy(api_deploy_to_rpc_transaction(deploy)) - } - } -} - -fn api_l1_handler_to_rpc_transaction(transaction: L1HandlerApiTransaction) -> L1HandlerTransaction { - L1HandlerTransaction { - transaction_hash: transaction.transaction_hash.0.into(), - contract_address: (*transaction.contract_address.0.key()).into(), - nonce: >::into(transaction.nonce.0) - .try_into() - .expect("able to convert starkfelt to u64"), - version: >::into(transaction.version.0) - .try_into() - .expect("able to convert starkfelt to u64"), - entry_point_selector: transaction.entry_point_selector.0.into(), - calldata: stark_felt_to_field_element_array(&transaction.calldata.0), - } -} - -fn api_deploy_to_rpc_transaction(transaction: DeployApiTransaction) -> DeployTransaction { - DeployTransaction { - transaction_hash: transaction.transaction_hash.0.into(), - version: >::into(transaction.version.0) - .try_into() - .expect("able to convert starkfelt to u64"), - class_hash: transaction.class_hash.0.into(), - contract_address_salt: transaction.contract_address_salt.0.into(), - constructor_calldata: stark_felt_to_field_element_array( - &transaction.constructor_calldata.0, - ), - } -} - -fn api_deploy_account_to_rpc_transaction( - transaction: DeployAccountApiTransaction, -) -> DeployAccountTransaction { - DeployAccountTransaction { - nonce: transaction.nonce.0.into(), - max_fee: transaction.max_fee.0.into(), - class_hash: transaction.class_hash.0.into(), - transaction_hash: transaction.transaction_hash.0.into(), - contract_address_salt: transaction.contract_address_salt.0.into(), - constructor_calldata: stark_felt_to_field_element_array( - &transaction.constructor_calldata.0, - ), - signature: stark_felt_to_field_element_array(&transaction.signature.0), - } -} - -fn api_invoke_to_rpc_transaction(transaction: InvokeApiTransaction) -> InvokeTransaction { - match transaction { - InvokeApiTransaction::V0(tx) => InvokeTransaction::V0(InvokeTransactionV0 { - max_fee: tx.max_fee.0.into(), - transaction_hash: tx.transaction_hash.0.into(), - contract_address: (*tx.contract_address.0.key()).into(), - entry_point_selector: tx.entry_point_selector.0.into(), - calldata: stark_felt_to_field_element_array(&tx.calldata.0), - signature: stark_felt_to_field_element_array(&tx.signature.0), - }), - InvokeApiTransaction::V1(tx) => InvokeTransaction::V1(InvokeTransactionV1 { - nonce: tx.nonce.0.into(), - max_fee: tx.max_fee.0.into(), - transaction_hash: tx.transaction_hash.0.into(), - sender_address: (*tx.sender_address.0.key()).into(), - calldata: stark_felt_to_field_element_array(&tx.calldata.0), - signature: stark_felt_to_field_element_array(&tx.signature.0), - }), - } -} - -fn api_declare_to_rpc_transaction(transaction: DeclareApiTransaction) -> DeclareTransaction { - match transaction { - DeclareApiTransaction::V0(tx) | DeclareApiTransaction::V1(tx) => { - DeclareTransaction::V1(DeclareTransactionV1 { - nonce: tx.nonce.0.into(), - max_fee: tx.max_fee.0.into(), - class_hash: tx.class_hash.0.into(), - transaction_hash: tx.transaction_hash.0.into(), - sender_address: (*tx.sender_address.0.key()).into(), - signature: stark_felt_to_field_element_array(&tx.signature.0), - }) - } - DeclareApiTransaction::V2(tx) => DeclareTransaction::V2(DeclareTransactionV2 { - nonce: tx.nonce.0.into(), - max_fee: tx.max_fee.0.into(), - class_hash: tx.class_hash.0.into(), - transaction_hash: tx.transaction_hash.0.into(), - sender_address: (*tx.sender_address.0.key()).into(), - compiled_class_hash: tx.compiled_class_hash.0.into(), - signature: stark_felt_to_field_element_array(&tx.signature.0), - }), - } -} - -/// Convert `blockfiier` transaction type to `starknet_api` transaction. -pub fn convert_blockifier_to_api_tx(transaction: &ExecutionTransaction) -> ApiTransaction { - match transaction { - ExecutionTransaction::AccountTransaction(tx) => match tx { - AccountTransaction::Invoke(tx) => ApiTransaction::Invoke(tx.clone()), - AccountTransaction::Declare(tx) => ApiTransaction::Declare(tx.tx().clone()), - AccountTransaction::DeployAccount(tx) => ApiTransaction::DeployAccount(tx.tx.clone()), - }, - ExecutionTransaction::L1HandlerTransaction(tx) => ApiTransaction::L1Handler(tx.tx.clone()), - } -} - -/// Convert broadcasted Invoke transaction type from `starknet-rs` to `starknet_api`'s -/// Invoke transaction. -pub fn broadcasted_invoke_rpc_to_api_transaction( - transaction: BroadcastedInvokeTransaction, - chain_id: FieldElement, -) -> InvokeApiTransaction { - let BroadcastedInvokeTransaction { - calldata, max_fee, nonce, sender_address, signature, .. - } = transaction; - - let hash = compute_invoke_v1_transaction_hash( - sender_address, - &calldata, - max_fee, - chain_id, - nonce, - transaction.is_query, - ); - - let transaction = InvokeApiTransactionV1 { - nonce: Nonce(nonce.into()), - transaction_hash: TransactionHash(hash.into()), - sender_address: ContractAddress(patricia_key!(sender_address)), - signature: TransactionSignature(signature.into_iter().map(|e| e.into()).collect()), - calldata: Calldata(Arc::new(calldata.into_iter().map(|c| c.into()).collect())), - max_fee: Fee(starkfelt_to_u128(max_fee.into()).expect("convert max fee StarkFelt to u128")), - }; - - InvokeApiTransaction::V1(transaction) -} - -/// Convert broadcasted Declare transaction type from `starknet-rs` to `starknet_api`'s -/// Declare transaction. -/// -/// Returns the transaction and the contract class. -pub fn broadcasted_declare_rpc_to_api_transaction( - transaction: BroadcastedDeclareTransaction, - chain_id: FieldElement, -) -> Result<(DeclareApiTransaction, ContractClass), Box> { - match transaction { - BroadcastedDeclareTransaction::V1(tx) => { - let (class_hash, contract) = legacy_rpc_to_inner_class(&tx.contract_class)?; - - let transaction_hash = compute_declare_v1_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.is_query, - ); - - let transaction = DeclareApiTransactionV0V1 { - nonce: Nonce(tx.nonce.into()), - class_hash: ClassHash(class_hash.into()), - transaction_hash: TransactionHash(transaction_hash.into()), - sender_address: ContractAddress(patricia_key!(tx.sender_address)), - max_fee: Fee(starkfelt_to_u128(tx.max_fee.into()) - .expect("convert max fee StarkFelt to u128")), - signature: TransactionSignature( - tx.signature.into_iter().map(|e| e.into()).collect(), - ), - }; - - Ok((DeclareApiTransaction::V1(transaction), contract)) - } - - BroadcastedDeclareTransaction::V2(tx) => { - let (class_hash, contract_class) = rpc_to_inner_class(&tx.contract_class)?; - - let transaction_hash = compute_declare_v2_transaction_hash( - tx.sender_address, - class_hash, - tx.max_fee, - chain_id, - tx.nonce, - tx.compiled_class_hash, - tx.is_query, - ); - - let transaction = DeclareApiTransactionV2 { - nonce: Nonce(tx.nonce.into()), - class_hash: ClassHash(class_hash.into()), - transaction_hash: TransactionHash(transaction_hash.into()), - sender_address: ContractAddress(patricia_key!(tx.sender_address)), - compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.into()), - max_fee: Fee(starkfelt_to_u128(tx.max_fee.into()) - .expect("convert max fee StarkFelt to u128")), - signature: TransactionSignature( - tx.signature.into_iter().map(|e| e.into()).collect(), - ), - }; - - Ok((DeclareApiTransaction::V2(transaction), contract_class)) - } - } -} - -/// Convert broadcasted DeployAccount transaction type from `starknet-rs` to `starknet_api`'s -/// DeployAccount transaction. -/// -/// Returns the transaction and the contract address of the account to be deployed. -pub fn broadcasted_deploy_account_rpc_to_api_transaction( - transaction: BroadcastedDeployAccountTransaction, - chain_id: FieldElement, -) -> (DeployAccountApiTransaction, FieldElement) { - let BroadcastedDeployAccountTransaction { - nonce, - max_fee, - signature, - class_hash, - constructor_calldata, - contract_address_salt, - .. - } = transaction; - - let contract_address = get_contract_address( - contract_address_salt, - class_hash, - &constructor_calldata, - FieldElement::ZERO, - ); - - let transaction_hash = compute_deploy_account_v1_transaction_hash( - contract_address, - &constructor_calldata, - class_hash, - contract_address_salt, - max_fee, - chain_id, - nonce, - transaction.is_query, - ); - - let api_transaction = DeployAccountApiTransaction { - signature: TransactionSignature(signature.into_iter().map(|s| s.into()).collect()), - contract_address_salt: ContractAddressSalt(StarkFelt::from(contract_address_salt)), - constructor_calldata: Calldata(Arc::new( - constructor_calldata.into_iter().map(|d| d.into()).collect(), - )), - class_hash: ClassHash(class_hash.into()), - max_fee: Fee(starkfelt_to_u128(max_fee.into()).expect("convert max fee StarkFelt to u128")), - nonce: Nonce(nonce.into()), - transaction_hash: TransactionHash(transaction_hash.into()), - version: TransactionVersion(stark_felt!(1_u32)), - }; - - (api_transaction, contract_address) -} - -pub fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { - match err { - TransactionExecutionError::EntryPointExecutionError(ref eperr) - | TransactionExecutionError::ExecutionError(ref eperr) => match eperr { - EntryPointExecutionError::ExecutionFailed { error_data } => { - let mut reasons: Vec = vec![]; - error_data.iter().for_each(|felt| { - if let Ok(s) = parse_cairo_short_string(&FieldElement::from(*felt)) { - reasons.push(s); - } - }); - - tracing::warn!(target: "executor", - "Transaction validation error: {}", reasons.join(" ")); - } - _ => tracing::warn!(target: "executor", - "Transaction validation error: {:?}", err), - }, - _ => tracing::warn!(target: "executor", - "Transaction validation error: {:?}", err), - } -} - -#[cfg(test)] -mod tests { - use starknet::core::chain_id; - - use super::*; - - #[test] - fn test_compute_deploy_account_v1_transaction_hash() { - let contract_address = FieldElement::from_hex_be( - "0x0617e350ebed9897037bdef9a09af65049b85ed2e4c9604b640f34bffa152149", - ) - .unwrap(); - let constructor_calldata = vec![ - FieldElement::from_hex_be( - "0x33434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2", - ) - .unwrap(), - FieldElement::from_hex_be( - "0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463", - ) - .unwrap(), - FieldElement::from_hex_be("0x2").unwrap(), - FieldElement::from_hex_be( - "0x43a8fbe19d5ace41a2328bb870143241831180eb3c3c48096642d63709c3096", - ) - .unwrap(), - FieldElement::from_hex_be("0x0").unwrap(), - ]; - let class_hash = FieldElement::from_hex_be( - "0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", - ) - .unwrap(); - let salt = FieldElement::from_hex_be( - "0x43a8fbe19d5ace41a2328bb870143241831180eb3c3c48096642d63709c3096", - ) - .unwrap(); - let max_fee = FieldElement::from_hex_be("0x38d7ea4c68000").unwrap(); - let chain_id = chain_id::MAINNET; - let nonce = FieldElement::ZERO; - - let hash = compute_deploy_account_v1_transaction_hash( - contract_address, - &constructor_calldata, - class_hash, - salt, - max_fee, - chain_id, - nonce, - false, - ); - - assert_eq!( - hash, - FieldElement::from_hex_be( - "0x3d013d17c20a5db05d5c2e06c948a4e0bf5ea5b851b15137316533ec4788b6b" - ) - .unwrap() - ); - } -} diff --git a/crates/katana/core/tests/backend.rs b/crates/katana/core/tests/backend.rs index fcd1442c07..be08975e33 100644 --- a/crates/katana/core/tests/backend.rs +++ b/crates/katana/core/tests/backend.rs @@ -1,5 +1,6 @@ use katana_core::backend::config::{Environment, StarknetConfig}; use katana_core::backend::Backend; +use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; use starknet_api::block::BlockNumber; fn create_test_starknet_config() -> StarknetConfig { @@ -18,21 +19,23 @@ async fn create_test_backend() -> Backend { #[tokio::test] async fn test_creating_blocks() { - let starknet = create_test_backend().await; + let backend = create_test_backend().await; - assert_eq!(starknet.blockchain.storage.read().blocks.len(), 1); - assert_eq!(starknet.blockchain.storage.read().latest_number, 0); + let provider = backend.blockchain.provider(); - starknet.mine_empty_block().await; - starknet.mine_empty_block().await; + assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 0); - assert_eq!(starknet.blockchain.storage.read().blocks.len(), 3); - assert_eq!(starknet.blockchain.storage.read().latest_number, 2); - assert_eq!(starknet.env.read().block.block_number, BlockNumber(2),); + backend.mine_empty_block(); + backend.mine_empty_block(); - let block0 = starknet.blockchain.storage.read().block_by_number(0).unwrap().clone(); - let block1 = starknet.blockchain.storage.read().block_by_number(1).unwrap().clone(); + assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 2); + assert_eq!(backend.env.read().block.block_number, BlockNumber(2)); + + let block0 = BlockProvider::block_by_number(provider, 0).unwrap().unwrap(); + let block1 = BlockProvider::block_by_number(provider, 1).unwrap().unwrap(); + let block2 = BlockProvider::block_by_number(provider, 2).unwrap().unwrap(); assert_eq!(block0.header.number, 0); assert_eq!(block1.header.number, 1); + assert_eq!(block2.header.number, 2); } diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs index ec8726027b..1782ce8355 100644 --- a/crates/katana/core/tests/sequencer.rs +++ b/crates/katana/core/tests/sequencer.rs @@ -1,18 +1,6 @@ -use std::time::Duration; - use katana_core::backend::config::{Environment, StarknetConfig}; -use katana_core::backend::storage::transaction::{DeclareTransaction, KnownTransaction}; use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; -use katana_core::utils::contract::get_contract_class; -use starknet::core::types::FieldElement; -use starknet_api::core::{ClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::state::StorageKey; -use starknet_api::transaction::{ - DeclareTransaction as DeclareApiTransaction, DeclareTransactionV0V1, TransactionHash, -}; -use starknet_api::{patricia_key, stark_felt}; -use tokio::time::sleep; +use katana_provider::traits::block::BlockProvider; fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { ( @@ -32,48 +20,20 @@ async fn create_test_sequencer() -> KatanaSequencer { KatanaSequencer::new(sequencer_config, starknet_config).await } -fn create_declare_transaction(sender_address: ContractAddress) -> DeclareTransaction { - let compiled_class = - get_contract_class(include_str!("../contracts/compiled/test_contract.json")); - DeclareTransaction { - inner: DeclareApiTransaction::V0(DeclareTransactionV0V1 { - class_hash: ClassHash(stark_felt!("0x1234")), - nonce: Nonce(1u8.into()), - sender_address, - transaction_hash: TransactionHash(stark_felt!("0x6969")), - ..Default::default() - }), - compiled_class, - sierra_class: None, - } -} - #[tokio::test] async fn test_next_block_timestamp_in_past() { let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp - 1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; + + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; + + sequencer.set_next_block_timestamp(block1_timestamp - 1000).unwrap(); + + let block2 = sequencer.backend.mine_empty_block().block_number; + let block2_timestamp = + BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; assert_eq!(block2_timestamp, block1_timestamp - 1000, "timestamp should be updated"); } @@ -81,141 +41,59 @@ async fn test_next_block_timestamp_in_past() { #[tokio::test] async fn test_set_next_block_timestamp_in_future() { let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp + 1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; - assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); -} + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; -#[tokio::test] -async fn test_increase_next_block_timestamp() { - let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.increase_next_block_timestamp(1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + sequencer.set_next_block_timestamp(block1_timestamp + 1000).unwrap(); + + let block2 = sequencer.backend.mine_empty_block().block_number; + let block2_timestamp = + BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); } #[tokio::test] -async fn test_set_storage_at_on_instant_mode() { +async fn test_increase_next_block_timestamp() { let sequencer = create_test_sequencer().await; - sequencer.backend.mine_empty_block().await; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; - let contract_address = ContractAddress(patricia_key!("0x1337")); - let key = StorageKey(patricia_key!("0x20")); - let val = stark_felt!("0xABC"); + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; - { - let mut state = sequencer.backend.state.write().await; - let read_val = state.get_storage_at(contract_address, key).unwrap(); - assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); - } + sequencer.increase_next_block_timestamp(1000).unwrap(); - sequencer.set_storage_at(contract_address, key, val).await.unwrap(); + let block2 = sequencer.backend.mine_empty_block().block_number; + let block2_timestamp = + BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; - { - let mut state = sequencer.backend.state.write().await; - let read_val = state.get_storage_at(contract_address, key).unwrap(); - assert_eq!(val, read_val, "latest storage value incorrect after generate"); - } + assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); } -#[tokio::test(flavor = "multi_thread")] -async fn dump_and_load_state() { - let sequencer_old = create_test_sequencer().await; - assert_eq!(sequencer_old.block_number().await, 0); - - let declare_tx = create_declare_transaction(ContractAddress(patricia_key!( - sequencer_old.backend.accounts[0].address - ))); - - let tx_hash = declare_tx.inner.transaction_hash(); - - sequencer_old.add_declare_transaction(declare_tx); - - // wait for the tx to be picked up from the mempool, and executed and included in the next block - sleep(Duration::from_millis(500)).await; - - let tx_in_storage = sequencer_old.transaction(&tx_hash.0.into()).await.unwrap(); - - matches!(tx_in_storage, KnownTransaction::Included(_)); - assert_eq!(sequencer_old.block_number().await, 1); - - let serializable_state = sequencer_old - .backend - .state - .read() - .await - .dump_state() - .expect("must be able to serialize state"); - - assert!( - serializable_state.classes.get(&FieldElement::from_hex_be("0x1234").unwrap()).is_some(), - "class must be serialized" - ); - - // instantiate a new sequencer with the serialized state - let (sequencer_config, mut starknet_config) = create_test_sequencer_config(); - starknet_config.init_state = Some(serializable_state); - let sequencer_new = KatanaSequencer::new(sequencer_config, starknet_config).await; - - let old_contract = sequencer_old - .backend - .state - .write() - .await - .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) - .unwrap(); - - let new_contract = sequencer_new - .backend - .state - .write() - .await - .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) - .unwrap(); - - assert_eq!(old_contract, new_contract); -} +// #[tokio::test] +// async fn test_set_storage_at_on_instant_mode() { +// let sequencer = create_test_sequencer().await; +// sequencer.backend.mine_empty_block(); + +// let contract_address = ContractAddress(patricia_key!("0x1337")); +// let key = StorageKey(patricia_key!("0x20")); +// let val = stark_felt!("0xABC"); + +// { +// let mut state = sequencer.backend.state.write().await; +// let read_val = state.get_storage_at(contract_address, key).unwrap(); +// assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); +// } + +// sequencer.set_storage_at(contract_address, key, val).await.unwrap(); + +// { +// let mut state = sequencer.backend.state.write().await; +// let read_val = state.get_storage_at(contract_address, key).unwrap(); +// assert_eq!(val, read_val, "latest storage value incorrect after generate"); +// } +// } diff --git a/crates/katana/rpc/Cargo.toml b/crates/katana/rpc/Cargo.toml index f070468350..114a181902 100644 --- a/crates/katana/rpc/Cargo.toml +++ b/crates/katana/rpc/Cargo.toml @@ -7,10 +7,14 @@ repository.workspace = true version.workspace = true [dependencies] +katana-executor = { path = "../executor" } +katana-primitives = { path = "../primitives" } +katana-provider = { path = "../storage/provider" } +katana-rpc-types = { path = "rpc-types" } +katana-rpc-types-builder = { path = "rpc-types-builder" } + anyhow.workspace = true -blockifier.workspace = true cairo-lang-starknet = "2.3.1" -cairo-vm.workspace = true flate2.workspace = true futures.workspace = true hex = { version = "0.4.3", default-features = false } diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index cf6505be69..c276d98924 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -1,23 +1,22 @@ use jsonrpsee::core::Error; use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::error::{CallError, ErrorObject}; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use starknet::core::serde::unsigned_field_element::UfeHex; -use starknet::core::types::{ - BlockHashAndNumber, BlockId, BroadcastedDeclareTransaction, - BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, - ContractClass, DeclareTransactionResult, DeployAccountTransactionResult, EventFilterWithPage, - EventsPage, FeeEstimate, FieldElement, FunctionCall, InvokeTransactionResult, - MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, - MsgFromL1, StateUpdate, Transaction, +use katana_primitives::block::{BlockIdOrTag, BlockNumber}; +use katana_primitives::transaction::TxHash; +use katana_primitives::FieldElement; +use katana_rpc_types::block::{ + BlockHashAndNumber, BlockTxCount, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, }; +use katana_rpc_types::event::{EventFilterWithPage, EventsPage}; +use katana_rpc_types::message::MsgFromL1; +use katana_rpc_types::receipt::MaybePendingTxReceipt; +use katana_rpc_types::state_update::StateUpdate; +use katana_rpc_types::transaction::{ + BroadcastedDeclareTx, BroadcastedDeployAccountTx, BroadcastedInvokeTx, BroadcastedTx, + DeclareTxResult, DeployAccountTxResult, InvokeTxResult, Tx, +}; +use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; -#[serde_as] -#[derive(Serialize, Deserialize)] -pub struct Felt(#[serde_as(as = "UfeHex")] pub FieldElement); - -// TODO: implement From for StarknetApiError #[derive(thiserror::Error, Clone, Copy, Debug)] pub enum StarknetApiError { #[error("Failed to write transaction")] @@ -91,28 +90,28 @@ pub trait StarknetApi { // Read API #[method(name = "chainId")] - async fn chain_id(&self) -> Result; + async fn chain_id(&self) -> Result; #[method(name = "getNonce")] - async fn nonce(&self, block_id: BlockId, contract_address: FieldElement) - -> Result; + async fn nonce( + &self, + block_id: BlockIdOrTag, + contract_address: FieldElement, + ) -> Result; #[method(name = "blockNumber")] - async fn block_number(&self) -> Result; + async fn block_number(&self) -> Result; #[method(name = "getTransactionByHash")] - async fn transaction_by_hash( - &self, - transaction_hash: FieldElement, - ) -> Result; + async fn transaction_by_hash(&self, transaction_hash: TxHash) -> Result; #[method(name = "getBlockTransactionCount")] - async fn block_transaction_count(&self, block_id: BlockId) -> Result; + async fn block_transaction_count(&self, block_id: BlockIdOrTag) -> Result; #[method(name = "getClassAt")] async fn class_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result; @@ -122,39 +121,42 @@ pub trait StarknetApi { #[method(name = "getBlockWithTxHashes")] async fn block_with_tx_hashes( &self, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result; - #[method(name = "getTransactionByBlockIdAndIndex")] + #[method(name = "getTransactionByBlockIdOrTagAndIndex")] async fn transaction_by_block_id_and_index( &self, - block_id: BlockId, - index: usize, - ) -> Result; + block_id: BlockIdOrTag, + index: u64, + ) -> Result; #[method(name = "getBlockWithTxs")] - async fn block_with_txs(&self, block_id: BlockId) -> Result; + async fn block_with_txs( + &self, + block_id: BlockIdOrTag, + ) -> Result; #[method(name = "getStateUpdate")] - async fn state_update(&self, block_id: BlockId) -> Result; + async fn state_update(&self, block_id: BlockIdOrTag) -> Result; #[method(name = "getTransactionReceipt")] async fn transaction_receipt( &self, - transaction_hash: FieldElement, - ) -> Result; + transaction_hash: TxHash, + ) -> Result; #[method(name = "getClassHashAt")] async fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, - ) -> Result; + ) -> Result; #[method(name = "getClass")] async fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: FieldElement, ) -> Result; @@ -162,50 +164,54 @@ pub trait StarknetApi { async fn events(&self, filter: EventFilterWithPage) -> Result; #[method(name = "pendingTransactions")] - async fn pending_transactions(&self) -> Result, Error>; + async fn pending_transactions(&self) -> Result, Error>; #[method(name = "estimateFee")] async fn estimate_fee( &self, - request: Vec, - block_id: BlockId, + request: Vec, + block_id: BlockIdOrTag, ) -> Result, Error>; #[method(name = "estimateMessageFee")] async fn estimate_message_fee( &self, message: MsgFromL1, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result; #[method(name = "call")] - async fn call(&self, request: FunctionCall, block_id: BlockId) -> Result, Error>; + async fn call( + &self, + request: FunctionCall, + block_id: BlockIdOrTag, + ) -> Result, Error>; #[method(name = "getStorageAt")] async fn storage_at( &self, contract_address: FieldElement, key: FieldElement, - block_id: BlockId, - ) -> Result; + block_id: BlockIdOrTag, + ) -> Result; // Write API #[method(name = "addDeployAccountTransaction")] async fn add_deploy_account_transaction( &self, - deploy_account_transaction: BroadcastedDeployAccountTransaction, - ) -> Result; + deploy_account_transaction: BroadcastedDeployAccountTx, + ) -> Result; #[method(name = "addDeclareTransaction")] async fn add_declare_transaction( &self, - declare_transaction: BroadcastedDeclareTransaction, - ) -> Result; + declare_transaction: BroadcastedDeclareTx, + ) -> Result; #[method(name = "addInvokeTransaction")] async fn add_invoke_transaction( &self, - invoke_transaction: BroadcastedInvokeTransaction, - ) -> Result; + invoke_transaction: BroadcastedInvokeTx, + ) -> Result; } diff --git a/crates/katana/rpc/src/katana.rs b/crates/katana/rpc/src/katana.rs index 966d9f7384..12eeec6d11 100644 --- a/crates/katana/rpc/src/katana.rs +++ b/crates/katana/rpc/src/katana.rs @@ -3,11 +3,7 @@ use std::sync::Arc; use jsonrpsee::core::{async_trait, Error}; use katana_core::accounts::Account; use katana_core::sequencer::KatanaSequencer; -use starknet::core::types::FieldElement; -use starknet_api::core::{ContractAddress, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::state::StorageKey; -use starknet_api::{patricia_key, stark_felt}; +use katana_primitives::FieldElement; use crate::api::katana::{KatanaApiError, KatanaApiServer}; @@ -35,14 +31,12 @@ impl KatanaApiServer for KatanaApi { async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { self.sequencer .set_next_block_timestamp(timestamp) - .await .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) } async fn increase_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { self.sequencer .increase_next_block_timestamp(timestamp) - .await .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) } @@ -52,17 +46,14 @@ impl KatanaApiServer for KatanaApi { async fn set_storage_at( &self, - contract_address: FieldElement, - key: FieldElement, - value: FieldElement, + _contract_address: FieldElement, + _key: FieldElement, + _value: FieldElement, ) -> Result<(), Error> { - self.sequencer - .set_storage_at( - ContractAddress(patricia_key!(contract_address)), - StorageKey(patricia_key!(key)), - stark_felt!(value), - ) - .await - .map_err(|_| Error::from(KatanaApiError::FailedToUpdateStorage)) + // self.sequencer + // .set_storage_at(contract_address.into(), key, value) + // .await + // .map_err(|_| Error::from(KatanaApiError::FailedToUpdateStorage)) + Ok(()) } } diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index c0edfb61a8..7c9d7b7d0a 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -1,35 +1,34 @@ +use std::str::FromStr; use std::sync::Arc; -use blockifier::state::errors::StateError; use jsonrpsee::core::{async_trait, Error}; use katana_core::backend::contract::StarknetContract; -use katana_core::backend::storage::transaction::{ - DeclareTransaction, DeployAccountTransaction, InvokeTransaction, KnownTransaction, - L1HandlerTransaction, PendingTransaction, Transaction, -}; -use katana_core::backend::ExternalFunctionCall; use katana_core::sequencer::KatanaSequencer; use katana_core::sequencer_error::SequencerError; use katana_core::utils::contract::legacy_inner_to_rpc_class; -use katana_core::utils::transaction::{ - broadcasted_declare_rpc_to_api_transaction, broadcasted_deploy_account_rpc_to_api_transaction, - broadcasted_invoke_rpc_to_api_transaction, +use katana_executor::blockifier::utils::EntryPointCall; +use katana_primitives::block::{BlockHashOrNumber, BlockIdOrTag, PartialHeader}; +use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash}; +use katana_primitives::FieldElement; +use katana_provider::traits::block::{BlockHashProvider, BlockIdReader, BlockNumberProvider}; +use katana_provider::traits::transaction::TransactionProvider; +use katana_rpc_types::block::{ + BlockHashAndNumber, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, + PendingBlockWithTxHashes, PendingBlockWithTxs, }; -use starknet::core::types::{ - BlockHashAndNumber, BlockId, BlockTag, BroadcastedDeclareTransaction, - BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, - ContractClass, DeclareTransactionResult, DeployAccountTransactionResult, EventFilterWithPage, - EventsPage, FeeEstimate, FieldElement, FunctionCall, InvokeTransactionResult, - MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, - MsgFromL1, StateUpdate, Transaction as RpcTransaction, +use katana_rpc_types::event::{EventFilterWithPage, EventsPage}; +use katana_rpc_types::message::MsgFromL1; +use katana_rpc_types::receipt::{MaybePendingTxReceipt, PendingTxReceipt}; +use katana_rpc_types::state_update::StateUpdate; +use katana_rpc_types::transaction::{ + BroadcastedDeclareTx, BroadcastedDeployAccountTx, BroadcastedInvokeTx, BroadcastedTx, + DeclareTxResult, DeployAccountTxResult, InvokeTxResult, Tx, }; -use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::patricia_key; -use starknet_api::state::StorageKey; -use starknet_api::transaction::Calldata; +use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; +use katana_rpc_types_builder::ReceiptBuilder; +use starknet::core::types::BlockTag; -use crate::api::starknet::{Felt, StarknetApiError, StarknetApiServer}; +use crate::api::starknet::{StarknetApiError, StarknetApiServer}; pub struct StarknetApi { sequencer: Arc, @@ -42,149 +41,268 @@ impl StarknetApi { } #[async_trait] impl StarknetApiServer for StarknetApi { - async fn chain_id(&self) -> Result { - Ok(self.sequencer.chain_id().await.as_hex()) + async fn chain_id(&self) -> Result { + let chain_id = self.sequencer.chain_id().as_hex(); + Ok(FieldElement::from_str(&chain_id).map_err(|_| StarknetApiError::UnexpectedError)?.into()) } async fn nonce( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, - ) -> Result { + ) -> Result { let nonce = self .sequencer - .nonce_at(block_id, ContractAddress(patricia_key!(contract_address))) + .nonce_at(block_id, contract_address.into()) .await .map_err(|e| match e { SequencerError::StateNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, _ => StarknetApiError::UnexpectedError, - })?; + })? + .ok_or(StarknetApiError::ContractNotFound)?; - Ok(Felt(nonce.0.into())) + Ok(nonce.into()) } async fn block_number(&self) -> Result { - Ok(self.sequencer.block_number().await) + Ok(self.sequencer.block_number()) } - async fn transaction_by_hash( - &self, - transaction_hash: FieldElement, - ) -> Result { - let transaction = self + async fn transaction_by_hash(&self, transaction_hash: FieldElement) -> Result { + let tx = self .sequencer .transaction(&transaction_hash) - .await - .ok_or(Error::from(StarknetApiError::TxnHashNotFound))?; - - Ok(transaction.into()) + .map_err(|_| StarknetApiError::UnexpectedError)? + .ok_or(StarknetApiError::TxnHashNotFound)?; + Ok(tx.into()) } - async fn block_transaction_count(&self, block_id: BlockId) -> Result { - let block = self + async fn block_transaction_count(&self, block_id: BlockIdOrTag) -> Result { + let count = self .sequencer - .block(block_id) - .await - .ok_or(Error::from(StarknetApiError::BlockNotFound))?; - - Ok(block.transaction_count() as u64) + .block_tx_count(block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .ok_or(StarknetApiError::BlockNotFound)?; + Ok(count) } async fn class_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result { - let class_hash = self.class_hash_at(block_id, contract_address).await?; - self.class(block_id, class_hash.0).await + let class_hash = self + .sequencer + .class_hash_at(block_id, contract_address.into()) + .map_err(|_| StarknetApiError::UnexpectedError)? + .ok_or(StarknetApiError::ContractNotFound)?; + + self.class(block_id, class_hash).await } async fn block_hash_and_number(&self) -> Result { - let (block_hash, block_number) = self.sequencer.block_hash_and_number().await; - Ok(BlockHashAndNumber { block_hash, block_number }) + let hash_and_num_pair = self + .sequencer + .block_hash_and_number() + .map_err(|_| StarknetApiError::UnexpectedError)?; + Ok(hash_and_num_pair.into()) } async fn block_with_tx_hashes( &self, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result { - let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; - Ok(block.into()) + let provider = self.sequencer.backend.blockchain.provider(); + + if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { + let pending_state = self.sequencer.pending_state().expect("pending state should exist"); + + let block_context = self.sequencer.backend.env.read().block.clone(); + let latest_hash = BlockHashProvider::latest_hash(provider)?; + + let header = PartialHeader { + parent_hash: latest_hash, + gas_price: block_context.gas_price, + number: block_context.block_number.0, + timestamp: block_context.block_timestamp.0, + sequencer_address: block_context.sequencer_address.into(), + }; + + let transactions = + pending_state.executed_txs.read().iter().map(|(tx, _)| tx.hash).collect::>(); + + Ok(MaybePendingBlockWithTxHashes::Pending(PendingBlockWithTxHashes::new( + header, + transactions, + ))) + } else { + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; + + katana_rpc_types_builder::BlockBuilder::new(block_num, provider) + .build_with_tx_hash() + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(MaybePendingBlockWithTxHashes::Block) + .ok_or(Error::from(StarknetApiError::BlockNotFound)) + } } async fn transaction_by_block_id_and_index( &self, - block_id: BlockId, - index: usize, - ) -> Result { - let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; - - let hash: FieldElement = block - .transactions() - .get(index) - .map(|t| t.inner.hash()) - .ok_or(StarknetApiError::InvalidTxnIndex)?; - - self.transaction_by_hash(hash).await + block_id: BlockIdOrTag, + index: u64, + ) -> Result { + // TEMP: have to handle pending tag independently for now + let tx = if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { + let Some(pending_state) = self.sequencer.pending_state() else { + return Err(StarknetApiError::BlockNotFound.into()); + }; + + let pending_txs = pending_state.executed_txs.read(); + pending_txs.iter().nth(index as usize).map(|(tx, _)| tx.clone()) + } else { + let provider = &self.sequencer.backend.blockchain.provider(); + + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; + + TransactionProvider::transaction_by_block_and_idx(provider, block_num, index) + .map_err(|_| StarknetApiError::UnexpectedError)? + }; + + Ok(tx.ok_or(StarknetApiError::InvalidTxnIndex)?.into()) } - async fn block_with_txs(&self, block_id: BlockId) -> Result { - let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; - Ok(block.into()) + async fn block_with_txs( + &self, + block_id: BlockIdOrTag, + ) -> Result { + let provider = self.sequencer.backend.blockchain.provider(); + + if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { + let pending_state = self.sequencer.pending_state().expect("pending state should exist"); + + let block_context = self.sequencer.backend.env.read().block.clone(); + let latest_hash = BlockHashProvider::latest_hash(provider)?; + + let header = PartialHeader { + parent_hash: latest_hash, + gas_price: block_context.gas_price, + number: block_context.block_number.0, + timestamp: block_context.block_timestamp.0, + sequencer_address: block_context.sequencer_address.into(), + }; + + let transactions = pending_state + .executed_txs + .read() + .iter() + .map(|(tx, _)| tx.clone()) + .collect::>(); + + Ok(MaybePendingBlockWithTxs::Pending(PendingBlockWithTxs::new(header, transactions))) + } else { + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; + + katana_rpc_types_builder::BlockBuilder::new(block_num, provider) + .build() + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(MaybePendingBlockWithTxs::Block) + .ok_or(Error::from(StarknetApiError::BlockNotFound)) + } } - async fn state_update(&self, block_id: BlockId) -> Result { - self.sequencer - .state_update(block_id) - .await - .map_err(|_| StarknetApiError::BlockNotFound.into()) + async fn state_update(&self, block_id: BlockIdOrTag) -> Result { + let provider = self.sequencer.backend.blockchain.provider(); + + let block_id = match block_id { + BlockIdOrTag::Number(num) => BlockHashOrNumber::Num(num), + BlockIdOrTag::Hash(hash) => BlockHashOrNumber::Hash(hash), + + BlockIdOrTag::Tag(BlockTag::Latest) => BlockNumberProvider::latest_number(provider) + .map(BlockHashOrNumber::Num) + .map_err(|_| StarknetApiError::BlockNotFound)?, + BlockIdOrTag::Tag(BlockTag::Pending) => { + return Err(StarknetApiError::BlockNotFound.into()); + } + }; + + katana_rpc_types_builder::StateUpdateBuilder::new(block_id, provider) + .build() + .map_err(|_| StarknetApiError::UnexpectedError)? + .ok_or(Error::from(StarknetApiError::BlockNotFound)) } async fn transaction_receipt( &self, transaction_hash: FieldElement, - ) -> Result { - self.sequencer - .transaction_receipt(&transaction_hash) - .await - .ok_or(StarknetApiError::TxnHashNotFound.into()) + ) -> Result { + let provider = self.sequencer.backend.blockchain.provider(); + let receipt = ReceiptBuilder::new(transaction_hash, provider) + .build() + .map_err(|_| StarknetApiError::UnexpectedError)?; + + match receipt { + Some(receipt) => Ok(MaybePendingTxReceipt::Receipt(receipt)), + + None => { + let pending_receipt = self.sequencer.pending_state().and_then(|s| { + s.executed_txs + .read() + .iter() + .find(|(tx, _)| tx.hash == transaction_hash) + .map(|(_, rct)| rct.receipt.clone()) + }); + + let Some(pending_receipt) = pending_receipt else { + return Err(StarknetApiError::TxnHashNotFound.into()); + }; + + Ok(MaybePendingTxReceipt::Pending(PendingTxReceipt::new( + transaction_hash, + pending_receipt, + ))) + } + } } async fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, - ) -> Result { - let class_hash = self + ) -> Result { + let hash = self .sequencer - .class_hash_at(block_id, ContractAddress(patricia_key!(contract_address))) - .await + .class_hash_at(block_id, contract_address.into()) .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, _ => StarknetApiError::UnexpectedError, - })?; + })? + .ok_or(Error::from(StarknetApiError::ContractNotFound))?; - Ok(Felt(class_hash.0.into())) + Ok(hash.into()) } async fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: FieldElement, ) -> Result { - let contract = self.sequencer.class(block_id, ClassHash(class_hash.into())).await.map_err( - |e| match e { - SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::State(StateError::UndeclaredClassHash(_)) => { - StarknetApiError::ClassHashNotFound - } - _ => StarknetApiError::UnexpectedError, - }, - )?; + let class = self.sequencer.class(block_id, class_hash).map_err(|e| match e { + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + _ => StarknetApiError::UnexpectedError, + })?; + + let Some(class) = class else { return Err(StarknetApiError::ClassHashNotFound.into()) }; - match contract { + match class { StarknetContract::Legacy(c) => { let contract = legacy_inner_to_rpc_class(c).map_err(|_| StarknetApiError::UnexpectedError)?; @@ -195,8 +313,8 @@ impl StarknetApiServer for StarknetApi { } async fn events(&self, filter: EventFilterWithPage) -> Result { - let from_block = filter.event_filter.from_block.unwrap_or(BlockId::Number(0)); - let to_block = filter.event_filter.to_block.unwrap_or(BlockId::Tag(BlockTag::Latest)); + let from_block = filter.event_filter.from_block.unwrap_or(BlockIdOrTag::Number(0)); + let to_block = filter.event_filter.to_block.unwrap_or(BlockIdOrTag::Tag(BlockTag::Latest)); let keys = filter.event_filter.keys; let keys = keys.filter(|keys| !(keys.len() == 1 && keys.is_empty())); @@ -206,7 +324,7 @@ impl StarknetApiServer for StarknetApi { .events( from_block, to_block, - filter.event_filter.address, + filter.event_filter.address.map(|f| f.into()), keys, filter.result_page_request.continuation_token, filter.result_page_request.chunk_size, @@ -220,144 +338,118 @@ impl StarknetApiServer for StarknetApi { Ok(events) } - async fn pending_transactions(&self) -> Result, Error> { - let block = self.sequencer.block(BlockId::Tag(BlockTag::Pending)).await; + async fn pending_transactions(&self) -> Result, Error> { + let Some(pending_state) = self.sequencer.pending_state() else { return Ok(vec![]) }; - Ok(block - .map(|b| { - b.transactions() - .iter() - .map(|tx| KnownTransaction::Pending(PendingTransaction(tx.clone())).into()) - .collect::>() - }) - .unwrap_or(Vec::new())) + let txs = pending_state + .executed_txs + .read() + .iter() + .map(|(tx, _)| tx.clone().into()) + .collect::>(); + + Ok(txs) } - async fn call(&self, request: FunctionCall, block_id: BlockId) -> Result, Error> { - let call = ExternalFunctionCall { - contract_address: ContractAddress(patricia_key!(request.contract_address)), - calldata: Calldata(Arc::new( - request.calldata.into_iter().map(StarkFelt::from).collect(), - )), - entry_point_selector: EntryPointSelector(StarkFelt::from(request.entry_point_selector)), + async fn call( + &self, + request: FunctionCall, + block_id: BlockIdOrTag, + ) -> Result, Error> { + let request = EntryPointCall { + calldata: request.calldata, + contract_address: request.contract_address.into(), + entry_point_selector: request.entry_point_selector, }; - let res = self.sequencer.call(block_id, call).await.map_err(|e| match e { + let res = self.sequencer.call(request, block_id).map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, SequencerError::EntryPointExecution(_) => StarknetApiError::ContractError, _ => StarknetApiError::UnexpectedError, })?; - let mut values = vec![]; - - for f in res.into_iter() { - values.push(Felt(f.into())); - } - - Ok(values) + Ok(res.into_iter().map(|v| v.into()).collect()) } async fn storage_at( &self, contract_address: FieldElement, key: FieldElement, - block_id: BlockId, - ) -> Result { - let value = self - .sequencer - .storage_at( - ContractAddress(patricia_key!(contract_address)), - StorageKey(patricia_key!(key)), - block_id, - ) - .await - .map_err(|e| match e { + block_id: BlockIdOrTag, + ) -> Result { + let value = self.sequencer.storage_at(contract_address.into(), key, block_id).map_err( + |e| match e { SequencerError::StateNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::State(_) => StarknetApiError::ContractNotFound, _ => StarknetApiError::UnexpectedError, - })?; + }, + )?; - Ok(Felt(value.into())) + Ok(value.into()) } async fn add_deploy_account_transaction( &self, - deploy_account_transaction: BroadcastedDeployAccountTransaction, - ) -> Result { + deploy_account_transaction: BroadcastedDeployAccountTx, + ) -> Result { if deploy_account_transaction.is_query { return Err(StarknetApiError::UnsupportedTransactionVersion.into()); } - let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; - let (transaction, contract_address) = - broadcasted_deploy_account_rpc_to_api_transaction(deploy_account_transaction, chain_id); - let transaction_hash = transaction.transaction_hash.0.into(); + let tx = deploy_account_transaction.into_tx_with_chain_id(chain_id); + let contract_address = tx.contract_address; - self.sequencer - .add_deploy_account_transaction(DeployAccountTransaction { - contract_address, - inner: transaction, - }) - .await; + let tx = ExecutableTxWithHash::new(ExecutableTx::DeployAccount(tx)); + let tx_hash = tx.hash; + + self.sequencer.add_transaction_to_pool(tx); - Ok(DeployAccountTransactionResult { transaction_hash, contract_address }) + Ok((tx_hash, contract_address).into()) } async fn estimate_fee( &self, - request: Vec, - block_id: BlockId, + request: Vec, + block_id: BlockIdOrTag, ) -> Result, Error> { - let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; let transactions = request .into_iter() - .map(|r| match r { - BroadcastedTransaction::Declare(tx) => { - let sierra_class = match tx { - BroadcastedDeclareTransaction::V2(ref tx) => { - Some(tx.contract_class.as_ref().clone()) - } - _ => None, - }; - - let (transaction, compiled_class) = - broadcasted_declare_rpc_to_api_transaction(tx, chain_id).unwrap(); - - Transaction::Declare(DeclareTransaction { - sierra_class, - compiled_class, - inner: transaction, - }) - } - - BroadcastedTransaction::Invoke(tx) => { - let transaction = broadcasted_invoke_rpc_to_api_transaction(tx, chain_id); - Transaction::Invoke(InvokeTransaction(transaction)) - } - - BroadcastedTransaction::DeployAccount(tx) => { - let (transaction, contract_address) = - broadcasted_deploy_account_rpc_to_api_transaction(tx, chain_id); - - Transaction::DeployAccount(DeployAccountTransaction { - contract_address, - inner: transaction, - }) - } + .map(|tx| { + let tx = match tx { + BroadcastedTx::Invoke(tx) => { + let tx = tx.into_tx_with_chain_id(chain_id); + ExecutableTxWithHash::new_query(ExecutableTx::Invoke(tx)) + } + + BroadcastedTx::DeployAccount(tx) => { + let tx = tx.into_tx_with_chain_id(chain_id); + ExecutableTxWithHash::new_query(ExecutableTx::DeployAccount(tx)) + } + + BroadcastedTx::Declare(tx) => { + let tx = tx + .try_into_tx_with_chain_id(chain_id) + .map_err(|_| StarknetApiError::InvalidContractClass)?; + ExecutableTxWithHash::new_query(ExecutableTx::Declare(tx)) + } + }; + + Result::::Ok(tx) }) - .collect::>(); + .collect::, _>>()?; - let res = - self.sequencer.estimate_fee(transactions, block_id).await.map_err(|e| match e { - SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, - _ => StarknetApiError::UnexpectedError, - })?; + let res = self.sequencer.estimate_fee(transactions, block_id).map_err(|e| match e { + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, + _ => StarknetApiError::UnexpectedError, + })?; Ok(res) } @@ -365,24 +457,11 @@ impl StarknetApiServer for StarknetApi { async fn estimate_message_fee( &self, message: MsgFromL1, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result { - let l1handler_tx = L1HandlerTransaction { - inner: starknet_api::transaction::L1HandlerTransaction { - contract_address: ContractAddress(patricia_key!(message.to_address)), - calldata: Calldata(Arc::new( - message.payload.into_iter().map(|f| f.into()).collect(), - )), - entry_point_selector: EntryPointSelector(message.entry_point_selector.into()), - ..Default::default() - }, - paid_l1_fee: 1, - }; - let res = self .sequencer - .estimate_fee(vec![Transaction::L1Handler(l1handler_tx)], block_id) - .await + .estimate_fee(vec![message.into()], block_id) .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, @@ -396,54 +475,54 @@ impl StarknetApiServer for StarknetApi { async fn add_declare_transaction( &self, - declare_transaction: BroadcastedDeclareTransaction, - ) -> Result { - if match &declare_transaction { - BroadcastedDeclareTransaction::V1(tx) => tx.is_query, - BroadcastedDeclareTransaction::V2(tx) => tx.is_query, - } { + declare_transaction: BroadcastedDeclareTx, + ) -> Result { + if declare_transaction.is_query() { return Err(StarknetApiError::UnsupportedTransactionVersion.into()); } - let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; - let sierra_class = match declare_transaction { - BroadcastedDeclareTransaction::V2(ref tx) => Some(tx.contract_class.as_ref().clone()), - _ => None, - }; + // // validate compiled class hash + // let is_valid = declare_transaction + // .validate_compiled_class_hash() + // .map_err(|_| StarknetApiError::InvalidContractClass)?; + + // if !is_valid { + // return Err(StarknetApiError::CompiledClassHashMismatch.into()); + // } - let (transaction, contract_class) = - broadcasted_declare_rpc_to_api_transaction(declare_transaction, chain_id).unwrap(); + let tx = declare_transaction + .try_into_tx_with_chain_id(chain_id) + .map_err(|_| StarknetApiError::InvalidContractClass)?; - let transaction_hash = transaction.transaction_hash().0.into(); - let class_hash = transaction.class_hash().0.into(); + let class_hash = tx.class_hash(); + let tx = ExecutableTxWithHash::new(ExecutableTx::Declare(tx)); + let tx_hash = tx.hash; - self.sequencer.add_declare_transaction(DeclareTransaction { - sierra_class, - inner: transaction, - compiled_class: contract_class, - }); + self.sequencer.add_transaction_to_pool(tx); - Ok(DeclareTransactionResult { transaction_hash, class_hash }) + Ok((tx_hash, class_hash).into()) } async fn add_invoke_transaction( &self, - invoke_transaction: BroadcastedInvokeTransaction, - ) -> Result { + invoke_transaction: BroadcastedInvokeTx, + ) -> Result { if invoke_transaction.is_query { return Err(StarknetApiError::UnsupportedTransactionVersion.into()); } - let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; - let transaction = broadcasted_invoke_rpc_to_api_transaction(invoke_transaction, chain_id); - let transaction_hash = transaction.transaction_hash().0.into(); + let tx = invoke_transaction.into_tx_with_chain_id(chain_id); + let tx = ExecutableTxWithHash::new(ExecutableTx::Invoke(tx)); + let tx_hash = tx.hash; - self.sequencer.add_invoke_transaction(InvokeTransaction(transaction)); + self.sequencer.add_transaction_to_pool(tx); - Ok(InvokeTransactionResult { transaction_hash }) + Ok(tx_hash.into()) } } diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index 9e2c317a65..bbee045459 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -6,7 +6,6 @@ use katana_core::backend::config::{Environment, StarknetConfig}; use katana_core::constants::{ DEFAULT_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_VALIDATE_MAX_STEPS, }; -use katana_core::db::serde::state::SerializableState; use katana_core::sequencer::SequencerConfig; use katana_rpc::api::ApiKind; use katana_rpc::config::ServerConfig; @@ -57,12 +56,6 @@ pub struct KatanaArgs { #[arg(help = "Fork the network at a specific block.")] pub fork_block_number: Option, - #[arg(long)] - #[arg(value_name = "PATH")] - #[arg(value_parser = SerializableState::parse)] - #[arg(help = "Initialize the chain from a previously saved state snapshot.")] - pub load_state: Option, - #[cfg(feature = "messaging")] #[arg(long)] #[arg(value_name = "PATH")] @@ -198,7 +191,6 @@ impl KatanaArgs { total_accounts: self.starknet.total_accounts, seed: parse_seed(&self.starknet.seed), disable_fee: self.starknet.disable_fee, - init_state: self.load_state.clone(), fork_rpc_url: self.rpc_url.clone(), fork_block_number: self.fork_block_number, env: Environment { diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index 67fcbfa61c..eabb82ff6d 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -1,5 +1,5 @@ +use std::io; use std::sync::Arc; -use std::{fs, io}; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Shell}; @@ -10,7 +10,7 @@ use katana_core::constants::{ use katana_core::sequencer::KatanaSequencer; use katana_rpc::{spawn, NodeHandle}; use tokio::signal::ctrl_c; -use tracing::{error, info}; +use tracing::info; mod args; @@ -67,7 +67,6 @@ async fn main() -> Result<(), Box> { // Wait until Ctrl + C is pressed, then shutdown ctrl_c().await?; - shutdown_handler(sequencer, config).await; handle.stop()?; Ok(()) @@ -137,22 +136,3 @@ ACCOUNTS SEED println!("\n{address}\n\n"); } - -pub async fn shutdown_handler(sequencer: Arc, config: KatanaArgs) { - if let Some(path) = config.dump_state { - info!("Dumping state on shutdown"); - let state = (*sequencer).backend().dump_state().await; - if let Ok(state) = state { - match fs::write(path.clone(), state) { - Ok(_) => { - info!("Successfully dumped state") - } - Err(_) => { - error!("Failed to write state dump to {:?}", path) - } - }; - } else { - error!("Failed to fetch state dump.") - } - }; -} From 7c8e2bb520c58e84e74a8e2582d7bdff3789e561 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 22:40:40 +0800 Subject: [PATCH 096/192] chore: fix doc --- crates/katana/primitives/src/conversion/rpc.rs | 3 +-- crates/katana/storage/db/src/mdbx/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs index b5c1ef259a..f95eef2eb1 100644 --- a/crates/katana/primitives/src/conversion/rpc.rs +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -105,8 +105,7 @@ pub fn legacy_rpc_to_inner_class( Ok((class_hash, CompiledContractClass::V0(contract_class))) } -/// Returns a [LegacyEntryPointsByType](rpc::LegacyEntryPointsByType) (RPC type) from a -/// [EntryPointType] (blockifier type) +/// Returns a [LegacyEntryPointsByType] (RPC type) from a [EntryPointType] (blockifier type) fn to_rpc_legacy_entry_points_by_type( entries: &HashMap>, ) -> Result { diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 8dc9c7f168..9e2fbdec23 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -37,7 +37,7 @@ pub struct DbEnv(libmdbx::Environment); impl DbEnv { /// Opens the database at the specified path with the given `EnvKind`. /// - /// It does not create the tables, for that call [`Env::create_tables`]. + /// It does not create the tables, for that call [`DbEnv::create_tables`]. pub fn open(path: &Path, kind: EnvKind) -> Result { let mode = match kind { EnvKind::RO => Mode::ReadOnly, From ed64f656cf10059fe06a3b0cdd8336c3863a3ef2 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 5 Dec 2023 12:15:39 +0800 Subject: [PATCH 097/192] fix(katana-core): return default value if storage key is uninitialized --- crates/katana/core/src/sequencer.rs | 8 ++++++-- crates/katana/rpc/src/starknet.rs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index bf04269eea..53d688d20f 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -201,10 +201,14 @@ impl KatanaSequencer { block_id: BlockIdOrTag, ) -> SequencerResult { let state = self.state(&block_id)?; - let Some(value) = StateProvider::storage(&state, contract_address, storage_key)? else { + + // check that contract exist by checking the class hash of the contract + let Some(_) = StateProvider::class_hash_of_contract(&state, contract_address)? else { return Err(SequencerError::ContractNotFound(contract_address)); }; - Ok(value) + + let value = StateProvider::storage(&state, contract_address, storage_key)?; + Ok(value.unwrap_or_default()) } pub fn chain_id(&self) -> ChainId { diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index 7c9d7b7d0a..7aac882eac 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -380,8 +380,8 @@ impl StarknetApiServer for StarknetApi { ) -> Result { let value = self.sequencer.storage_at(contract_address.into(), key, block_id).map_err( |e| match e { - SequencerError::StateNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::State(_) => StarknetApiError::ContractNotFound, + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, _ => StarknetApiError::UnexpectedError, }, )?; From d9d36606f491e1fdbe6530cb6e6d5610da5dedf8 Mon Sep 17 00:00:00 2001 From: Kariy Date: Tue, 5 Dec 2023 13:50:02 +0800 Subject: [PATCH 098/192] fix(katana-core): fix test --- .../core/src/service/messaging/ethereum.rs | 121 ++++++++---------- 1 file changed, 55 insertions(+), 66 deletions(-) diff --git a/crates/katana/core/src/service/messaging/ethereum.rs b/crates/katana/core/src/service/messaging/ethereum.rs index ee9e796099..e8646b6336 100644 --- a/crates/katana/core/src/service/messaging/ethereum.rs +++ b/crates/katana/core/src/service/messaging/ethereum.rs @@ -260,75 +260,64 @@ fn felt_from_address(v: Address) -> FieldElement { #[cfg(test)] mod tests { - use starknet::macros::selector; + use starknet::macros::{felt, selector}; use super::*; - // #[test] - // fn l1_handler_tx_from_log_parse_ok() { - // let from_address = "0x000000000000000000000000be3C44c09bc1a3566F3e1CA12e5AbA0fA4Ca72Be"; - // let to_address = "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1"; - // let selector = "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f"; - // let nonce = 783082_u128; - // let fee = 30000_u128; - - // // Payload two values: [1, 2]. - // let payload_buf = - // hex::decode(" - // 000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bf2ea0000000000000000000000000000000000000000000000000000000000007530000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" - // ).unwrap(); - - // let calldata: Vec = vec![ - // FieldElement::from_hex_be(from_address).unwrap().into(), - // FieldElement::ONE.into(), - // FieldElement::TWO.into(), - // ]; - - // let transaction_hash: FieldElement = FieldElement::from_hex_be( - // "0x6182c63599a9638272f1ce5b5cadabece9c81c2d2b8f88ab7a294472b8fce8b", - // ) - // .unwrap(); - - // let log = Log { - // address: H160::from_str("0xde29d060D45901Fb19ED6C6e959EB22d8626708e").unwrap(), - // topics: vec![ - // H256::from_str( - // "0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b", - // ) - // .unwrap(), - // H256::from_str(from_address).unwrap(), - // H256::from_str(to_address).unwrap(), - // H256::from_str(selector).unwrap(), - // ], - // data: payload_buf.into(), - // ..Default::default() - // }; - - // let expected = L1HandlerTransaction { - // inner: ApiL1HandlerTransaction { - // transaction_hash: TransactionHash(transaction_hash.into()), - // version: TransactionVersion(stark_felt!(0_u32)), - // nonce: Nonce(FieldElement::from(nonce).into()), - // contract_address: ContractAddress::try_from( - // >::into( - // FieldElement::from_hex_be(to_address).unwrap(), - // ), - // ) - // .unwrap(), - // entry_point_selector: EntryPointSelector( - // FieldElement::from_hex_be(selector).unwrap().into(), - // ), - // calldata: Calldata(calldata.into()), - // }, - // paid_l1_fee: fee, - // }; - - // // SN_GOERLI. - // let chain_id = starknet::macros::felt!("0x534e5f474f45524c49"); - // let tx = l1_handler_tx_from_log(log, chain_id).expect("bad log format"); - - // assert_eq!(tx.inner, expected.inner); - // } + #[test] + fn l1_handler_tx_from_log_parse_ok() { + let from_address = "0x000000000000000000000000be3C44c09bc1a3566F3e1CA12e5AbA0fA4Ca72Be"; + let to_address = "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1"; + let selector = "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f"; + let nonce = 783082_u128; + let fee = 30000_u128; + + // Payload two values: [1, 2]. + let payload_buf = hex::decode("000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bf2ea0000000000000000000000000000000000000000000000000000000000007530000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002").unwrap(); + + let calldata = vec![ + FieldElement::from_hex_be(from_address).unwrap(), + FieldElement::ONE, + FieldElement::TWO, + ]; + + let expected_tx_hash = + felt!("0x6182c63599a9638272f1ce5b5cadabece9c81c2d2b8f88ab7a294472b8fce8b"); + + let log = Log { + address: H160::from_str("0xde29d060D45901Fb19ED6C6e959EB22d8626708e").unwrap(), + topics: vec![ + H256::from_str( + "0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b", + ) + .unwrap(), + H256::from_str(from_address).unwrap(), + H256::from_str(to_address).unwrap(), + H256::from_str(selector).unwrap(), + ], + data: payload_buf.into(), + ..Default::default() + }; + + // SN_GOERLI. + let chain_id = starknet::macros::felt!("0x534e5f474f45524c49"); + + let expected = L1HandlerTx { + calldata, + chain_id, + paid_fee_on_l1: fee, + version: FieldElement::ZERO, + nonce: FieldElement::from(nonce), + entry_point_selector: FieldElement::from_hex_be(selector).unwrap(), + contract_address: FieldElement::from_hex_be(to_address).unwrap().into(), + }; + let tx_hash = expected.calculate_hash(); + + let tx = l1_handler_tx_from_log(log, chain_id).expect("bad log format"); + + assert_eq!(tx, expected); + assert_eq!(tx_hash, expected_tx_hash); + } #[test] fn parse_msg_to_l1() { From 634b6dc829a38ef079ae70d2e7e22e2e21c1d075 Mon Sep 17 00:00:00 2001 From: Kariy Date: Fri, 8 Dec 2023 21:52:52 +0800 Subject: [PATCH 099/192] refactor(katana-provider): handle error properly --- .../provider/src/providers/fork/backend.rs | 146 ++++++++++++------ 1 file changed, 102 insertions(+), 44 deletions(-) diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 95b2abc9ca..f75805cec1 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -6,7 +6,7 @@ use std::task::{Context, Poll}; use std::thread; use anyhow::Result; -use futures::channel::mpsc::{channel, Receiver, Sender, TrySendError}; +use futures::channel::mpsc::{channel, Receiver, SendError, Sender}; use futures::future::BoxFuture; use futures::stream::Stream; use futures::{Future, FutureExt}; @@ -20,10 +20,12 @@ use katana_primitives::conversion::rpc::{ }; use katana_primitives::FieldElement; use parking_lot::Mutex; -use starknet::core::types::{BlockId, ContractClass}; +use starknet::core::types::{BlockId, ContractClass, StarknetError}; use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::{JsonRpcClient, Provider, ProviderError}; -use tracing::trace; +use starknet::providers::{ + JsonRpcClient, MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, +}; +use tracing::{error, trace}; use crate::providers::in_memory::cache::CacheStateDb; use crate::traits::contract::{ContractClassProvider, ContractInfoProvider}; @@ -37,7 +39,7 @@ type GetClassAtResult = Result), + Send(SendError), #[error("Compute class hash error: {0}")] ComputeClassHashError(String), #[error(transparent)] @@ -236,12 +238,12 @@ impl ForkedBackend { &self, contract_address: ContractAddress, ) -> Result { - trace!(target: "forked_backend", "request nonce for contract address {contract_address}"); + trace!(target: "forked_backend", "requesting nonce for contract address {contract_address}"); let (sender, rx) = oneshot(); self.0 .lock() .try_send(BackendRequest::GetNonce(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; + .map_err(|e| ForkedBackendError::Send(e.into_send_error()))?; rx.recv().expect("failed to receive nonce result") } @@ -250,12 +252,12 @@ impl ForkedBackend { contract_address: ContractAddress, key: StorageKey, ) -> Result { - trace!(target: "forked_backend", "request storage for address {contract_address} at key {key:#x}" ); + trace!(target: "forked_backend", "requesting storage for address {contract_address} at key {key:#x}" ); let (sender, rx) = oneshot(); self.0 .lock() .try_send(BackendRequest::GetStorage(contract_address, key, sender)) - .map_err(ForkedBackendError::Send)?; + .map_err(|e| ForkedBackendError::Send(e.into_send_error()))?; rx.recv().expect("failed to receive storage result") } @@ -263,12 +265,12 @@ impl ForkedBackend { &self, contract_address: ContractAddress, ) -> Result { - trace!(target: "forked_backend", "request class hash at address {contract_address}"); + trace!(target: "forked_backend", "requesting class hash at address {contract_address}"); let (sender, rx) = oneshot(); self.0 .lock() .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) - .map_err(ForkedBackendError::Send)?; + .map_err(|e| ForkedBackendError::Send(e.into_send_error()))?; rx.recv().expect("failed to receive class hash result") } @@ -276,12 +278,12 @@ impl ForkedBackend { &self, class_hash: ClassHash, ) -> Result { - trace!(target: "forked_backend", "request class at hash {class_hash:#x}"); + trace!(target: "forked_backend", "requesting class at hash {class_hash:#x}"); let (sender, rx) = oneshot(); self.0 .lock() .try_send(BackendRequest::GetClassAt(class_hash, sender)) - .map_err(ForkedBackendError::Send)?; + .map_err(|e| ForkedBackendError::Send(e.into_send_error()))?; rx.recv().expect("failed to receive class result") } @@ -289,7 +291,7 @@ impl ForkedBackend { &self, class_hash: ClassHash, ) -> Result { - trace!(target: "forked_backend", "request compiled class hash at class {class_hash:#x}"); + trace!(target: "forked_backend", "requesting compiled class hash at class {class_hash:#x}"); let class = self.do_get_class_at(class_hash)?; // if its a legacy class, then we just return back the class hash // else if sierra class, then we have to compile it and compute the compiled class hash. @@ -319,24 +321,26 @@ impl SharedStateProvider { impl ContractInfoProvider for SharedStateProvider { fn contract(&self, address: ContractAddress) -> Result> { - if let Some(info) = self.0.contract_state.read().get(&address).cloned() { - return Ok(Some(info)); - } - - let nonce = self.0.do_get_nonce(address).unwrap(); - let class_hash = self.0.do_get_class_hash_at(address).unwrap(); - let info = GenericContractInfo { nonce, class_hash }; - - self.0.contract_state.write().insert(address, info.clone()); - - Ok(Some(info)) + let info = self.0.contract_state.read().get(&address).cloned(); + Ok(info) } } impl StateProvider for SharedStateProvider { fn nonce(&self, address: ContractAddress) -> Result> { - let nonce = ContractInfoProvider::contract(&self, address)?.map(|i| i.nonce); - Ok(nonce) + if let nonce @ Some(_) = self.contract(address)?.map(|i| i.nonce) { + return Ok(nonce); + } + + if let Some(nonce) = handle_contract_or_class_not_found_err(self.0.do_get_nonce(address)).map_err(|e| { + error!(target: "forked_backend", "error while fetching nonce of contract {address}: {e}"); + e + })? { + self.0.contract_state.write().entry(address).or_default().nonce = nonce; + Ok(Some(nonce)) + } else { + Ok(None) + } } fn storage( @@ -345,20 +349,40 @@ impl StateProvider for SharedStateProvider { storage_key: StorageKey, ) -> Result> { if let value @ Some(_) = - self.0.storage.read().get(&address).and_then(|s| s.get(&storage_key)).copied() + self.0.storage.read().get(&address).and_then(|s| s.get(&storage_key)) { - return Ok(value); + return Ok(value.copied()); } - let value = self.0.do_get_storage(address, storage_key).unwrap(); - self.0.storage.write().entry(address).or_default().insert(storage_key, value); + let value = handle_contract_or_class_not_found_err(self.0.do_get_storage(address, storage_key)).map_err(|e| { + error!(target: "forked_backend", "error while fetching storage value of contract {address} at key {storage_key:#x}: {e}"); + e + })?; + + self.0 + .storage + .write() + .entry(address) + .or_default() + .insert(storage_key, value.unwrap_or_default()); - Ok(Some(value)) + Ok(value) } fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { - let hash = ContractInfoProvider::contract(&self, address)?.map(|i| i.class_hash); - Ok(hash) + if let hash @ Some(_) = self.contract(address)?.map(|i| i.class_hash) { + return Ok(hash); + } + + if let Some(hash) = handle_contract_or_class_not_found_err(self.0.do_get_class_hash_at(address)).map_err(|e| { + error!(target: "forked_backend", "error while fetching class hash of contract {address}: {e}"); + e + })? { + self.0.contract_state.write().entry(address).or_default().class_hash = hash; + Ok(Some(hash)) + } else { + Ok(None) + } } } @@ -368,7 +392,7 @@ impl ContractClassProvider for SharedStateProvider { return Ok(class.cloned()); } - let class = self.0.do_get_class_at(hash).unwrap(); + let class = self.0.do_get_class_at(hash)?; match class { starknet::core::types::ContractClass::Legacy(_) => Ok(None), starknet::core::types::ContractClass::Sierra(sierra_class) => { @@ -377,7 +401,6 @@ impl ContractClassProvider for SharedStateProvider { .sierra_classes .write() .insert(hash, sierra_class.clone()); - Ok(Some(sierra_class)) } } @@ -391,27 +414,42 @@ impl ContractClassProvider for SharedStateProvider { return Ok(hash.cloned()); } - let compiled_hash = self.0.do_get_compiled_class_hash(hash).unwrap(); + let compiled_hash = self.0.do_get_compiled_class_hash(hash)?; self.0.compiled_class_hashes.write().insert(hash, compiled_hash); Ok(Some(hash)) } fn class(&self, hash: ClassHash) -> Result> { - if let Some(class) = - self.0.shared_contract_classes.compiled_classes.read().get(&hash).cloned() - { - return Ok(Some(class)); + if let Some(class) = self.0.shared_contract_classes.compiled_classes.read().get(&hash) { + return Ok(Some(class.clone())); } - let class = self.0.do_get_class_at(hash).unwrap(); + let Some(class) = handle_contract_or_class_not_found_err(self.0.do_get_class_at(hash)) + .map_err(|e| { + error!(target: "forked_backend", "error while fetching class {hash:#x}: {e}"); + e + })? + else { + return Ok(None); + }; + let (class_hash, compiled_class_hash, casm, sierra) = match class { ContractClass::Legacy(class) => { - let (_, compiled_class) = legacy_rpc_to_inner_class(&class)?; + let (_, compiled_class) = legacy_rpc_to_inner_class(&class).map_err(|e| { + error!(target: "forked_backend", "error while parsing legacy class {hash:#x}: {e}"); + e + })?; + (hash, hash, compiled_class, None) } + ContractClass::Sierra(sierra_class) => { - let (_, compiled_class_hash, compiled_class) = rpc_to_inner_class(&sierra_class)?; + let (_, compiled_class_hash, compiled_class) = rpc_to_inner_class(&sierra_class).map_err(|e|{ + error!(target: "forked_backend", "error while parsing sierra class {hash:#x}: {e}"); + e + })?; + (hash, compiled_class_hash, compiled_class, Some(sierra_class)) } }; @@ -438,6 +476,26 @@ impl ContractClassProvider for SharedStateProvider { } } +fn handle_contract_or_class_not_found_err( + result: Result, +) -> Result, ForkedBackendError> { + match result { + Ok(value) => Ok(Some(value)), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: + MaybeUnknownErrorCode::Known( + StarknetError::ContractNotFound | StarknetError::ClassHashNotFound, + ), + .. + }, + ))) => Ok(None), + + Err(e) => Err(e), + } +} + #[cfg(test)] mod tests { use katana_primitives::block::BlockNumber; From 74149441b8c0d08d8b8554215ad2320b57b2b05e Mon Sep 17 00:00:00 2001 From: Kariy Date: Fri, 8 Dec 2023 21:54:22 +0800 Subject: [PATCH 100/192] refactor(katana-provider): get tx status from block status --- .../storage/provider/src/providers/fork/mod.rs | 14 ++++++++++---- .../provider/src/providers/in_memory/cache.rs | 2 -- .../provider/src/providers/in_memory/mod.rs | 15 +++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 32b4df213a..c43e5ec635 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -290,13 +290,19 @@ impl TransactionsProviderExt for ForkedProvider { impl TransactionStatusProvider for ForkedProvider { fn transaction_status(&self, hash: TxHash) -> Result> { - let status = self + let tx_block = self .storage .read() .transaction_numbers .get(&hash) - .and_then(|n| self.storage.read().transaction_status.get(n).copied()); - Ok(status) + .and_then(|n| self.storage.read().transaction_block.get(n).copied()); + + if let Some(num) = tx_block { + let status = self.block_status(num.into())?; + Ok(status) + } else { + Ok(None) + } } } @@ -404,7 +410,7 @@ impl BlockWriter for ForkedProvider { // create block body indices let tx_count = txs.len() as u64; - let tx_offset = self.storage.read().transactions.len() as u64; + let tx_offset = storage.transactions.len() as u64; let block_body_indices = StoredBlockBodyIndices { tx_offset, tx_count }; let (txs_id, txs): (Vec<(TxNumber, TxHash)>, Vec) = txs diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs index 0544012692..960d5307be 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/cache.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -85,7 +85,6 @@ pub struct CacheDb { pub(crate) transaction_hashes: HashMap, pub(crate) transaction_numbers: HashMap, pub(crate) transaction_block: HashMap, - pub(crate) transaction_status: HashMap, } impl CacheStateDb { @@ -112,7 +111,6 @@ impl CacheDb { block_numbers: HashMap::new(), block_statusses: HashMap::new(), transaction_block: HashMap::new(), - transaction_status: HashMap::new(), transaction_hashes: HashMap::new(), block_body_indices: HashMap::new(), transaction_numbers: HashMap::new(), diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 477f8ba149..b727b44638 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -266,7 +266,6 @@ impl TransactionProvider for InMemoryProvider { let storage_read = self.storage.read(); let Some(number) = storage_read.transaction_numbers.get(&hash) else { return Ok(None) }; - println!("number: {:?}", number); let block_num = storage_read.transaction_block.get(number).expect("block num should exist"); let block_hash = storage_read.block_hashes.get(block_num).expect("block hash should exist"); @@ -288,13 +287,19 @@ impl TransactionsProviderExt for InMemoryProvider { impl TransactionStatusProvider for InMemoryProvider { fn transaction_status(&self, hash: TxHash) -> Result> { - let status = self + let tx_block = self .storage .read() .transaction_numbers .get(&hash) - .and_then(|n| self.storage.read().transaction_status.get(n).copied()); - Ok(status) + .and_then(|n| self.storage.read().transaction_block.get(n).copied()); + + if let Some(num) = tx_block { + let status = self.block_status(num.into())?; + Ok(status) + } else { + Ok(None) + } } } @@ -413,7 +418,6 @@ impl BlockWriter for InMemoryProvider { let txs_num = txs_id.clone().into_iter().map(|(num, hash)| (hash, num)); let txs_block = txs_id.clone().into_iter().map(|(num, _)| (num, block_number)); - let txs_status = txs_id.clone().into_iter().map(|(num, _)| (num, block.status)); storage.latest_block_hash = block_hash; storage.latest_block_number = block_number; @@ -428,7 +432,6 @@ impl BlockWriter for InMemoryProvider { storage.transaction_hashes.extend(txs_id); storage.transaction_numbers.extend(txs_num); storage.transaction_block.extend(txs_block); - storage.transaction_status.extend(txs_status); storage.receipts.extend(receipts); storage.state_update.insert(block_number, states.state_updates.clone()); From 270ce93cf73dbe074843bc2a513639e6fac52bef Mon Sep 17 00:00:00 2001 From: Kariy Date: Fri, 8 Dec 2023 22:16:31 +0800 Subject: [PATCH 101/192] test(katana-core): add tests for storage creation --- Cargo.lock | 12 +- crates/katana/core/src/backend/mod.rs | 26 ++-- crates/katana/core/src/backend/storage.rs | 145 ++++++++++++++++++++-- crates/katana/core/src/utils/mod.rs | 2 +- crates/katana/primitives/src/block.rs | 8 +- crates/katana/src/args.rs | 6 +- 6 files changed, 170 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a604d9ced..78edafec05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5118,7 +5118,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5133,7 +5133,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "blockifier", @@ -5149,7 +5149,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "blockifier", @@ -5166,7 +5166,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "auto_impl", @@ -5215,7 +5215,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "derive_more", @@ -5228,7 +5228,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.3.14" +version = "0.3.15" dependencies = [ "anyhow", "katana-executor", diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 4c3d431d74..3dcf35c6c0 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use blockifier::block_context::BlockContext; use katana_primitives::block::{ - Block, BlockHashOrNumber, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, + Block, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, }; use katana_primitives::contract::ContractAddress; use katana_primitives::receipt::Receipt; @@ -14,7 +14,7 @@ use katana_provider::providers::in_memory::InMemoryProvider; use katana_provider::traits::block::{BlockHashProvider, BlockWriter}; use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; use parking_lot::RwLock; -use starknet::core::types::{BlockId, MaybePendingBlockWithTxHashes}; +use starknet::core::types::{BlockId, BlockStatus, MaybePendingBlockWithTxHashes}; use starknet::core::utils::parse_cairo_short_string; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; @@ -56,9 +56,7 @@ impl Backend { .with_balance(*DEFAULT_PREFUNDED_ACCOUNT_BALANCE) .generate(); - let provider: Box = if let Some(forked_url) = - &config.fork_rpc_url - { + let blockchain: Blockchain = if let Some(forked_url) = &config.fork_rpc_url { let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(forked_url.clone()))); let forked_chain_id = provider.chain_id().await.unwrap(); @@ -90,12 +88,24 @@ impl Backend { forked_url ); - Box::new(ForkedProvider::new(provider, BlockHashOrNumber::Num(forked_block_num))) + Blockchain::new_from_forked( + ForkedProvider::new(provider, forked_block_num.into()), + block.block_hash, + block.parent_hash, + &block_context, + block.new_root, + match block.status { + BlockStatus::AcceptedOnL1 => FinalityStatus::AcceptedOnL1, + BlockStatus::AcceptedOnL2 => FinalityStatus::AcceptedOnL2, + _ => panic!("unable to fork for non-accepted block"), + }, + ) + .expect("able to create forked blockchain") } else { - Box::new(InMemoryProvider::new()) + Blockchain::new_with_genesis(InMemoryProvider::new(), &block_context) + .expect("able to create blockchain from genesis block") }; - let blockchain = Blockchain::new_with_genesis(provider, &block_context).unwrap(); let env = Env { block: block_context }; for acc in &accounts { diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index f6b32b1c6e..545e06a453 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -1,9 +1,10 @@ use anyhow::Result; use blockifier::block_context::BlockContext; use katana_primitives::block::{ - Block, FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, + Block, BlockHash, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, }; use katana_primitives::state::StateUpdatesWithDeclaredClasses; +use katana_primitives::FieldElement; use katana_provider::traits::block::{BlockProvider, BlockWriter}; use katana_provider::traits::contract::{ContractClassWriter, ContractInfoProvider}; use katana_provider::traits::state::{StateFactoryProvider, StateRootProvider, StateWriter}; @@ -63,30 +64,154 @@ impl Blockchain { Self { inner: BlockchainProvider::new(Box::new(provider)) } } - pub fn provider(&self) -> &BlockchainProvider> { - &self.inner - } - pub fn new_with_genesis(provider: impl Database, block_context: &BlockContext) -> Result { let header = PartialHeader { - gas_price: block_context.gas_price, - number: 0, parent_hash: 0u8.into(), + gas_price: block_context.gas_price, + number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: *SEQUENCER_ADDRESS, }; - let block = Block { header: Header::new(header, 0u8.into()), body: vec![] }.seal(); + let block = SealedBlockWithStatus { + status: FinalityStatus::AcceptedOnL1, + block: Block { header: Header::new(header, 0u8.into()), body: vec![] }.seal(), + }; + Self::new_with_block_and_state(provider, block, get_genesis_states_for_testing()) } + // TODO: make this function to just accept a `Header` created from the forked block. + /// Builds a new blockchain with a forked block. + pub fn new_from_forked( + provider: impl Database, + block_hash: BlockHash, + parent_hash: FieldElement, + block_context: &BlockContext, + state_root: FieldElement, + block_status: FinalityStatus, + ) -> Result { + let header = Header { + state_root, + parent_hash, + gas_price: block_context.gas_price, + number: block_context.block_number.0, + timestamp: block_context.block_timestamp.0, + sequencer_address: *SEQUENCER_ADDRESS, + }; + + let block = SealedBlockWithStatus { + status: block_status, + block: Block { header, body: vec![] }.seal_with_hash(block_hash), + }; + + Self::new_with_block_and_state(provider, block, StateUpdatesWithDeclaredClasses::default()) + } + + pub fn provider(&self) -> &BlockchainProvider> { + &self.inner + } + fn new_with_block_and_state( provider: impl Database, - block: SealedBlock, + block: SealedBlockWithStatus, states: StateUpdatesWithDeclaredClasses, ) -> Result { - let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL1 }; BlockWriter::insert_block_with_states_and_receipts(&provider, block, states, vec![])?; Ok(Self::new(provider)) } } + +#[cfg(test)] +mod tests { + use blockifier::block_context::BlockContext; + use katana_primitives::block::FinalityStatus; + use katana_primitives::FieldElement; + use katana_provider::providers::in_memory::InMemoryProvider; + use katana_provider::traits::block::{ + BlockHashProvider, BlockNumberProvider, BlockStatusProvider, HeaderProvider, + }; + use katana_provider::traits::state::StateFactoryProvider; + use starknet::macros::felt; + use starknet_api::block::{BlockNumber, BlockTimestamp}; + use starknet_api::core::ChainId; + + use super::Blockchain; + use crate::constants::{ + ERC20_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, UDC_ADDRESS, UDC_CLASS_HASH, + }; + + #[test] + fn blockchain_from_genesis_states() { + let provider = InMemoryProvider::new(); + let block_context = BlockContext { + gas_price: 0, + max_recursion_depth: 0, + validate_max_n_steps: 0, + invoke_tx_max_n_steps: 0, + block_number: BlockNumber(0), + chain_id: ChainId("test".into()), + block_timestamp: BlockTimestamp(0), + sequencer_address: Default::default(), + fee_token_address: Default::default(), + vm_resource_fee_cost: Default::default(), + }; + + let blockchain = Blockchain::new_with_genesis(provider, &block_context) + .expect("failed to create blockchain from genesis block"); + let state = blockchain.provider().latest().expect("failed to get latest state"); + + let latest_number = blockchain.provider().latest_number().unwrap(); + let fee_token_class_hash = + state.class_hash_of_contract(*FEE_TOKEN_ADDRESS).unwrap().unwrap(); + let udc_class_hash = state.class_hash_of_contract(*UDC_ADDRESS).unwrap().unwrap(); + + assert_eq!(latest_number, 0); + assert_eq!(udc_class_hash, *UDC_CLASS_HASH); + assert_eq!(fee_token_class_hash, *ERC20_CONTRACT_CLASS_HASH); + } + + #[test] + fn blockchain_from_fork() { + let provider = InMemoryProvider::new(); + + let block_context = BlockContext { + gas_price: 9090, + max_recursion_depth: 0, + validate_max_n_steps: 0, + invoke_tx_max_n_steps: 0, + chain_id: ChainId("test".into()), + block_number: BlockNumber(23), + block_timestamp: BlockTimestamp(6868), + sequencer_address: Default::default(), + fee_token_address: Default::default(), + vm_resource_fee_cost: Default::default(), + }; + + let blockchain = Blockchain::new_from_forked( + provider, + felt!("1111"), + FieldElement::ZERO, + &block_context, + felt!("1334"), + FinalityStatus::AcceptedOnL1, + ) + .expect("failed to create fork blockchain"); + + let latest_number = blockchain.provider().latest_number().unwrap(); + let latest_hash = blockchain.provider().latest_hash().unwrap(); + let header = blockchain.provider().header(latest_number.into()).unwrap().unwrap(); + let block_status = + blockchain.provider().block_status(latest_number.into()).unwrap().unwrap(); + + assert_eq!(latest_number, 23); + assert_eq!(latest_hash, felt!("1111")); + + assert_eq!(header.gas_price, 9090); + assert_eq!(header.timestamp, 6868); + assert_eq!(header.number, latest_number); + assert_eq!(header.state_root, felt!("1334")); + assert_eq!(header.parent_hash, FieldElement::ZERO); + assert_eq!(block_status, FinalityStatus::AcceptedOnL1); + } +} diff --git a/crates/katana/core/src/utils/mod.rs b/crates/katana/core/src/utils/mod.rs index 61f6dc7442..825533216c 100644 --- a/crates/katana/core/src/utils/mod.rs +++ b/crates/katana/core/src/utils/mod.rs @@ -87,7 +87,7 @@ pub fn convert_state_diff_to_rpc_state_diff(state_diff: CommitmentStateDiff) -> } } -pub fn get_genesis_states_for_testing() -> StateUpdatesWithDeclaredClasses { +pub(super) fn get_genesis_states_for_testing() -> StateUpdatesWithDeclaredClasses { let nonce_updates = HashMap::from([(*UDC_ADDRESS, 0u8.into()), (*FEE_TOKEN_ADDRESS, 0u8.into())]); diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index aa4662a627..c860d2065b 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -20,7 +20,7 @@ pub type BlockNumber = u64; pub type BlockHash = FieldElement; /// Finality status of a canonical block. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum FinalityStatus { AcceptedOnL2, @@ -99,9 +99,15 @@ pub struct BlockWithTxHashes { } impl Block { + /// Seals the block. This computes the hash of the block. pub fn seal(self) -> SealedBlock { SealedBlock { header: self.header.seal(), body: self.body } } + + /// Seals the block with a given hash. + pub fn seal_with_hash(self, hash: BlockHash) -> SealedBlock { + SealedBlock { header: SealedHeader { hash, header: self.header }, body: self.body } + } } #[derive(Debug, Clone)] diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index bbee045459..0bbdce35e7 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -145,9 +145,9 @@ pub struct EnvironmentOptions { impl KatanaArgs { pub fn init_logging(&self) -> Result<(), Box> { - const DEFAULT_LOG_FILTER: &str = "info,executor=trace,server=debug,katana_core=trace,\ - blockifier=off,jsonrpsee_server=off,hyper=off,\ - messaging=debug"; + const DEFAULT_LOG_FILTER: &str = "info,executor=trace,forked_backend=trace,server=debug,\ + katana_core=trace,blockifier=off,jsonrpsee_server=off,\ + hyper=off,messaging=debug"; let builder = fmt::Subscriber::builder().with_env_filter( EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, From e6ff218ffc7bcf8e3e1897319ea381016397e583 Mon Sep 17 00:00:00 2001 From: Kariy Date: Fri, 8 Dec 2023 22:34:12 +0800 Subject: [PATCH 102/192] test(katana-provider): fix test --- crates/katana/storage/provider/src/providers/fork/backend.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index f75805cec1..96ac74e4fc 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -566,11 +566,10 @@ mod tests { } #[test] - #[should_panic] - fn fetch_from_fork_will_panic_if_backend_thread_not_running() { + fn fetch_from_fork_will_err_if_backend_thread_not_running() { let (backend, _) = create_forked_backend(LOCAL_RPC_URL.into(), 1); let provider = SharedStateProvider(Arc::new(CacheStateDb::new(backend))); - let _ = StateProvider::nonce(&provider, ADDR_1); + assert!(StateProvider::nonce(&provider, ADDR_1).is_err()) } const FORKED_URL: &str = From d7a174c7e9e63e3a2e53fbfd59807f500e76cbf1 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 8 Dec 2023 10:54:04 -0500 Subject: [PATCH 103/192] Prepare v0.4.0-rc0 --- Cargo.lock | 42 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 4 +- examples/spawn-and-move/Scarb.toml | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78edafec05..e0c8c5a881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2461,7 +2461,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "dojo-languge-server" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2530,7 +2530,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "starknet", @@ -2538,7 +2538,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "assert_fs", @@ -2569,7 +2569,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "crypto-bigint", "hex", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "assert_fs", @@ -5066,7 +5066,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "assert_matches", "clap", @@ -5084,7 +5084,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "assert_matches", @@ -5118,7 +5118,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5133,7 +5133,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "blockifier", @@ -5149,7 +5149,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "blockifier", @@ -5166,7 +5166,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "auto_impl", @@ -5183,7 +5183,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "assert_matches", @@ -5215,7 +5215,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "derive_more", @@ -5228,7 +5228,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "katana-executor", @@ -7800,7 +7800,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "assert_fs", @@ -8962,7 +8962,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "async-trait", "camino", @@ -8988,7 +8988,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "async-trait", @@ -9024,7 +9024,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "async-graphql", @@ -9063,7 +9063,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "bytes", "crypto-bigint", @@ -9101,7 +9101,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index c9f1028b12..f2a26833fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.3.15" +version = "0.4.0-rc0" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index e823bee6d9..9e87daaffa 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index aea83290b9..cf78540539 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.3.1" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.3.15" +version = "0.4.0-rc0" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 3b99dabe1d..b6861d3661 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.3.15" +version = "0.4.0-rc0" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 6d7f9768f6..b5b1412454 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,14 +3,14 @@ version = 1 [[package]] name = "dojo" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_examples" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "dojo", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 876b73d7ba..97759bbd25 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "dojo_examples" -version = "0.3.15" +version = "0.4.0-rc0" [cairo] sierra-replace-ids = true From 8b758b5cc2d61cd4f107091147090e28c6a65a18 Mon Sep 17 00:00:00 2001 From: Yun Date: Fri, 8 Dec 2023 13:00:42 -0800 Subject: [PATCH 104/192] Cleanup grpc types (#1244) --- crates/dojo-types/src/primitive.rs | 14 +- crates/torii/client/src/client/mod.rs | 3 +- crates/torii/grpc/src/server/mod.rs | 118 ++------ .../torii/grpc/src/{types.rs => types/mod.rs} | 162 +---------- crates/torii/grpc/src/types/schema.rs | 267 ++++++++++++++++++ 5 files changed, 300 insertions(+), 264 deletions(-) rename crates/torii/grpc/src/{types.rs => types/mod.rs} (55%) create mode 100644 crates/torii/grpc/src/types/schema.rs diff --git a/crates/dojo-types/src/primitive.rs b/crates/dojo-types/src/primitive.rs index 49d67fc9a5..c2727e75e7 100644 --- a/crates/dojo-types/src/primitive.rs +++ b/crates/dojo-types/src/primitive.rs @@ -114,7 +114,19 @@ impl Primitive { set_primitive!(set_contract_address, ContractAddress, FieldElement); pub fn to_numeric(&self) -> usize { - Self::iter().position(|p| p == *self).unwrap() + match self { + Primitive::U8(_) => 0, + Primitive::U16(_) => 1, + Primitive::U32(_) => 2, + Primitive::U64(_) => 3, + Primitive::U128(_) => 4, + Primitive::U256(_) => 5, + Primitive::USize(_) => 6, + Primitive::Bool(_) => 7, + Primitive::Felt252(_) => 8, + Primitive::ClassHash(_) => 9, + Primitive::ContractAddress(_) => 10, + } } pub fn from_numeric(value: usize) -> Option { diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index 9520ef7782..08c2f1694d 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -18,7 +18,8 @@ use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; use torii_grpc::client::EntityUpdateStreaming; use torii_grpc::proto::world::RetrieveEntitiesResponse; -use torii_grpc::types::{Entity, KeysClause, Query}; +use torii_grpc::types::schema::Entity; +use torii_grpc::types::{KeysClause, Query}; use self::error::{Error, ParseError}; use self::storage::ModelStorage; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index de83ad13cb..1e32ba5308 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -8,8 +8,7 @@ use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; -use dojo_types::primitive::Primitive; -use dojo_types::schema::{Struct, Ty}; +use dojo_types::schema::Ty; use futures::Stream; use proto::world::{ MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, @@ -28,7 +27,7 @@ use tonic::transport::Server; use tonic::{Request, Response, Status}; use torii_core::cache::ModelCache; use torii_core::error::{Error, ParseError, QueryError}; -use torii_core::model::build_sql_query; +use torii_core::model::{build_sql_query, map_row_to_ty}; use self::subscription::SubscribeRequest; use crate::proto::types::clause::ClauseType; @@ -132,11 +131,15 @@ impl DojoWorld { let entity_query = format!("{} WHERE entities.id = ?", build_sql_query(&schemas)?); let row = sqlx::query(&entity_query).bind(&entity_id).fetch_one(&self.pool).await?; - let mut models = Vec::with_capacity(schemas.len()); - for schema in schemas { - let struct_ty = schema.as_struct().expect("schema should be struct"); - models.push(Self::map_row_to_struct(&schema.name(), struct_ty, &row)?.into()); - } + let models = schemas + .iter() + .map(|s| { + let mut struct_ty = s.as_struct().expect("schema should be struct").to_owned(); + map_row_to_ty(&s.name(), &mut struct_ty, &row)?; + + Ok(struct_ty.try_into().unwrap()) + }) + .collect::, Error>>()?; let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) @@ -310,103 +313,14 @@ impl DojoWorld { let models = schemas .iter() .map(|schema| { - let struct_ty = schema.as_struct().expect("schema should be struct"); - Self::map_row_to_struct(&schema.name(), struct_ty, row).map(Into::into) - }) - .collect::, Error>>()?; - - Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) - } - - /// Helper function to map Sqlite row to proto::types::Struct - // TODO: refactor this to use `map_row_to_ty` from core and implement Ty to protobuf conversion - fn map_row_to_struct( - path: &str, - struct_ty: &Struct, - row: &SqliteRow, - ) -> Result { - let children = struct_ty - .children - .iter() - .map(|member| { - let column_name = format!("{}.{}", path, member.name); - let name = member.name.clone(); - let ty_type = match &member.ty { - Ty::Primitive(primitive) => { - let value_type = match primitive { - Primitive::Bool(_) => Some(proto::types::value::ValueType::BoolValue( - row.try_get::(&column_name)?, - )), - Primitive::U8(_) - | Primitive::U16(_) - | Primitive::U32(_) - | Primitive::U64(_) - | Primitive::USize(_) => { - let value = row.try_get::(&column_name)?; - Some(proto::types::value::ValueType::UintValue(value as u64)) - } - Primitive::U128(_) - | Primitive::Felt252(_) - | Primitive::ClassHash(_) - | Primitive::ContractAddress(_) => { - let value = row.try_get::(&column_name)?; - let felt = - FieldElement::from_str(&value).map_err(ParseError::FromStr)?; - Some(proto::types::value::ValueType::ByteValue( - felt.to_bytes_be().to_vec(), - )) - } - Primitive::U256(_) => { - let value = row.try_get::(&column_name)?; - Some(proto::types::value::ValueType::StringValue(value)) - } - }; - - Some(proto::types::ty::TyType::Primitive(proto::types::Primitive { - value: Some(proto::types::Value { value_type }), - r#type: primitive.to_numeric() as i32, - })) - } - Ty::Enum(enum_ty) => { - let value = row.try_get::(&column_name)?; - let options = enum_ty - .options - .iter() - .map(|r#enum| proto::types::EnumOption { - name: r#enum.name.clone(), - ty: None, - }) - .collect::>(); - let option = - options.iter().position(|o| o.name == value).expect("wrong enum value") - as u32; - - Some(proto::types::ty::TyType::Enum(proto::types::Enum { - option, - options, - name: member.ty.name(), - })) - } - Ty::Struct(struct_ty) => { - let path = [path, &struct_ty.name].join("$"); - Some(proto::types::ty::TyType::Struct(Self::map_row_to_struct( - &path, struct_ty, row, - )?)) - } - ty => { - unimplemented!("unimplemented type_enum: {ty}"); - } - }; + let mut struct_ty = schema.as_struct().expect("schema should be struct").to_owned(); + map_row_to_ty(&schema.name(), &mut struct_ty, row)?; - Ok(proto::types::Member { - name, - ty: Some(proto::types::Ty { ty_type }), - key: member.key, - }) + Ok(struct_ty.try_into().unwrap()) }) - .collect::, Error>>()?; + .collect::, Error>>()?; - Ok(proto::types::Struct { name: struct_ty.name.clone(), children }) + Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) } } diff --git a/crates/torii/grpc/src/types.rs b/crates/torii/grpc/src/types/mod.rs similarity index 55% rename from crates/torii/grpc/src/types.rs rename to crates/torii/grpc/src/types/mod.rs index ddb6820cc2..25531536df 100644 --- a/crates/torii/grpc/src/types.rs +++ b/crates/torii/grpc/src/types/mod.rs @@ -2,27 +2,16 @@ use std::collections::HashMap; use std::str::FromStr; use dojo_types::primitive::Primitive; -use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use dojo_types::schema::Ty; use serde::{Deserialize, Serialize}; use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; use starknet_crypto::FieldElement; -use crate::client::Error as ClientError; use crate::proto::{self}; -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct Entity { - pub key: FieldElement, - pub models: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] -pub struct Model { - pub name: String, - pub members: Vec, -} +pub mod schema; #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Query { @@ -170,34 +159,6 @@ impl TryFrom for KeysClause { } } -impl TryFrom for Entity { - type Error = ClientError; - fn try_from(entity: proto::types::Entity) -> Result { - Ok(Self { - key: FieldElement::from_byte_slice_be(&entity.key).map_err(ClientError::SliceError)?, - models: entity - .models - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?, - }) - } -} - -impl TryFrom for Model { - type Error = ClientError; - fn try_from(model: proto::types::Model) -> Result { - Ok(Self { - name: model.name, - members: model - .members - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?, - }) - } -} - impl From for proto::types::MemberClause { fn from(value: MemberClause) -> Self { Self { @@ -233,125 +194,6 @@ impl From for proto::types::Value { } } -impl From for EnumOption { - fn from(option: proto::types::EnumOption) -> Self { - EnumOption { name: option.name, ty: Ty::Tuple(vec![]) } - } -} - -impl From for Enum { - fn from(r#enum: proto::types::Enum) -> Self { - Enum { - name: r#enum.name.clone(), - option: Some(r#enum.option as u8), - options: r#enum.options.into_iter().map(Into::into).collect::>(), - } - } -} - -impl TryFrom for Struct { - type Error = ClientError; - fn try_from(r#struct: proto::types::Struct) -> Result { - Ok(Struct { - name: r#struct.name, - children: r#struct - .children - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?, - }) - } -} - -impl From for proto::types::Model { - fn from(r#struct: proto::types::Struct) -> Self { - Self { name: r#struct.name, members: r#struct.children } - } -} - -// FIX: weird catch-22 issue - prost Enum has `try_from` trait we can use, however, using it results -// in wasm compile err about From missing. Implementing that trait results in clippy error -// about duplicate From... Workaround is to use deprecated `from_i32` and allow deprecation -// warning. -#[allow(deprecated)] -impl TryFrom for Primitive { - type Error = ClientError; - fn try_from(primitive: proto::types::Primitive) -> Result { - let primitive_type = primitive.r#type; - let value_type = primitive - .value - .ok_or(ClientError::MissingExpectedData)? - .value_type - .ok_or(ClientError::MissingExpectedData)?; - - let primitive = match &value_type { - proto::types::value::ValueType::BoolValue(bool) => Primitive::Bool(Some(*bool)), - proto::types::value::ValueType::UintValue(int) => { - match proto::types::PrimitiveType::from_i32(primitive_type) { - Some(proto::types::PrimitiveType::U8) => Primitive::U8(Some(*int as u8)), - Some(proto::types::PrimitiveType::U16) => Primitive::U16(Some(*int as u16)), - Some(proto::types::PrimitiveType::U32) => Primitive::U32(Some(*int as u32)), - Some(proto::types::PrimitiveType::U64) => Primitive::U64(Some(*int)), - Some(proto::types::PrimitiveType::Usize) => Primitive::USize(Some(*int as u32)), - _ => return Err(ClientError::UnsupportedType), - } - } - proto::types::value::ValueType::ByteValue(bytes) => { - match proto::types::PrimitiveType::from_i32(primitive_type) { - Some(proto::types::PrimitiveType::U128) - | Some(proto::types::PrimitiveType::Felt252) - | Some(proto::types::PrimitiveType::ClassHash) - | Some(proto::types::PrimitiveType::ContractAddress) => { - Primitive::Felt252(Some( - FieldElement::from_byte_slice_be(bytes) - .map_err(ClientError::SliceError)?, - )) - } - _ => return Err(ClientError::UnsupportedType), - } - } - proto::types::value::ValueType::StringValue(_string) => { - match proto::types::PrimitiveType::from_i32(primitive_type) { - Some(proto::types::PrimitiveType::U256) => { - // TODO: Handle u256 - Primitive::U256(None) - } - _ => return Err(ClientError::UnsupportedType), - } - } - _ => { - return Err(ClientError::UnsupportedType); - } - }; - - Ok(primitive) - } -} - -impl TryFrom for Ty { - type Error = ClientError; - fn try_from(ty: proto::types::Ty) -> Result { - match ty.ty_type.ok_or(ClientError::MissingExpectedData)? { - proto::types::ty::TyType::Primitive(primitive) => { - Ok(Ty::Primitive(primitive.try_into()?)) - } - proto::types::ty::TyType::Struct(r#struct) => Ok(Ty::Struct(r#struct.try_into()?)), - proto::types::ty::TyType::Enum(r#enum) => Ok(Ty::Enum(r#enum.into())), - } - } -} - -impl TryFrom for Member { - type Error = ClientError; - fn try_from(member: proto::types::Member) -> Result { - Ok(Member { - name: member.name, - ty: member.ty.ok_or(ClientError::MissingExpectedData)?.try_into()?, - key: member.key, - }) - } -} - impl TryFrom for StorageEntry { type Error = FromStrError; fn try_from(value: proto::types::StorageEntry) -> Result { diff --git a/crates/torii/grpc/src/types/schema.rs b/crates/torii/grpc/src/types/schema.rs new file mode 100644 index 0000000000..72047b6f06 --- /dev/null +++ b/crates/torii/grpc/src/types/schema.rs @@ -0,0 +1,267 @@ +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use serde::{Deserialize, Serialize}; +use starknet_crypto::FieldElement; + +use crate::client::Error as ClientError; +use crate::proto::{self}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Entity { + pub key: FieldElement, + pub models: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +pub struct Model { + pub name: String, + pub members: Vec, +} + +impl TryFrom for Entity { + type Error = ClientError; + fn try_from(entity: proto::types::Entity) -> Result { + Ok(Self { + key: FieldElement::from_byte_slice_be(&entity.key).map_err(ClientError::SliceError)?, + models: entity + .models + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for Model { + type Error = ClientError; + fn try_from(model: proto::types::Model) -> Result { + Ok(Self { + name: model.name, + members: model + .members + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for proto::types::Ty { + type Error = ClientError; + fn try_from(ty: Ty) -> Result { + let ty_type = match ty { + Ty::Primitive(primitive) => { + Some(proto::types::ty::TyType::Primitive(primitive.try_into()?)) + } + Ty::Enum(r#enum) => Some(proto::types::ty::TyType::Enum(r#enum.into())), + Ty::Struct(r#struct) => Some(proto::types::ty::TyType::Struct(r#struct.try_into()?)), + Ty::Tuple(_) => unimplemented!("unimplemented typle type"), + }; + + Ok(proto::types::Ty { ty_type }) + } +} + +impl TryFrom for Member { + type Error = ClientError; + fn try_from(member: proto::types::Member) -> Result { + Ok(Member { + name: member.name, + ty: member.ty.ok_or(ClientError::MissingExpectedData)?.try_into()?, + key: member.key, + }) + } +} + +impl TryFrom for proto::types::Member { + type Error = ClientError; + fn try_from(member: Member) -> Result { + Ok(proto::types::Member { + name: member.name, + ty: Some(member.ty.try_into()?), + key: member.key, + }) + } +} + +impl From for EnumOption { + fn from(option: proto::types::EnumOption) -> Self { + EnumOption { name: option.name, ty: Ty::Tuple(vec![]) } + } +} + +impl From for proto::types::EnumOption { + fn from(option: EnumOption) -> Self { + proto::types::EnumOption { name: option.name, ty: None } + } +} + +impl From for Enum { + fn from(r#enum: proto::types::Enum) -> Self { + Enum { + name: r#enum.name.clone(), + option: Some(r#enum.option as u8), + options: r#enum.options.into_iter().map(Into::into).collect::>(), + } + } +} + +impl From for proto::types::Enum { + fn from(r#enum: Enum) -> Self { + proto::types::Enum { + name: r#enum.name, + option: r#enum.option.expect("option value") as u32, + options: r#enum.options.into_iter().map(Into::into).collect::>(), + } + } +} + +impl TryFrom for Struct { + type Error = ClientError; + fn try_from(r#struct: proto::types::Struct) -> Result { + Ok(Struct { + name: r#struct.name, + children: r#struct + .children + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for proto::types::Struct { + type Error = ClientError; + fn try_from(r#struct: Struct) -> Result { + Ok(proto::types::Struct { + name: r#struct.name, + children: r#struct + .children + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for proto::types::Model { + type Error = ClientError; + fn try_from(r#struct: Struct) -> Result { + let r#struct: proto::types::Struct = r#struct.try_into()?; + + Ok(r#struct.into()) + } +} + +impl From for proto::types::Model { + fn from(r#struct: proto::types::Struct) -> Self { + Self { name: r#struct.name, members: r#struct.children } + } +} + +// FIX: weird catch-22 issue - prost Enum has `try_from` trait we can use, however, using it results +// in wasm compile err about From missing. Implementing that trait results in clippy error +// about duplicate From... Workaround is to use deprecated `from_i32` and allow deprecation +// warning. +#[allow(deprecated)] +impl TryFrom for Primitive { + type Error = ClientError; + fn try_from(primitive: proto::types::Primitive) -> Result { + let primitive_type = primitive.r#type; + let value_type = primitive + .value + .ok_or(ClientError::MissingExpectedData)? + .value_type + .ok_or(ClientError::MissingExpectedData)?; + + let primitive = match &value_type { + proto::types::value::ValueType::BoolValue(bool) => Primitive::Bool(Some(*bool)), + proto::types::value::ValueType::UintValue(int) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U8) => Primitive::U8(Some(*int as u8)), + Some(proto::types::PrimitiveType::U16) => Primitive::U16(Some(*int as u16)), + Some(proto::types::PrimitiveType::U32) => Primitive::U32(Some(*int as u32)), + Some(proto::types::PrimitiveType::U64) => Primitive::U64(Some(*int)), + Some(proto::types::PrimitiveType::Usize) => Primitive::USize(Some(*int as u32)), + _ => return Err(ClientError::UnsupportedType), + } + } + proto::types::value::ValueType::ByteValue(bytes) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U128) + | Some(proto::types::PrimitiveType::Felt252) + | Some(proto::types::PrimitiveType::ClassHash) + | Some(proto::types::PrimitiveType::ContractAddress) => { + Primitive::Felt252(Some( + FieldElement::from_byte_slice_be(bytes) + .map_err(ClientError::SliceError)?, + )) + } + _ => return Err(ClientError::UnsupportedType), + } + } + proto::types::value::ValueType::StringValue(_string) => { + match proto::types::PrimitiveType::from_i32(primitive_type) { + Some(proto::types::PrimitiveType::U256) => { + // TODO: Handle u256 + Primitive::U256(None) + } + _ => return Err(ClientError::UnsupportedType), + } + } + _ => { + return Err(ClientError::UnsupportedType); + } + }; + + Ok(primitive) + } +} + +impl TryFrom for proto::types::Primitive { + type Error = ClientError; + fn try_from(primitive: Primitive) -> Result { + use proto::types::value::ValueType; + + let value_type = match primitive { + Primitive::Bool(bool) => bool.map(ValueType::BoolValue), + Primitive::U8(u8) => u8.map(|val| ValueType::UintValue(val as u64)), + Primitive::U16(u16) => u16.map(|val| ValueType::UintValue(val as u64)), + Primitive::U32(u32) => u32.map(|val| ValueType::UintValue(val as u64)), + Primitive::U64(u64) => u64.map(ValueType::UintValue), + Primitive::USize(usize) => usize.map(|val| ValueType::UintValue(val as u64)), + Primitive::U128(u128) => { + u128.map(|val| ValueType::ByteValue(val.to_be_bytes().to_vec())) + } + Primitive::U256(u256) => u256.map(|val| ValueType::StringValue(val.to_string())), + Primitive::Felt252(felt) => { + felt.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + Primitive::ClassHash(class) => { + class.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + Primitive::ContractAddress(contract) => { + contract.map(|val| ValueType::ByteValue(val.to_bytes_be().to_vec())) + } + } + .expect("value expected"); + + Ok(proto::types::Primitive { + value: Some(proto::types::Value { value_type: Some(value_type) }), + r#type: primitive.to_numeric() as i32, + }) + } +} + +impl TryFrom for Ty { + type Error = ClientError; + fn try_from(ty: proto::types::Ty) -> Result { + match ty.ty_type.ok_or(ClientError::MissingExpectedData)? { + proto::types::ty::TyType::Primitive(primitive) => { + Ok(Ty::Primitive(primitive.try_into()?)) + } + proto::types::ty::TyType::Struct(r#struct) => Ok(Ty::Struct(r#struct.try_into()?)), + proto::types::ty::TyType::Enum(r#enum) => Ok(Ty::Enum(r#enum.into())), + } + } +} From 1388c14e3ce3f7833d24c4fe6c5897faa9e07998 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Fri, 8 Dec 2023 22:09:13 +0100 Subject: [PATCH 105/192] dojo-lang: add plugin test for contract with components (#1246) --- crates/dojo-lang/src/contract.rs | 4 +- crates/dojo-lang/src/plugin_test_data/system | 180 ++++++++++++++++++- 2 files changed, 180 insertions(+), 4 deletions(-) diff --git a/crates/dojo-lang/src/contract.rs b/crates/dojo-lang/src/contract.rs index bdee671e77..c8afea1e3f 100644 --- a/crates/dojo-lang/src/contract.rs +++ b/crates/dojo-lang/src/contract.rs @@ -125,7 +125,7 @@ impl DojoContract { let elements = enum_ast.variants(db).elements(db); let variants = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); - let variants = variants.join(", "); + let variants = variants.join(",\n"); rewrite_nodes.push(RewriteNode::interpolate_patched( " @@ -164,7 +164,7 @@ impl DojoContract { let elements = struct_ast.members(db).elements(db); let members = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); - let members = members.join(", "); + let members = members.join(",\n"); rewrite_nodes.push(RewriteNode::interpolate_patched( " diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index 62d81aabdf..13d26fa0c8 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -33,7 +33,6 @@ mod ctxnamed { #[dojo::contract] mod withevent { - #[event] #[derive(Drop, starknet::Event)] enum Event { @@ -51,6 +50,41 @@ mod withevent { } } +#[starknet::component] +mod testcomponent1 { + #[storage] + struct Storage {} +} + +#[starknet::component] +mod testcomponent2 { + #[storage] + struct Storage {} +} + +#[dojo::contract] +mod withcomponent { + + component!(path: testcomponent1, storage: testcomponent1_storage, event: testcomponent1_event); + component!(path: testcomponent2, storage: testcomponent2_storage, event: testcomponent2_event); + + #[storage] + struct Storage { + #[substorage(v0)] + testcomponent1_storage: testcomponent1::Storage, + #[substorage(v0)] + testcomponent2_storage: testcomponent2::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + testcomponent1_event: testcomponent1::Event, + testcomponent2_event: testcomponent2::Event, + } +} + //! > generated_cairo_code #[starknet::contract] mod spawn { @@ -161,6 +195,16 @@ mod ctxnamed { } //! > expected_diagnostics +error: Unsupported attribute. + --> test_src/lib.cairo:47:1 +#[starknet::component] +^********************^ + +error: Unsupported attribute. + --> test_src/lib.cairo:53:1 +#[starknet::component] +^********************^ + error: Unsupported attribute. --> test_src/lib.cairo[spawn]:2:17 #[starknet::contract] @@ -181,6 +225,21 @@ error: Unsupported attribute. #[starknet::contract] ^*******************^ +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:2:17 + #[starknet::contract] + ^*******************^ + +error: Unsupported attribute. + --> test_src/lib.cairo:49:5 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo:55:5 + #[storage] + ^********^ + error: Unknown inline item macro: 'component'. --> test_src/lib.cairo[spawn]:9:21 component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); @@ -326,8 +385,80 @@ error: Unsupported attribute. #[substorage(v0)] ^***************^ +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[withcomponent]:9:21 + component!(path: dojo::components::upgradeable::upgradeable, storage: upgradeable, event: UpgradeableEvent); + ^**********************************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:11:21 + #[external(v0)] + ^*************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:16:21 + #[external(v0)] + ^*************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:23:21 + #[abi(embed_v0)] + ^**************^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[withcomponent]:27:5 + component!(path: testcomponent1, storage: testcomponent1_storage, event: testcomponent1_event); + ^*********************************************************************************************^ + +error: Unknown inline item macro: 'component'. + --> test_src/lib.cairo[withcomponent]:28:5 + component!(path: testcomponent2, storage: testcomponent2_storage, event: testcomponent2_event); + ^*********************************************************************************************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:30:13 + #[storage] + ^********^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:33:17 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:35:25 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:37:9 + #[substorage(v0)] + ^***************^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:41:13 + #[event] + ^******^ + +error: Unsupported attribute. + --> test_src/lib.cairo[withcomponent]:45:25 + #[flat] + ^*****^ + //! > expanded_cairo_code -#[starknet::contract] +#[starknet::component] +mod testcomponent1 { + #[storage] + struct Storage {} +} + +#[starknet::component] +mod testcomponent2 { + #[storage] + struct Storage {} +} + + #[starknet::contract] mod spawn { use dojo::world; use dojo::world::IWorldDispatcher; @@ -509,3 +640,48 @@ impl EventDrop of Drop::; impl TestEventDrop of Drop::; } + + #[starknet::contract] + mod withcomponent { + use dojo::world; + use dojo::world::IWorldDispatcher; + use dojo::world::IWorldDispatcherTrait; + use dojo::world::IWorldProvider; + + #[external(v0)] + fn dojo_resource(self: @ContractState) -> felt252 { + 'withcomponent' + } + + #[external(v0)] + impl WorldProviderImpl of IWorldProvider { + fn world(self: @ContractState) -> IWorldDispatcher { + self.world_dispatcher.read() + } + } + + #[abi(embed_v0)] + impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; + + #[storage] + struct Storage { + world_dispatcher: IWorldDispatcher, + #[substorage(v0)] + upgradeable: dojo::components::upgradeable::upgradeable::Storage, + #[substorage(v0)] + testcomponent1_storage: testcomponent1::Storage, + #[substorage(v0)] + testcomponent2_storage: testcomponent2::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, + #[flat] + testcomponent1_event: testcomponent1::Event, + testcomponent2_event: testcomponent2::Event + } +impl EventDrop of Drop::; + + } From b217aac442513c4b8a0dfe335615854ebd813af5 Mon Sep 17 00:00:00 2001 From: GianMarco Date: Fri, 8 Dec 2023 16:39:51 -0500 Subject: [PATCH 106/192] Pad query values (#1238) * Pad filter input * add missing case * rollback records contract * Feat: Parse value to hex * fmt --- .../graphql/src/object/inputs/where_input.rs | 17 +++++++++++++---- crates/torii/graphql/src/tests/models_test.rs | 10 +++++----- .../graphql/src/tests/types-test/Scarb.lock | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/torii/graphql/src/object/inputs/where_input.rs b/crates/torii/graphql/src/object/inputs/where_input.rs index b7acfe7e6d..92821a0aac 100644 --- a/crates/torii/graphql/src/object/inputs/where_input.rs +++ b/crates/torii/graphql/src/object/inputs/where_input.rs @@ -109,8 +109,17 @@ fn parse_integer( } fn parse_string(input: ValueAccessor<'_>, type_name: &str) -> Result { - input - .string() - .map(|i| FilterValue::String(i.to_string())) - .map_err(|_| GqlError::new(format!("Expected string on field {}", type_name))) + match input.string() { + Ok(i) => match i.starts_with("0x") { + true => Ok(FilterValue::String(format!("0x{:0>64}", i.strip_prefix("0x").unwrap()))), /* safe to unwrap since we know it starts with 0x */ + false => match i.parse::() { + Ok(val) => Ok(FilterValue::String(format!("0x{:0>64x}", val))), + Err(_) => Err(GqlError::new(format!( + "Failed to parse integer value on field {}", + type_name + ))), + }, + }, + Err(_) => Err(GqlError::new(format!("Expected string on field {}", type_name))), + } } diff --git a/crates/torii/graphql/src/tests/models_test.rs b/crates/torii/graphql/src/tests/models_test.rs index 10d4ef6bbe..d28df29a2d 100644 --- a/crates/torii/graphql/src/tests/models_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -123,16 +123,16 @@ mod tests { let first_record = connection.edges.first().unwrap(); assert_eq!(first_record.node.type_u64, 3); - // NOTE: output leading zeros on hex strings are trimmed, however, we don't do this yet on - // input hex strings - let felt_str_0x5 = format!("0x{:064x}", 5); + // NOTE: Server side is gonna parse "0x5" and "5" to hexadecimal format + let felt_str_0x5 = "0x5"; + let felt_int_5 = "5"; // where filter EQ on class_hash and contract_address let records = records_model_query( &schema, &format!( "(where: {{ type_class_hash: \"{}\", type_contract_address: \"{}\" }})", - felt_str_0x5, felt_str_0x5 + felt_str_0x5, felt_int_5 ), ) .await; @@ -156,7 +156,7 @@ mod tests { // where filter LT on u256 (string) let records = records_model_query( &schema, - &format!("(where: {{ type_u256LT: \"{}\" }})", felt_str_0x5), + &format!("(where: {{ type_u256LT: \"{}\" }})", felt_int_5), ) .await; let connection: Connection = serde_json::from_value(records).unwrap(); diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index 048d2746c4..6a1758f1d7 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.3.13" +version = "0.3.15" dependencies = [ "dojo_plugin", ] From a0145cd368e465b1067404460850fce05065ade6 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Sat, 9 Dec 2023 00:30:10 +0100 Subject: [PATCH 107/192] display class_hash in hex, update messaging doc link (#1248) --- crates/katana/src/args.rs | 2 +- crates/katana/src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index 0bbdce35e7..070f5b6c56 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -63,7 +63,7 @@ pub struct KatanaArgs { #[arg(help = "Configure the messaging with an other chain.")] #[arg(long_help = "Configure the messaging to allow Katana listening/sending messages on a \ settlement chain that can be Ethereum or an other Starknet sequencer. \ - The configuration file details and examples can be found here: TODO.")] + The configuration file details and examples can be found here: https://book.dojoengine.org/toolchain/katana/reference.html#messaging")] pub messaging: Option, #[command(flatten)] diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index eabb82ff6d..45da67f74b 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -60,7 +60,7 @@ async fn main() -> Result<(), Box> { "🚀 JSON-RPC server started: {}", Style::new().red().apply_to(format!("http://{addr}")) ), - format!("{}", account_class_hash), + format!("{:#064x}", account_class_hash), ); } } @@ -102,11 +102,11 @@ PREDEPLOYED CONTRACTS | Contract | Fee Token | Address | {} -| Class Hash | {} +| Class Hash | {:#064x} | Contract | Universal Deployer | Address | {} -| Class Hash | {} +| Class Hash | {:#064x} | Contract | Account Contract | Class Hash | {} From fff037f800f111b1f5829f8502dcd95898f584d3 Mon Sep 17 00:00:00 2001 From: glihm Date: Sat, 9 Dec 2023 11:51:24 -0600 Subject: [PATCH 108/192] feat: blockifier update and bump cairo `2.4.0` (#1247) * wip: compile with latest blockifier * fix: fmt * chore: use blockifier from git * fix: fix merge typo * chore: specify v0.4.0-rc9.2 tag for blockifier * fix: add PluginSuite support * fix(sozo): add support for PluginSuite * wip(language-server): bump version to 2.4.0 * chore(language-server): bump tower_lsp * chore: comment patch on scarb * katana(core): fix tests * fix: adjust tests with cairo-lang plugin * fix(dojo-lang): adjust class hash in manifest tests * wip: check on cairo-lang dependencies * chore: fix scarb version in dojo-world * fix(dojo-lang): fix test plugin expand print * fix(sozo): fit to scarb workspace plugin suite * example: bump spawn-and-move to 2.4.0 * fix(dojo-core): update version to 2.4.0 * fix: fmt * fix(dojo-lang/sozo): fix compiler with dojo plugin and relocate scarb internal * fix(dojo-lang): run with CAIRO_FIX_TESTS * fix: clippy fmt * fix(dojo-world): replace class hash of position contract * fix(katana-rpc): ignore cairo 0 declare test for now * fix(torii): fix test hardcoded address * fix(dojo-language-server): typo * fix test * update * Revert plugin version change --------- Co-authored-by: Kariy Co-authored-by: Tarrence van As --- Cargo.lock | 1010 +++++++++++------ Cargo.toml | 56 +- crates/dojo-core/Scarb.toml | 4 +- crates/dojo-lang/Cargo.toml | 1 + crates/dojo-lang/README.md | 6 +- crates/dojo-lang/Scarb.toml | 2 +- crates/dojo-lang/src/compiler_test.rs | 19 +- crates/dojo-lang/src/inline_macros/emit.rs | 14 +- crates/dojo-lang/src/inline_macros/get.rs | 12 +- crates/dojo-lang/src/inline_macros/set.rs | 18 +- crates/dojo-lang/src/lib.rs | 4 + .../dojo-lang/src/manifest_test_data/manifest | 87 +- crates/dojo-lang/src/plugin.rs | 31 +- crates/dojo-lang/src/plugin_test.rs | 9 +- .../dojo-lang/src/plugin_test_data/introspect | 144 +-- crates/dojo-lang/src/plugin_test_data/model | 98 +- crates/dojo-lang/src/plugin_test_data/print | 60 +- crates/dojo-lang/src/plugin_test_data/system | 12 +- crates/dojo-lang/src/scarb_internal/mod.rs | 100 ++ crates/dojo-lang/src/semantics/test_data/get | 10 - crates/dojo-lang/src/semantics/test_data/set | 18 +- crates/dojo-lang/src/semantics/test_utils.rs | 32 +- crates/dojo-language-server/Cargo.toml | 4 +- .../src/bin/language_server.rs | 22 +- crates/dojo-test-utils/build.rs | 10 +- crates/dojo-world/Cargo.toml | 2 +- crates/dojo-world/src/contracts/model_test.rs | 2 +- crates/katana/core/src/backend/config.rs | 16 +- crates/katana/core/src/backend/mod.rs | 2 +- crates/katana/core/src/backend/storage.rs | 21 +- crates/katana/core/src/env.rs | 12 +- crates/katana/core/src/sequencer.rs | 13 +- crates/katana/executor/src/blockifier/mod.rs | 7 +- .../katana/executor/src/blockifier/state.rs | 9 +- .../executor/src/blockifier/transactions.rs | 38 +- .../katana/executor/src/blockifier/utils.rs | 51 +- crates/katana/rpc/src/starknet.rs | 4 +- crates/katana/src/args.rs | 4 +- crates/katana/storage/db/Cargo.toml | 2 +- crates/sozo/Cargo.toml | 1 + crates/sozo/src/commands/build.rs | 10 +- crates/sozo/src/commands/dev.rs | 6 +- crates/sozo/src/commands/migrate.rs | 7 +- crates/sozo/src/commands/mod.rs | 3 - .../sozo/src/commands/scarb_internal/mod.rs | 55 - crates/sozo/src/commands/test.rs | 28 +- crates/sozo/src/ops/migration/mod.rs | 14 +- crates/torii/graphql/src/tests/mod.rs | 2 +- .../graphql/src/tests/types-test/Scarb.lock | 3 +- examples/spawn-and-move/.tool-versions | 1 + examples/spawn-and-move/Scarb.toml | 2 +- 51 files changed, 1212 insertions(+), 886 deletions(-) create mode 100644 crates/dojo-lang/src/scarb_internal/mod.rs delete mode 100644 crates/sozo/src/commands/scarb_internal/mod.rs create mode 100644 examples/spawn-and-move/.tool-versions diff --git a/Cargo.lock b/Cargo.lock index e0c8c5a881..318961f903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -129,30 +129,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -350,8 +350,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +dependencies = [ + "concurrent-queue", + "event-listener 4.0.0", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] @@ -372,30 +385,30 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock", + "async-lock 3.2.0", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite", + "fastrand 2.0.1", + "futures-lite 2.1.0", "slab", ] [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526" dependencies = [ - "async-channel", + "async-channel 2.1.1", "async-executor", - "async-io", - "async-lock", + "async-io 2.2.1", + "async-lock 3.2.0", "blocking", - "futures-lite", + "futures-lite 2.1.0", "once_cell", ] @@ -492,27 +505,57 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", - "polling", + "polling 2.8.0", "rustix 0.37.27", "slab", "socket2 0.4.10", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +dependencies = [ + "async-lock 3.2.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.1.0", + "parking", + "polling 3.3.1", + "rustix 0.38.27", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +dependencies = [ + "event-listener 4.0.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -532,15 +575,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-global-executor", - "async-io", - "async-lock", + "async-io 1.13.0", + "async-lock 2.8.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite", + "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", "log", @@ -576,9 +619,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" @@ -613,9 +656,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic-write-file" @@ -877,19 +920,23 @@ dependencies = [ [[package]] name = "blockifier" -version = "0.1.0-rc0" -source = "git+https://github.com/dojoengine/blockifier?rev=3357e69#3357e69d701e02d90766302b7d2315060ef0dbef" +version = "0.4.0-rc9.2" +source = "git+https://github.com/dojoengine/blockifier?rev=e6e9c90#e6e9c902ce699269e38aecda49dc50bd11a9bec0" dependencies = [ + "ark-ec", "ark-ff", "ark-secp256k1", + "ark-secp256r1", + "cached", "cairo-felt", "cairo-lang-casm", "cairo-lang-runner", "cairo-lang-starknet", + "cairo-lang-utils", "cairo-vm", "ctor", "derive_more", - "indexmap 1.9.3", + "indexmap 2.1.0", "itertools 0.10.5", "keccak", "log", @@ -909,17 +956,18 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel", - "async-lock", + "async-channel 2.1.1", + "async-lock 3.2.0", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite", - "log", + "fastrand 2.0.1", + "futures-io", + "futures-lite 2.1.0", + "piper", + "tracing", ] [[package]] @@ -1006,12 +1054,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" -[[package]] -name = "byteyarn" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7534301c0ea17abb4db06d75efc7b4b0fa360fce8e175a4330d721c71c942ff" - [[package]] name = "bzip2" version = "0.4.4" @@ -1033,6 +1075,42 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cached" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b195e4fbc4b6862bbd065b991a34750399c119797efff72492f28a5864de8700" +dependencies = [ + "async-trait", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.13.2", + "instant", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48814962d2fd604c50d2b9433c2a41a0ab567779ee2c02f7fba6eca1221f082" +dependencies = [ + "cached_proc_macro_types", + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" + [[package]] name = "cairo-felt" version = "0.8.2" @@ -1047,9 +1125,9 @@ dependencies = [ [[package]] name = "cairo-lang-casm" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cca7891c0df31a87740acbcda3f3c04e6516e283b67842386873f3a181fd91" +checksum = "2850dc1d46d5bfb64c5ed0bc7ccd4821e4d4c36a8f2678a897df7c2bfaefe6fc" dependencies = [ "cairo-lang-utils", "indoc 2.0.4", @@ -1064,9 +1142,9 @@ dependencies = [ [[package]] name = "cairo-lang-compiler" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c4bd031bf62046af88e75b86f419ad7e2317c3b7ee26cbad367f2ff2f2bfa4" +checksum = "2e6360b6735eeff503c6103520fef7410ca2c5a5ae90584822baa326607721ac" dependencies = [ "anyhow", "cairo-lang-defs", @@ -1074,7 +1152,6 @@ dependencies = [ "cairo-lang-filesystem", "cairo-lang-lowering", "cairo-lang-parser", - "cairo-lang-plugins", "cairo-lang-project", "cairo-lang-semantic", "cairo-lang-sierra", @@ -1088,18 +1165,18 @@ dependencies = [ [[package]] name = "cairo-lang-debug" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954529b40c914ff089bd06b4cdfa3b51f39fb8769a6f9af92ba745e4a1300bd4" +checksum = "0c190deb7ba826a462fa7339e482d5e2df78d329435f4988b15f7752e033b5ac" dependencies = [ "cairo-lang-utils", ] [[package]] name = "cairo-lang-defs" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2ab80b21943392da07b2ee54f1f7e15ac783ea1567ed27bd4682774713f7ee" +checksum = "b42a34d9952b04fa0c96fafd08d170097fb5075ff81826a034ef9faa70556de8" dependencies = [ "cairo-lang-debug", "cairo-lang-diagnostics", @@ -1114,9 +1191,9 @@ dependencies = [ [[package]] name = "cairo-lang-diagnostics" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07052c58dc014904bfecc6fb253a0461bbdcdd3ac41f1385ac9fba5ef9a0da61" +checksum = "c399832f9fc462cd51687a415c391ead4b99ee48c54cad5c8e1d5004ff6520c7" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -1126,9 +1203,9 @@ dependencies = [ [[package]] name = "cairo-lang-eq-solver" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac351e6a4af689df90119d95d8fa9441b8ad1b2eef6f4868ed7a1c1808f786c" +checksum = "d73846e0dec2a204bc429f7421020fc6a98ae48f20f0cfa2aa1091b78221d6ce" dependencies = [ "cairo-lang-utils", "good_lp", @@ -1136,9 +1213,9 @@ dependencies = [ [[package]] name = "cairo-lang-filesystem" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f253875f0503f13d2a15e303db4f77a932a84600787a496938d0daf687945d" +checksum = "64f15f4a10963dcd5baa0386632c5ce4136d54f93d6c71cc16a49cbcbf774ee2" dependencies = [ "cairo-lang-debug", "cairo-lang-utils", @@ -1150,9 +1227,9 @@ dependencies = [ [[package]] name = "cairo-lang-formatter" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f7455bb3e5c462929211b34241138ab3322e752168bc8b41f66abd1acb61d7" +checksum = "19ecbe94310d9b52ff654be27099c00aafb22c54857c32bc48754567114a4e09" dependencies = [ "anyhow", "cairo-lang-diagnostics", @@ -1170,9 +1247,9 @@ dependencies = [ [[package]] name = "cairo-lang-language-server" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62679e47cb4d7e815c1c418c26a2805c66497f7f7ddedbe0809c6d01a3643a13" +checksum = "d7d917691ac8efabf0ed76e6b7d160f2163b89ab55c8f6477a9b84d63d7fe866" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -1191,7 +1268,7 @@ dependencies = [ "log", "lsp-types", "salsa", - "scarb-metadata 1.9.0", + "scarb-metadata 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", "tokio", @@ -1200,9 +1277,9 @@ dependencies = [ [[package]] name = "cairo-lang-lowering" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa602a50c7d216beb4c261036b024b24f90ce6724d623f1b23f56076584473c" +checksum = "5547bb3e13841a840b4faad3eb7fe7c39b525220f708973b71b1b9077747758b" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", @@ -1221,13 +1298,14 @@ dependencies = [ "num-traits 0.2.17", "once_cell", "salsa", + "smol_str", ] [[package]] name = "cairo-lang-parser" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25e847ef219635b837cbfd8eed797a7aa6a4b01e1775065cff67b1d5bfda1fe" +checksum = "197445f8db467e28dbeddc573047dd8f2a0ef3fcc3d1c32575162d4cf79988df" dependencies = [ "cairo-lang-diagnostics", "cairo-lang-filesystem", @@ -1245,9 +1323,9 @@ dependencies = [ [[package]] name = "cairo-lang-plugins" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c109f0b788a95bb86cff0e3917e1ce7d75210020fc53904d2a5e3ba54728adb" +checksum = "9c3ea747577bd93e4791bdd57744dfddbc4b99ce056fffb5fd41340759642f91" dependencies = [ "cairo-lang-defs", "cairo-lang-diagnostics", @@ -1264,9 +1342,9 @@ dependencies = [ [[package]] name = "cairo-lang-proc-macros" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bbbfe1934e11fe3cce4f23cdccd22341ed63af5d76e593234288dd4ba06f56" +checksum = "c8cc59c40344194d2cc825071080d887826dcf0df37de71e58fc8aa4c344bb84" dependencies = [ "cairo-lang-debug", "quote", @@ -1275,23 +1353,23 @@ dependencies = [ [[package]] name = "cairo-lang-project" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba814a9dd17b1341204d8e7bb67775aadebc5138a475bdf176dff0f11999cb" +checksum = "312b65dec1d0b8e1b420d7b464c0c771f18301177376432681c05c30f5ef9604" dependencies = [ "cairo-lang-filesystem", "cairo-lang-utils", "serde", "smol_str", "thiserror", - "toml 0.7.8", + "toml 0.8.8", ] [[package]] name = "cairo-lang-runner" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c759ff583b3771e07338433ebc328a9c5a7c845e09e2875d24c0701a252709c9" +checksum = "a2376aa33117e2feae26ca030e8b6b5ec7c6c1edfc599885d1157d92f3fc413a" dependencies = [ "ark-ff", "ark-secp256k1", @@ -1301,7 +1379,6 @@ dependencies = [ "cairo-lang-casm", "cairo-lang-sierra", "cairo-lang-sierra-ap-change", - "cairo-lang-sierra-gas", "cairo-lang-sierra-to-casm", "cairo-lang-sierra-type-size", "cairo-lang-starknet", @@ -1317,9 +1394,9 @@ dependencies = [ [[package]] name = "cairo-lang-semantic" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19678648e0efec3f837c0d75b6071bc2afe5c4bc611e177381478c023b72a74c" +checksum = "7e18c57cd10bcf69b427b901ce058268d21f65f5199b33e36b72b02ba7ceff74" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", @@ -1332,6 +1409,7 @@ dependencies = [ "cairo-lang-test-utils", "cairo-lang-utils", "id-arena", + "indoc 2.0.4", "itertools 0.11.0", "num-bigint", "num-traits 0.2.17", @@ -1342,9 +1420,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b7d09f0b7461701a9ba5d7a2260551b5026cd8f6efc6ca9ca270f6c0a6fd23" +checksum = "84cf029a71e0176992cc401f7f182dc92e14a51662b1576240a7ecc79efac6bc" dependencies = [ "anyhow", "cairo-lang-utils", @@ -1359,6 +1437,7 @@ dependencies = [ "regex", "salsa", "serde", + "serde_json", "sha3", "smol_str", "thiserror", @@ -1366,9 +1445,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-ap-change" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e66740bcfadb365d488ff9c334f68cb4cb6a6cb9666ae12109fc6eee7371116" +checksum = "529ed2d8d14ef4c2d77e45db597425488e194b8ab1d3210742a1c54d78743407" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -1380,9 +1459,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-gas" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac27c07af11fcdc9546a9c55c1463cb871fb5b7af1daa3cdf31cfb0872da3d88" +checksum = "d0cbe3dd4f663d7df902a2f10cf52990d62f178741fe1494de51f08bb89b7aa6" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -1394,9 +1473,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-generator" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456cd75547a127b8f4088216a419d317c753c6b9188e944846bf3a5193c14797" +checksum = "6c5bed52b240e1546b08e075493b2df4030dba2199e019d36f52da1423f2c653" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", @@ -1408,6 +1487,7 @@ dependencies = [ "cairo-lang-sierra", "cairo-lang-syntax", "cairo-lang-utils", + "indexmap 2.1.0", "itertools 0.11.0", "num-bigint", "once_cell", @@ -1417,9 +1497,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-to-casm" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da74c7c4a2df66b961a982396e0f5221d6594266aed48c76d8c22d5b0d96af5d" +checksum = "fc16661a7a78f885f6b5a4fdb3c7463d9ee3f6bca83266b4f2b956e65579ec72" dependencies = [ "assert_matches", "cairo-felt", @@ -1438,9 +1518,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-type-size" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7fdf2dbda71f1ed4e4020914e7494ad32db84dbc75cc8dbf05c06caef678fc8" +checksum = "9f3fa025f6bc1c8d4556c9fc4609fb6f27071470ed47eb3bd0b5f9a159e51124" dependencies = [ "cairo-lang-sierra", "cairo-lang-utils", @@ -1448,9 +1528,9 @@ dependencies = [ [[package]] name = "cairo-lang-starknet" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9217e979f11980609d13d3a5adea8438ec9345709ddfebca975cc9cd1f85201e" +checksum = "ac5523d9c5b8e7c98afb2907c2cf4821a251d94fc42d37940063a9f2adbea05f" dependencies = [ "anyhow", "cairo-felt", @@ -1479,14 +1559,15 @@ dependencies = [ "serde_json", "sha3", "smol_str", + "starknet-crypto 0.6.1", "thiserror", ] [[package]] name = "cairo-lang-syntax" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d461d88e09ba7055822eb42d6b2c2ea38a4eaa5b9e4196d8f63db48c563fb56" +checksum = "8c8e9b19fa724135353470ee3452605f82edfec17a7dd4e8388d77152ea4fbd2" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -1500,9 +1581,9 @@ dependencies = [ [[package]] name = "cairo-lang-syntax-codegen" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615745282c0c0d3a255c2ac2665a18ae1d163c54285014d85dacda2d5e53637" +checksum = "7a50c3a5dc5d890a523122e40dac59f3a430952cec73fe7312dd266ad865f049" dependencies = [ "genco", "xshell", @@ -1510,9 +1591,9 @@ dependencies = [ [[package]] name = "cairo-lang-test-plugin" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9d1225b798c61e15eaeeda9e050f818e0e5c8b15420991da5e1b8144f2093c" +checksum = "ed848115b9422014027b4c352ed6589a886e767efb4a11667de32a45be8edd52" dependencies = [ "anyhow", "cairo-felt", @@ -1528,16 +1609,18 @@ dependencies = [ "cairo-lang-starknet", "cairo-lang-syntax", "cairo-lang-utils", + "indoc 2.0.4", "itertools 0.11.0", + "num-bigint", "num-traits 0.2.17", "serde", ] [[package]] name = "cairo-lang-test-runner" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4775d0ededb8c5da1f3cc6eb4e4a8dbc77c9fde47c50acb88b1a75f9d913d6b" +checksum = "fb0cc6ebdae7e2d1aaf5a032e41b42a77090aa3c1568a4339c9f0af449eb0d07" dependencies = [ "anyhow", "cairo-felt", @@ -1557,9 +1640,9 @@ dependencies = [ [[package]] name = "cairo-lang-test-utils" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4df56b6054039aa585b53af87c350d3498016329ff0ddc94fecf5131a79191" +checksum = "41d8dfdf2e7e7db2d8eea86046870d00fbb40269c1c812ee4683580d07bfafe2" dependencies = [ "cairo-lang-utils", "colored", @@ -1569,9 +1652,9 @@ dependencies = [ [[package]] name = "cairo-lang-utils" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edcd2fba78af9b753614885464c9b5bf6041b7decba46587c2b0babc4197ac" +checksum = "88969fe46417affe9628bd039865693431837807eb981115f02756a35f488489" dependencies = [ "env_logger", "indexmap 2.1.0", @@ -1709,9 +1792,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", @@ -1729,9 +1812,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", @@ -1859,9 +1942,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] @@ -2006,7 +2089,7 @@ dependencies = [ [[package]] name = "create-output-dir" version = "1.0.0" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +source = "git+https://github.com/software-mansion/scarb?tag=v2.4.0#cba988e685f2f9b07a8ea0b5f056009f91c6c9ed" dependencies = [ "anyhow", "core-foundation", @@ -2250,9 +2333,9 @@ dependencies = [ [[package]] name = "deno_task_shell" -version = "0.13.2" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbbad0a7ba06a961df3cd638ab117f5d67787607f627defa65629a4ef29d576" +checksum = "cc333d47d4ec12897c2efd25031c02191ec115b4099470daeee10f8300035e0d" dependencies = [ "anyhow", "futures", @@ -2478,6 +2561,7 @@ dependencies = [ "cairo-lang-sierra-generator", "cairo-lang-starknet", "cairo-lang-syntax", + "cairo-lang-test-plugin", "cairo-lang-test-utils", "cairo-lang-utils", "camino", @@ -2507,7 +2591,7 @@ dependencies = [ ] [[package]] -name = "dojo-languge-server" +name = "dojo-language-server" version = "0.4.0-rc0" dependencies = [ "anyhow", @@ -3090,11 +3174,32 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.0", + "pin-project-lite", +] + [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "8bbb8258be8305fb0237d7b295f47bb24ff1b136a535f473baf40e70468515aa" dependencies = [ "indenter", "once_cell", @@ -3145,14 +3250,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", ] [[package]] @@ -3206,6 +3311,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -3231,10 +3351,18 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.26", + "async-trait", + "rustix 0.38.27", + "tokio", "windows-sys 0.48.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -3324,6 +3452,19 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-locks" version = "0.7.1" @@ -3448,13 +3589,14 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix" -version = "0.54.1" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6d32e74454459690d57d18ea4ebec1629936e6b130b51d12cb4a81630ac953" +checksum = "5b0dcdc9c60d66535897fa40a7ea2a635e72f99456b1d9ae86b7e170e80618cb" dependencies = [ "gix-actor", "gix-archive", "gix-attributes", + "gix-command", "gix-commitgraph", "gix-config", "gix-credentials", @@ -3506,9 +3648,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c60e982c5290897122d4e2622447f014a2dadd5a18cb73d50bb91b31645e27" +checksum = "2eadca029ef716b4378f7afb19f7ee101fde9e58ba1f1445971315ac866db417" dependencies = [ "bstr", "btoi", @@ -3520,9 +3662,9 @@ dependencies = [ [[package]] name = "gix-archive" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab71635f7fb38ad4cc7da7e98af79e3010e35b05de5cb6eb79b2da68ab93eac7" +checksum = "c9d1095b8cbc9369f1cd59dc73f7e37fab2252294265705da8bc80e42a2ecc38" dependencies = [ "bstr", "gix-date", @@ -3533,16 +3675,16 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2451665e70709ba4753b623ef97511ee98c4a73816b2c5b5df25678d607ed820" +checksum = "0f395469d38c76ec47cd1a6c5a53fbc3f13f737b96eaf7535f4e6b367e643381" dependencies = [ "bstr", - "byteyarn", "gix-glob", "gix-path", "gix-quote", "gix-trace", + "kstring", "smallvec", "thiserror", "unicode-bom", @@ -3550,36 +3692,39 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ccab4bc576844ddb51b78d81b4a42d73e6229660fa614dfc3d3999c874d1959" +checksum = "d49e1a13a30d3f88be4bceae184dd13a2d3fb9ffa7515f7ed7ae771b857f4916" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b42ea64420f7994000130328f3c7a2038f639120518870436d31b8bde704493" +checksum = "d411ecd9b558b0c20b3252b7e409eec48eabc41d18324954fe526bac6e2db55f" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.2.10" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c576cfbf577f72c097b5f88aedea502cd62952bdc1fb3adcab4531d5525a4c7" +checksum = "b3b54c1d8d63e6ef2adbd9b94d6e49ff168858510b44d3811cdd02dfacc4f0c9" dependencies = [ "bstr", + "gix-path", + "gix-trace", + "shell-words", ] [[package]] name = "gix-commitgraph" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75a975ee22cf0a002bfe9b5d5cb3d2a88e263a8a178cd7509133cff10f4df8a" +checksum = "85a7007ba021f059803afaf6f8a48872422abc20550ac12ede6ddea2936cec36" dependencies = [ "bstr", "gix-chunk", @@ -3591,9 +3736,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.30.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c171514b40487d3f677ae37efc0f45ac980e3169f23c27eb30a70b47fdf88ab5" +checksum = "0341471d55d8676e98b88e121d7065dfa4c9c5acea4b6d6ecdd2846e85cce0c3" dependencies = [ "bstr", "gix-config-value", @@ -3612,9 +3757,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47" +checksum = "6419db582ea84dfb58c7e7b0af7fd62c808aa14954af2936a33f89b0f4ed018e" dependencies = [ "bitflags 2.4.1", "bstr", @@ -3625,9 +3770,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46900b884cc5af6a6c141ee741607c0c651a4e1d33614b8d888a1ba81cc0bc8a" +checksum = "513dac42450b27946bd0a0535a3a5a88e473d6522e5e3439a129cab779c88f3d" dependencies = [ "bstr", "gix-command", @@ -3635,15 +3780,16 @@ dependencies = [ "gix-path", "gix-prompt", "gix-sec", + "gix-trace", "gix-url", "thiserror", ] [[package]] name = "gix-date" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7df669639582dc7c02737642f76890b03b5544e141caba68a7d6b4eb551e0d" +checksum = "468dfbe411f335f01525a1352271727f8e7772075a93fa747260f502086b30be" dependencies = [ "bstr", "itoa", @@ -3653,21 +3799,29 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.36.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788ddb152c388206e81f36bcbb574e7ed7827c27d8fa62227b34edc333d8928c" +checksum = "8119a985887cfe68f4bdf92e51bd64bc758a73882d82fcfc03ebcb164441c85d" dependencies = [ + "bstr", + "gix-command", + "gix-filter", + "gix-fs", "gix-hash", "gix-object", + "gix-path", + "gix-tempfile", + "gix-trace", + "gix-worktree", "imara-diff", "thiserror", ] [[package]] name = "gix-discover" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69507643d75a0ea9a402fcf73ced517d2b95cc95385904ac09d03e0b952fde33" +checksum = "6fad89416ebe0b3b7df78464124e2a02417b6cd3743d48ad93df86f4d2929c07" dependencies = [ "bstr", "dunce", @@ -3680,9 +3834,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9ff423ae4983f762659040d13dd7a5defbd54b6a04ac3cc7347741cec828cd" +checksum = "4d46a4a5c6bb5bebec9c0d18b65ada20e6517dbd7cf855b87dd4bbdce3a771b2" dependencies = [ "bytes", "bytesize", @@ -3703,9 +3857,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be40d28cd41445bb6cd52c4d847d915900e5466f7433eaee6a9e0a3d1d88b08" +checksum = "6d6a5c9d8e55c364e7c226919c19c9a28be1392d6208b5008059fa94ff7e2bf0" dependencies = [ "bstr", "encoding_rs", @@ -3717,24 +3871,25 @@ dependencies = [ "gix-path", "gix-quote", "gix-trace", + "gix-utils", "smallvec", "thiserror", ] [[package]] name = "gix-fs" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09815faba62fe9b32d918b75a554686c98e43f7d48c43a80df58eb718e5c6635" +checksum = "20e86eb040f5776a5ade092282e51cdcad398adb77d948b88d17583c2ae4e107" dependencies = [ "gix-features", ] [[package]] name = "gix-glob" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d76e85f11251dcf751d2c5e918a14f562db5be6f727fd24775245653e9b19d" +checksum = "5db19298c5eeea2961e5b3bf190767a2d1f09b8802aeb5f258e42276350aff19" dependencies = [ "bitflags 2.4.1", "bstr", @@ -3744,9 +3899,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60" +checksum = "99c1e554a87759e672c7d2e37211e761aa390c61ffcd3753a57c51173143f3cb" dependencies = [ "faster-hex", "thiserror", @@ -3754,9 +3909,9 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16" +checksum = "feb61880816d7ec4f0b20606b498147d480860ddd9133ba542628df2f548d3ca" dependencies = [ "gix-hash", "hashbrown 0.14.3", @@ -3765,9 +3920,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048f443a1f6b02da4205c34d2e287e3fd45d75e8e2f06cfb216630ea9bff5e3" +checksum = "a215cc8cf21645bca131fcf6329d3ebd46299c47dbbe27df71bb1ca9e328b879" dependencies = [ "bstr", "gix-glob", @@ -3777,9 +3932,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54d63a9d13c13088f41f5a3accbec284e492ac8f4f707fcc307c139622e17b7" +checksum = "65ce8d03ec25de952be7d2a9adce2a4c2cb8f7fc2d4c25be91301be9707f380b" dependencies = [ "bitflags 2.4.1", "bstr", @@ -3793,16 +3948,18 @@ dependencies = [ "gix-object", "gix-traverse", "itoa", + "libc", "memmap2", + "rustix 0.38.27", "smallvec", "thiserror", ] [[package]] name = "gix-lock" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47fc96fa8b6b6d33555021907c81eb3b27635daecf6e630630bdad44f8feaa95" +checksum = "7e5c65e6a29830a435664891ced3f3c1af010f14900226019590ee0971a22f37" dependencies = [ "gix-tempfile", "gix-utils", @@ -3811,9 +3968,9 @@ dependencies = [ [[package]] name = "gix-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" +checksum = "02a5bcaf6704d9354a3071cede7e77d366a5980c7352e102e2c2f9b645b1d3ae" dependencies = [ "proc-macro2", "quote", @@ -3822,9 +3979,9 @@ dependencies = [ [[package]] name = "gix-mailmap" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f00fa96e29e066ef208f5d13e0f5f95fa70c3ae4bd4f0234272ed4d708a7db" +checksum = "e2325fec0d5e6b2dd758083a6f560bfeabc73ff7a12ffe378986baf890fe9f07" dependencies = [ "bstr", "gix-actor", @@ -3834,9 +3991,9 @@ dependencies = [ [[package]] name = "gix-negotiate" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f1697bf9911c6d1b8d709b9e6ef718cb5ea5821a1b7991520125a8134448004" +checksum = "979f6accd9c051b3dd018b50adf29c0a2459edddf6105cc70b767976cd6f8014" dependencies = [ "bitflags 2.4.1", "gix-commitgraph", @@ -3850,9 +4007,9 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7e19616c67967374137bae83e950e9b518a9ea8a605069bd6716ada357fd6f" +checksum = "febf79c5825720c1c63fe974c7bbe695d0cb54aabad73f45671c60ce0e501e33" dependencies = [ "bstr", "btoi", @@ -3869,9 +4026,9 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.53.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6a392c6ba3a2f133cdc63120e9bc7aec81eef763db372c817de31febfe64bf" +checksum = "1fae5f971540c99c6ecc8d4368ecc9d18a9dc8b9391025c68c4399747dc93bac" dependencies = [ "arc-swap", "gix-date", @@ -3888,9 +4045,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.43.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7536203a45b31e1bc5694bbf90ba8da1b736c77040dd6a520db369f371eb1ab3" +checksum = "4569491c92446fddf373456ff360aff9a9effd627b40a70f2d7914dcd75a3205" dependencies = [ "clru", "gix-chunk", @@ -3909,20 +4066,21 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.16.6" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8395f7501c84d6a1fe902035fdfd8cd86d89e2dd6be0200ec1a72fd3c92d39" +checksum = "50052c0f76c5af5acda41177fb55b60c1e484cc246ae919d8d21129cd1000a4e" dependencies = [ "bstr", "faster-hex", + "gix-trace", "thiserror", ] [[package]] name = "gix-path" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1d370115171e3ae03c5c6d4f7d096f2981a40ddccb98dfd704c773530ba73b" +checksum = "d86d6fac2fabe07b67b7835f46d07571f68b11aa1aaecae94fe722ea4ef305e1" dependencies = [ "bstr", "gix-trace", @@ -3933,9 +4091,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e26c9b47c51be73f98d38c84494bd5fb99334c5d6fda14ef5d036d50a9e5fd" +checksum = "1dbbb92f75a38ef043c8bb830b339b38d0698d7f3746968b5fcbade7a880494d" dependencies = [ "bitflags 2.4.1", "bstr", @@ -3948,22 +4106,22 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c9a913769516f5e9d937afac206fb76428e3d7238e538845842887fda584678" +checksum = "4967b921304a5355e65a6257280eddf6e0f9ce3df111256531460adca3771305" dependencies = [ "gix-command", "gix-config-value", "parking_lot 0.12.1", - "rustix 0.38.26", + "rustix 0.38.27", "thiserror", ] [[package]] name = "gix-quote" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475c86a97dd0127ba4465fbb239abac9ea10e68301470c9791a6dd5351cdc905" +checksum = "4f84845efa535468bc79c5a87b9d29219f1da0313c8ecf0365a5daa7e72786f2" dependencies = [ "bstr", "btoi", @@ -3972,9 +4130,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e6b749660b613641769edc1954132eb8071a13c32224891686091bef078de4" +checksum = "1ac23ed741583c792f573c028785db683496a6dfcd672ec701ee54ba6a77e1ff" dependencies = [ "gix-actor", "gix-date", @@ -3993,9 +4151,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0895cb7b1e70f3c3bd4550c329e9f5caf2975f97fcd4238e05754e72208ef61e" +checksum = "76d9d3b82e1ee78fc0dc1c37ea5ea76c2dbc73f407db155f0dfcea285e583bee" dependencies = [ "bstr", "gix-hash", @@ -4007,9 +4165,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8c4b15cf2ab7a35f5bcb3ef146187c8d36df0177e171ca061913cbaaa890e89" +checksum = "fe5dd51710ce5434bc315ea30394fab483c5377276494edd79222b321a5a9544" dependencies = [ "bstr", "gix-date", @@ -4023,9 +4181,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9870c6b1032f2084567710c3b2106ac603377f8d25766b8a6b7c33e6e3ca279" +checksum = "69d4ed2493ca94a475fdf147138e1ef8bab3b6ebb56abf3d9bda1c05372ec1dd" dependencies = [ "gix-commitgraph", "gix-date", @@ -4038,9 +4196,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28" +checksum = "a36ea2c5907d64a9b4b5d3cc9f430e6c30f0509646b5e38eb275ca57c5bf29e2" dependencies = [ "bitflags 2.4.1", "gix-path", @@ -4050,27 +4208,28 @@ dependencies = [ [[package]] name = "gix-status" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082801c42ba707f2407b5cce3d99b89cfa84f3794962946dc94cfdc00ae522fa" +checksum = "255a11459330a9a6ef59a5614b90c5661f04836c0f715cf68f8ca53726843d24" dependencies = [ "bstr", "filetime", "gix-features", + "gix-filter", "gix-fs", "gix-hash", "gix-index", "gix-object", "gix-path", - "gix-pathspec", + "gix-worktree", "thiserror", ] [[package]] name = "gix-submodule" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0150e82e9282d3f2ab2dd57a22f9f6c3447b9d9856e5321ac92d38e3e0e2b7" +checksum = "02a3d7f60a95bdcaeb8981663c99d1c9f4de42aab1169524c949e948989809f9" dependencies = [ "bstr", "gix-config", @@ -4083,10 +4242,11 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae0978f3e11dc57290ee75ac2477c815bca1ce2fa7ed5dc5f16db067410ac4d" +checksum = "388dd29114a86ec69b28d1e26d6d63a662300ecf61ab3f4cc578f7d7dc9e7e23" dependencies = [ + "dashmap", "gix-fs", "libc", "once_cell", @@ -4098,15 +4258,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" +checksum = "b686a35799b53a9825575ca3f06481d0a053a409c4d97ffcf5ddd67a8760b497" [[package]] name = "gix-traverse" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ef04ab3643acba289b5cedd25d6f53c0430770b1d689d1d654511e6fb81ba0" +checksum = "df2112088122a0206592c84fbd42020db63b2ccaed66a0293779f2e5fbf80474" dependencies = [ "gix-commitgraph", "gix-date", @@ -4120,9 +4280,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.24.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125ecf46e8c68bf7202da6cad239831daebf0247ffbab30210d72f3856e420f" +checksum = "0c427a1a11ccfa53a4a2da47d9442c2241deee63a154bc15cc14b8312fbc4005" dependencies = [ "bstr", "gix-features", @@ -4134,18 +4294,18 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" +checksum = "9f82c41937f00e15a1f6cb0b55307f0ca1f77f4407ff2bf440be35aa688c6a3e" dependencies = [ "fastrand 2.0.1", ] [[package]] name = "gix-validate" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05cab2b03a45b866156e052aa38619f4ece4adcb2f79978bfc249bc3b21b8c5" +checksum = "75b7d8e4274be69f284bbc7e6bb2ccf7065dbcdeba22d8c549f2451ae426883f" dependencies = [ "bstr", "thiserror", @@ -4153,9 +4313,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.26.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f5e32972801bd82d56609e6fc84efc358fa1f11f25c5e83b7807ee2280f14fe" +checksum = "7f1d0ae01dee14abe8c8117d78d7518f9a507de2dc4522546fbf4c444e9860b4" dependencies = [ "bstr", "gix-attributes", @@ -4171,9 +4331,9 @@ dependencies = [ [[package]] name = "gix-worktree-state" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aeb06960f2c5ac9e4cdb6b38eb3c2b99d5e525e68285fef21ed17dfbd597ad" +checksum = "afef3c1cfe610ccfbca391515a1254662a648ce2635c9f60511eb1feb751fa01" dependencies = [ "bstr", "gix-features", @@ -4191,9 +4351,9 @@ dependencies = [ [[package]] name = "gix-worktree-stream" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89fe93a12e45cc3ad6ba429a7dd7506b03d7d406374a65ad9998b5cb2627569" +checksum = "3c783c269ed30193a00becb20209aa89fb48dadbaf30c9f66082472da966106c" dependencies = [ "gix-attributes", "gix-features", @@ -4454,9 +4614,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -4565,7 +4725,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.9", + "rustls 0.21.10", "tokio", "tokio-rustls 0.24.1", ] @@ -4582,6 +4742,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -4892,7 +5065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.26", + "rustix 0.38.27", "windows-sys 0.48.0", ] @@ -4914,11 +5087,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" @@ -5267,6 +5449,15 @@ dependencies = [ "libc", ] +[[package]] +name = "kstring" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +dependencies = [ + "static_assertions", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -5472,9 +5663,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] @@ -5540,9 +5731,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", @@ -5635,6 +5826,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -5884,9 +6093,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -5925,12 +6134,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -6000,9 +6247,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -6305,6 +6552,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -6348,11 +6606,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.27", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "portable-atomic" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "powerfmt" @@ -6565,7 +6837,7 @@ dependencies = [ "regex", "syn 1.0.109", "tempfile", - "which", + "which 4.4.2", ] [[package]] @@ -6587,7 +6859,7 @@ dependencies = [ "regex", "syn 2.0.39", "tempfile", - "which", + "which 4.4.2", ] [[package]] @@ -6721,19 +6993,19 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "redb" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "08837f9a129bde83c51953b8c96cbb3422b940166b730caa954836106eb1dfd2" dependencies = [ - "bitflags 1.3.2", + "libc", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] @@ -6820,6 +7092,7 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ + "async-compression", "base64 0.21.5", "bytes", "encoding_rs", @@ -6830,25 +7103,31 @@ dependencies = [ "http-body", "hyper", "hyper-rustls 0.24.2", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.9", + "rustls 0.21.10", + "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg", @@ -6856,8 +7135,8 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "0.1.0-alpha.12" -source = "git+https://github.com/paradigmxyz/reth.git#6f7d6d50160e0dbb84de5b74f9794e14cc04406b" +version = "0.1.0-alpha.13" +source = "git+https://github.com/paradigmxyz/reth.git#cf5006108c5ed2b774f09fbf02a694ec67e95ba0" dependencies = [ "bitflags 2.4.1", "byteorder", @@ -6871,8 +7150,8 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "0.1.0-alpha.12" -source = "git+https://github.com/paradigmxyz/reth.git#6f7d6d50160e0dbb84de5b74f9794e14cc04406b" +version = "0.1.0-alpha.13" +source = "git+https://github.com/paradigmxyz/reth.git#cf5006108c5ed2b774f09fbf02a694ec67e95ba0" dependencies = [ "bindgen", "cc", @@ -6906,9 +7185,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", @@ -7012,9 +7291,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "bfeae074e687625746172d639330f1de242a178bf3189b51e35a7a21573513ac" dependencies = [ "bitflags 2.4.1", "errno", @@ -7037,12 +7316,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.6", + "ring 0.17.7", "rustls-webpki", "sct", ] @@ -7074,7 +7353,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -7175,8 +7454,8 @@ dependencies = [ [[package]] name = "scarb" -version = "2.3.1" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +version = "2.4.0" +source = "git+https://github.com/software-mansion/scarb?tag=v2.4.0#cba988e685f2f9b07a8ea0b5f056009f91c6c9ed" dependencies = [ "anyhow", "async-trait", @@ -7200,18 +7479,21 @@ dependencies = [ "directories", "dunce", "fs4", + "fs_extra", "futures", "gix", "glob", "ignore", "include_dir", "indoc 2.0.4", - "itertools 0.11.0", + "itertools 0.12.0", "once_cell", "pathdiff", "petgraph", + "redb", + "reqwest", "scarb-build-metadata", - "scarb-metadata 1.8.0", + "scarb-metadata 1.9.0 (git+https://github.com/software-mansion/scarb?tag=v2.4.0)", "scarb-ui", "semver", "serde", @@ -7226,35 +7508,35 @@ dependencies = [ "thiserror", "tokio", "toml 0.8.8", - "toml_edit 0.20.7", + "toml_edit 0.21.0", "tracing", - "tracing-log 0.1.4", + "tracing-log 0.2.0", "tracing-subscriber", "typed-builder", "url", "walkdir", - "which", + "which 5.0.0", "windows-sys 0.48.0", "xxhash-rust", "zip", - "zstd 0.12.4", + "zstd 0.13.0", ] [[package]] name = "scarb-build-metadata" -version = "2.3.1" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +version = "2.4.0" +source = "git+https://github.com/software-mansion/scarb?tag=v2.4.0#cba988e685f2f9b07a8ea0b5f056009f91c6c9ed" dependencies = [ "cargo_metadata", ] [[package]] name = "scarb-metadata" -version = "1.8.0" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf294b35e5abed4510b98150fbdfad402111cb05532b38d8569a1c3edea6d1a6" dependencies = [ "camino", - "derive_builder", "semver", "serde", "serde_json", @@ -7264,10 +7546,10 @@ dependencies = [ [[package]] name = "scarb-metadata" version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf294b35e5abed4510b98150fbdfad402111cb05532b38d8569a1c3edea6d1a6" +source = "git+https://github.com/software-mansion/scarb?tag=v2.4.0#cba988e685f2f9b07a8ea0b5f056009f91c6c9ed" dependencies = [ "camino", + "derive_builder", "semver", "serde", "serde_json", @@ -7276,15 +7558,15 @@ dependencies = [ [[package]] name = "scarb-ui" -version = "0.1.0" -source = "git+https://github.com/software-mansion/scarb?rev=0c8def3#0c8def3aa0cd94d988336340202b24bfa52fff08" +version = "0.1.2" +source = "git+https://github.com/software-mansion/scarb?tag=v2.4.0#cba988e685f2f9b07a8ea0b5f056009f91c6c9ed" dependencies = [ "anyhow", "camino", "clap", "console", "indicatif", - "scarb-metadata 1.8.0", + "scarb-metadata 1.9.0 (git+https://github.com/software-mansion/scarb?tag=v2.4.0)", "serde", "serde_json", ] @@ -7353,7 +7635,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -7643,6 +7925,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.2.0" @@ -7728,9 +8016,9 @@ dependencies = [ [[package]] name = "snapbox" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" +checksum = "c4f1976ee8fd1be27d5f72c98be0aac4397a882a4736935d47418a5fbbd12042" dependencies = [ "anstream", "anstyle", @@ -7815,6 +8103,7 @@ dependencies = [ "cairo-lang-starknet", "cairo-lang-test-plugin", "cairo-lang-test-runner", + "cairo-lang-utils", "camino", "clap", "clap-verbosity-flag", @@ -7880,11 +8169,11 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.11.0", + "itertools 0.12.0", "nom", "unicode_categories", ] @@ -7909,7 +8198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ "ahash 0.8.6", - "async-io", + "async-io 1.13.0", "async-std", "atoi", "byteorder", @@ -7919,7 +8208,7 @@ dependencies = [ "crossbeam-queue", "dotenvy", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -8293,18 +8582,21 @@ dependencies = [ [[package]] name = "starknet_api" -version = "0.2.0" -source = "git+https://github.com/starkware-libs/starknet-api?rev=ecc9b6946ef13003da202838e4124a9ad2efabb0#ecc9b6946ef13003da202838e4124a9ad2efabb0" +version = "0.6.0-rc3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874475a79285b03525dcb6773c5b6436d0bb937de9791c43a02a682a0fcbefd4" dependencies = [ "cairo-lang-starknet", "derive_more", "hex", - "indexmap 1.9.3", + "indexmap 2.1.0", "once_cell", "primitive-types", "serde", "serde_json", "starknet-crypto 0.5.2", + "strum 0.24.1", + "strum_macros 0.24.3", "thiserror", ] @@ -8498,7 +8790,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.26", + "rustix 0.38.27", "windows-sys 0.48.0", ] @@ -8530,9 +8822,19 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "test-log" -version = "0.2.13" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6159ab4116165c99fc88cce31f99fa2c9dbe08d3691cb38da02fc3b45f357d2b" +dependencies = [ + "env_logger", + "test-log-macros", +] + +[[package]] +name = "test-log-macros" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66edd6b6cd810743c0c71e1d085e92b01ce6a72782032e3f794c8284fe4bcdd" +checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", @@ -8646,9 +8948,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -8684,6 +8986,16 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -8701,7 +9013,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.9", + "rustls 0.21.10", "tokio", ] @@ -8724,7 +9036,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.9", + "rustls 0.21.10", "tokio", "tokio-rustls 0.24.1", "tungstenite", @@ -8808,8 +9120,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.1.0", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -9198,9 +9508,9 @@ checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-lsp" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b38fb0e6ce037835174256518aace3ca621c4f96383c56bb846cfc11b341910" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" dependencies = [ "async-trait", "auto_impl", @@ -9221,13 +9531,13 @@ dependencies = [ [[package]] name = "tower-lsp-macros" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34723c06344244474fdde365b76aebef8050bf6be61a935b91ee9ff7c4e91157" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] @@ -9334,9 +9644,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" @@ -9351,7 +9661,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.21.9", + "rustls 0.21.10", "sha1", "thiserror", "url", @@ -9360,18 +9670,18 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.16.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34085c17941e36627a879208083e25d357243812c30e7d7387c3b954f30ade16" +checksum = "e47c0496149861b7c95198088cbf36645016b1a0734cf350c50e2a38e070f38a" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.16.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" +checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" dependencies = [ "proc-macro2", "quote", @@ -9437,9 +9747,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-bom" @@ -9561,9 +9871,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" [[package]] name = "vcpkg" @@ -9588,9 +9898,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" @@ -9743,7 +10053,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -9762,7 +10072,20 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.26", + "rustix 0.38.27", +] + +[[package]] +name = "which" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.27", + "windows-sys 0.48.0", ] [[package]] @@ -10020,9 +10343,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" dependencies = [ "memchr", ] @@ -10103,18 +10426,18 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.28" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.28" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", @@ -10170,15 +10493,6 @@ dependencies = [ "zstd-safe 5.0.2+zstd.1.5.2", ] -[[package]] -name = "zstd" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" -dependencies = [ - "zstd-safe 6.0.6", -] - [[package]] name = "zstd" version = "0.13.0" @@ -10198,16 +10512,6 @@ dependencies = [ "zstd-sys", ] -[[package]] -name = "zstd-safe" -version = "6.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" -dependencies = [ - "libc", - "zstd-sys", -] - [[package]] name = "zstd-safe" version = "7.0.0" diff --git a/Cargo.toml b/Cargo.toml index f2a26833fd..f1b8d8955f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,29 +41,29 @@ anyhow = "1.0.75" assert_matches = "1.5.0" async-trait = "0.1.68" base64 = "0.21.2" -blockifier = { git = "https://github.com/starkware-libs/blockifier" } -cairo-lang-casm = "2.3.1" -cairo-lang-compiler = "2.3.1" -cairo-lang-debug = "2.3.1" -cairo-lang-defs = "2.3.1" -cairo-lang-diagnostics = "2.3.1" -cairo-lang-filesystem = "2.3.1" -cairo-lang-formatter = "2.3.1" -cairo-lang-language-server = "2.3.1" -cairo-lang-lowering = "2.3.1" -cairo-lang-parser = "2.3.1" -cairo-lang-plugins = "2.3.1" -cairo-lang-project = "2.3.1" -cairo-lang-semantic = { version = "2.3.1", features = [ "testing" ] } -cairo-lang-sierra = "2.3.1" -cairo-lang-sierra-generator = "2.3.1" -cairo-lang-sierra-to-casm = "2.3.1" -cairo-lang-starknet = "2.3.1" -cairo-lang-syntax = "2.3.1" -cairo-lang-test-plugin = "2.3.1" -cairo-lang-test-runner = "2.3.1" -cairo-lang-test-utils = "2.3.1" -cairo-lang-utils = "2.3.1" +blockifier = { git = "https://github.com/starkware-libs/blockifier", tag = "v0.4.0-rc9.2" } +cairo-lang-casm = "2.4.0" +cairo-lang-compiler = "2.4.0" +cairo-lang-debug = "2.4.0" +cairo-lang-defs = "2.4.0" +cairo-lang-diagnostics = "2.4.0" +cairo-lang-filesystem = "2.4.0" +cairo-lang-formatter = "2.4.0" +cairo-lang-language-server = "2.4.0" +cairo-lang-lowering = "2.4.0" +cairo-lang-parser = "2.4.0" +cairo-lang-plugins = "2.4.0" +cairo-lang-project = "2.4.0" +cairo-lang-semantic = { version = "2.4.0", features = [ "testing" ] } +cairo-lang-sierra = "2.4.0" +cairo-lang-sierra-generator = "2.4.0" +cairo-lang-sierra-to-casm = "2.4.0" +cairo-lang-starknet = "2.4.0" +cairo-lang-syntax = "2.4.0" +cairo-lang-test-plugin = "2.4.0" +cairo-lang-test-runner = "2.4.0" +cairo-lang-test-utils = "2.4.0" +cairo-lang-utils = "2.4.0" cairo-vm = "0.8.2" camino = { version = "1.1.2", features = [ "serde1" ] } chrono = { version = "0.4.24", features = [ "serde" ] } @@ -87,17 +87,17 @@ parking_lot = "0.12.1" pretty_assertions = "1.2.1" rayon = "1.8.0" salsa = "0.16.1" -scarb = { git = "https://github.com/software-mansion/scarb", rev = "0c8def3" } -scarb-ui = { git = "https://github.com/software-mansion/scarb", rev = "0c8def3" } +scarb = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } +scarb-ui = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } semver = "1.0.5" serde = { version = "1.0.156", features = [ "derive" ] } serde_json = "1.0" serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } -sqlx = { version = "0.7.2", features = [ "chrono", "macros", "runtime-async-std", "runtime-tokio", "sqlite", "uuid", "regexp" ] } +sqlx = { version = "0.7.2", features = [ "chrono", "macros", "regexp", "runtime-async-std", "runtime-tokio", "sqlite", "uuid" ] } starknet = "0.7.0" starknet-crypto = "0.6.1" -starknet_api = { git = "https://github.com/starkware-libs/starknet-api", rev = "ecc9b6946ef13003da202838e4124a9ad2efabb0" } +starknet_api = "0.6.0-rc3" strum = "0.25" strum_macros = "0.25" test-log = "0.2.11" @@ -126,7 +126,7 @@ wasm-tonic = { version = "0.9.2", default-features = false, features = [ "codege wasm-tonic-build = { version = "0.9.2", default-features = false, features = [ "prost" ], package = "tonic-build" } [patch."https://github.com/starkware-libs/blockifier"] -blockifier = { git = "https://github.com/dojoengine/blockifier", rev = "3357e69" } +blockifier = { git = "https://github.com/dojoengine/blockifier", rev = "e6e9c90" } [patch.crates-io] cairo-felt = { git = "https://github.com/dojoengine/cairo-rs.git", rev = "262b7eb4b11ab165a2a936a5f914e78aa732d4a2" } diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index cf78540539..8790f62c3d 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -1,9 +1,9 @@ [package] -cairo-version = "2.3.1" +cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" version = "0.4.0-rc0" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } -starknet = "2.3.1" +starknet = "2.4.0" diff --git a/crates/dojo-lang/Cargo.toml b/crates/dojo-lang/Cargo.toml index 244e81a1ef..1d1da8d84c 100644 --- a/crates/dojo-lang/Cargo.toml +++ b/crates/dojo-lang/Cargo.toml @@ -24,6 +24,7 @@ cairo-lang-semantic.workspace = true cairo-lang-sierra-generator.workspace = true cairo-lang-starknet.workspace = true cairo-lang-syntax.workspace = true +cairo-lang-test-plugin.workspace = true cairo-lang-utils.workspace = true camino.workspace = true convert_case.workspace = true diff --git a/crates/dojo-lang/README.md b/crates/dojo-lang/README.md index 2868ce5cb4..8f1f5da0b5 100644 --- a/crates/dojo-lang/README.md +++ b/crates/dojo-lang/README.md @@ -12,4 +12,8 @@ To run the tests, run: cargo test --package dojo-lang --lib -- plugin::test::expand_contract::model --exact --nocapture ``` -To regenerate, set `CAIRO_FIX_TESTS=1`. +To regenerate, set `CAIRO_FIX_TESTS=1`: + +``` +CAIRO_FIX_TESTS=1 cargo test --package dojo-lang +``` diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index d6108e0735..6824d0ffd4 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.3.11" +version = "0.4.0" [cairo-plugin] diff --git a/crates/dojo-lang/src/compiler_test.rs b/crates/dojo-lang/src/compiler_test.rs index 39e84950d5..e124ac014d 100644 --- a/crates/dojo-lang/src/compiler_test.rs +++ b/crates/dojo-lang/src/compiler_test.rs @@ -7,11 +7,12 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use dojo_test_utils::compiler::build_test_config; use dojo_world::manifest::{BASE_CONTRACT_NAME, EXECUTOR_CONTRACT_NAME, WORLD_CONTRACT_NAME}; use scarb::core::TargetKind; -use scarb::ops::{self, CompileOpts}; +use scarb::ops::CompileOpts; use smol_str::SmolStr; use starknet::macros::felt; use super::do_update_manifest; +use crate::scarb_internal::{self}; fn build_mock_manifest() -> dojo_world::manifest::Manifest { dojo_world::manifest::Manifest { @@ -152,13 +153,10 @@ fn update_manifest_correctly() { #[test] fn test_compiler() { let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); - let ws = ops::read_workspace(config.manifest_path(), &config).unwrap(); - let packages = ws.members().map(|p| p.id).collect(); assert!( - ops::compile( - packages, + scarb_internal::compile_workspace( + &config, CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - &ws ) .is_ok(), "compilation failed" @@ -179,15 +177,12 @@ pub fn test_manifest_file( _args: &OrderedHashMap, ) -> TestRunnerResult { let config = build_test_config("./src/manifest_test_data/spawn-and-move/Scarb.toml").unwrap(); - let ws = ops::read_workspace(config.manifest_path(), &config).unwrap(); - let packages = ws.members().map(|p| p.id).collect(); - ops::compile( - packages, + scarb_internal::compile_workspace( + &config, CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - &ws, ) - .unwrap_or_else(|op| panic!("Error compiling: {op:?}")); + .unwrap_or_else(|err| panic!("Error compiling: {err:?}")); let target_dir = config.target_dir_override().unwrap(); diff --git a/crates/dojo-lang/src/inline_macros/emit.rs b/crates/dojo-lang/src/inline_macros/emit.rs index c0a08888bc..8cd306775d 100644 --- a/crates/dojo-lang/src/inline_macros/emit.rs +++ b/crates/dojo-lang/src/inline_macros/emit.rs @@ -1,15 +1,17 @@ use cairo_lang_defs::patcher::PatchBuilder; use cairo_lang_defs::plugin::{ - InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, + InlineMacroExprPlugin, InlinePluginResult, NamedPlugin, PluginDiagnostic, PluginGeneratedFile, }; use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct EmitMacro; -impl EmitMacro { - pub const NAME: &'static str = "emit"; + +impl NamedPlugin for EmitMacro { + const NAME: &'static str = "emit"; } + impl InlineMacroExprPlugin for EmitMacro { fn generate_code( &self, @@ -26,13 +28,13 @@ impl InlineMacroExprPlugin for EmitMacro { let mut data = Default::::default();", ); - let args = arg_list.args(db).elements(db); + let args = arg_list.arguments(db).elements(db); if args.len() != 2 { return InlinePluginResult { code: None, diagnostics: vec![PluginDiagnostic { - stable_ptr: arg_list.args(db).stable_ptr().untyped(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), message: "Invalid arguments. Expected \"emit!(world, event)\"".to_string(), }], }; diff --git a/crates/dojo-lang/src/inline_macros/get.rs b/crates/dojo-lang/src/inline_macros/get.rs index ecb3aec87a..cc249627f9 100644 --- a/crates/dojo-lang/src/inline_macros/get.rs +++ b/crates/dojo-lang/src/inline_macros/get.rs @@ -1,6 +1,6 @@ use cairo_lang_defs::patcher::PatchBuilder; use cairo_lang_defs::plugin::{ - InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, + InlineMacroExprPlugin, InlinePluginResult, NamedPlugin, PluginDiagnostic, PluginGeneratedFile, }; use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; use cairo_lang_syntax::node::ast::{Expr, ItemModule}; @@ -11,11 +11,13 @@ use itertools::Itertools; use super::utils::{parent_of_kind, SYSTEM_READS}; use super::{extract_models, unsupported_arg_diagnostic, CAIRO_ERR_MSG_LEN}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct GetMacro; -impl GetMacro { - pub const NAME: &'static str = "get"; + +impl NamedPlugin for GetMacro { + const NAME: &'static str = "get"; } + impl InlineMacroExprPlugin for GetMacro { fn generate_code( &self, @@ -31,7 +33,7 @@ impl InlineMacroExprPlugin for GetMacro { let mut __get_macro_keys__ = array::ArrayTrait::new();\n", ); - let args = arg_list.args(db).elements(db); + let args = arg_list.arguments(db).elements(db); if args.len() != 3 { return InlinePluginResult { diff --git a/crates/dojo-lang/src/inline_macros/set.rs b/crates/dojo-lang/src/inline_macros/set.rs index d95eb1bab5..d17c7cb48f 100644 --- a/crates/dojo-lang/src/inline_macros/set.rs +++ b/crates/dojo-lang/src/inline_macros/set.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use cairo_lang_defs::patcher::PatchBuilder; use cairo_lang_defs::plugin::{ - InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, + InlineMacroExprPlugin, InlinePluginResult, NamedPlugin, PluginDiagnostic, PluginGeneratedFile, }; use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; use cairo_lang_syntax::node::ast::{ExprPath, ExprStructCtorCall, FunctionWithBody, ItemModule}; @@ -12,10 +12,11 @@ use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use super::unsupported_arg_diagnostic; use super::utils::{parent_of_kind, SystemRWOpRecord, SYSTEM_WRITES}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SetMacro; -impl SetMacro { - pub const NAME: &'static str = "set"; + +impl NamedPlugin for SetMacro { + const NAME: &'static str = "set"; // Parents of set!() // ----------------- // StatementExpr @@ -31,6 +32,7 @@ impl SetMacro { // ItemList // SyntaxFile } + impl InlineMacroExprPlugin for SetMacro { fn generate_code( &self, @@ -43,13 +45,13 @@ impl InlineMacroExprPlugin for SetMacro { let mut builder = PatchBuilder::new(db); builder.add_str("{"); - let args = arg_list.args(db).elements(db); + let args = arg_list.arguments(db).elements(db); if args.len() != 2 { return InlinePluginResult { code: None, diagnostics: vec![PluginDiagnostic { - stable_ptr: arg_list.args(db).stable_ptr().untyped(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), }], }; @@ -83,7 +85,7 @@ impl InlineMacroExprPlugin for SetMacro { code: None, diagnostics: vec![PluginDiagnostic { message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), - stable_ptr: arg_list.args(db).stable_ptr().untyped(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), }], }; } @@ -94,7 +96,7 @@ impl InlineMacroExprPlugin for SetMacro { code: None, diagnostics: vec![PluginDiagnostic { message: "Invalid arguments: No models provided.".to_string(), - stable_ptr: arg_list.args(db).stable_ptr().untyped(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), }], }; } diff --git a/crates/dojo-lang/src/lib.rs b/crates/dojo-lang/src/lib.rs index 218e499037..ced65c7cde 100644 --- a/crates/dojo-lang/src/lib.rs +++ b/crates/dojo-lang/src/lib.rs @@ -12,3 +12,7 @@ pub mod plugin; pub mod print; pub mod semantics; pub(crate) mod version; + +// Copy of non pub functions from scarb + extension. +// Also used by `sozo`. +pub mod scarb_internal; diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 587db37a3a..0a737a9b4f 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -8,7 +8,7 @@ test_manifest_file "world": { "name": "world", "address": null, - "class_hash": "0x99b08b2ff33750916e36b5e241b5d4a63e8d48862bf90a68fec2ff58a8de6", + "class_hash": "0x5ac623f0c96059936bd2d0904bdd31799e430fe08a0caff7a5f497260b16497", "abi": [ { "type": "impl", @@ -791,7 +791,7 @@ test_manifest_file "executor": { "name": "executor", "address": null, - "class_hash": "0x54ee9d99457d82d53e21030920f7e6f43509b98ea62282b5c2520050264a28c", + "class_hash": "0x585507fa2818fe78e66da6ea4c5915376739f4abf509d41153f60a16cb1f68d", "abi": [ { "type": "impl", @@ -851,7 +851,7 @@ test_manifest_file }, "base": { "name": "base", - "class_hash": "0x77638e9a645209ac1e32e143bfdbfe9caf723c4f7645fcf465c38967545ea2f", + "class_hash": "0x6c458453d35753703ad25632deec20a29faf8531942ec109e6eb0650316a2bc", "abi": [ { "type": "impl", @@ -955,7 +955,7 @@ test_manifest_file { "name": "actions", "address": null, - "class_hash": "0x1f740b30fc835ecf509a40e8dc8e4eb7ada046243833d2060f17ab961e4e154", + "class_hash": "0x69c6bec7de74fc2404fe6b68ad8ece7be81ad6d861b38a8ba8fa583bfc3666b", "abi": [ { "type": "impl", @@ -1198,49 +1198,14 @@ test_manifest_file ] } ], - "reads": [ - "Moves", - "Position" - ], - "writes": [ - "Moves", - "Position" - ], - "computed": [ - { - "contract": "actions", - "entrypoint": "tile_terrain", - "model": null - }, - { - "contract": "actions", - "entrypoint": "quadrant", - "model": "Position" - } - ] - } - ], - "models": [ + "reads": [], + "writes": [], + "computed": [] + }, { - "name": "Moves", - "members": [ - { - "name": "player", - "type": "ContractAddress", - "key": true - }, - { - "name": "remaining", - "type": "u8", - "key": false - }, - { - "name": "last_direction", - "type": "Direction", - "key": false - } - ], - "class_hash": "0x509a65bd8cc5516176a694a3b3c809011f1f0680959c567b3189e60ddab7ce1", + "name": "moves", + "address": null, + "class_hash": "0x64495ca6dc1dc328972697b30468cea364bcb7452bbb6e4aaad3e4b3f190147", "abi": [ { "type": "function", @@ -1401,23 +1366,15 @@ test_manifest_file "kind": "enum", "variants": [] } - ] + ], + "reads": [], + "writes": [], + "computed": [] }, { - "name": "Position", - "members": [ - { - "name": "player", - "type": "ContractAddress", - "key": true - }, - { - "name": "vec", - "type": "Vec2", - "key": false - } - ], - "class_hash": "0x52a1da1853c194683ca5d6d154452d0654d23f2eacd4267c555ff2338e144d6", + "name": "position", + "address": null, + "class_hash": "0x2b233bba9a232a5e891c85eca9f67beedca7a12f9768729ff017bcb62d25c9d", "abi": [ { "type": "function", @@ -1578,7 +1535,11 @@ test_manifest_file "kind": "enum", "variants": [] } - ] + ], + "reads": [], + "writes": [], + "computed": [] } - ] + ], + "models": [] } diff --git a/crates/dojo-lang/src/plugin.rs b/crates/dojo-lang/src/plugin.rs index 0c350b829d..725a0e1b93 100644 --- a/crates/dojo-lang/src/plugin.rs +++ b/crates/dojo-lang/src/plugin.rs @@ -1,10 +1,8 @@ -use std::sync::Arc; - use anyhow::Result; use cairo_lang_defs::patcher::PatchBuilder; use cairo_lang_defs::plugin::{ - DynGeneratedFileAuxData, GeneratedFileAuxData, InlineMacroExprPlugin, MacroPlugin, - PluginDiagnostic, PluginGeneratedFile, PluginResult, + DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, PluginDiagnostic, + PluginGeneratedFile, PluginResult, PluginSuite, }; use cairo_lang_syntax::attribute::structured::{ AttributeArg, AttributeArgVariant, AttributeStructurize, @@ -188,7 +186,8 @@ impl BuiltinDojoPlugin { impl CairoPlugin for BuiltinDojoPlugin { fn id(&self) -> PackageId { let url = Url::parse("https://github.com/dojoengine/dojo").unwrap(); - let version = "0.3.11"; + let version = "0.4.0"; + // TODO: update this once pushed. let rev = "1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659"; let source_id = @@ -207,17 +206,21 @@ impl CairoPlugin for BuiltinDojoPlugin { struct BuiltinDojoPluginInstance; impl CairoPluginInstance for BuiltinDojoPluginInstance { - fn macro_plugins(&self) -> Vec> { - vec![Arc::new(BuiltinDojoPlugin)] + fn plugin_suite(&self) -> PluginSuite { + dojo_plugin_suite() } +} - fn inline_macro_plugins(&self) -> Vec<(String, Arc)> { - vec![ - (GetMacro::NAME.into(), Arc::new(GetMacro)), - (SetMacro::NAME.into(), Arc::new(SetMacro)), - (EmitMacro::NAME.into(), Arc::new(EmitMacro)), - ] - } +pub fn dojo_plugin_suite() -> PluginSuite { + let mut suite = PluginSuite::default(); + + suite + .add_plugin::() + .add_inline_macro_plugin::() + .add_inline_macro_plugin::() + .add_inline_macro_plugin::(); + + suite } impl MacroPlugin for BuiltinDojoPlugin { diff --git a/crates/dojo-lang/src/plugin_test.rs b/crates/dojo-lang/src/plugin_test.rs index 338cbbc8eb..d789ff5a5b 100644 --- a/crates/dojo-lang/src/plugin_test.rs +++ b/crates/dojo-lang/src/plugin_test.rs @@ -6,11 +6,11 @@ use cairo_lang_defs::plugin::MacroPlugin; use cairo_lang_diagnostics::{format_diagnostics, DiagnosticLocation}; use cairo_lang_filesystem::cfg::CfgSet; use cairo_lang_filesystem::db::{ - init_files_group, AsFilesGroupMut, FilesDatabase, FilesGroup, FilesGroupEx, + init_files_group, AsFilesGroupMut, CrateConfiguration, FilesDatabase, FilesGroup, FilesGroupEx, }; use cairo_lang_filesystem::ids::{CrateLongId, Directory, FileLongId}; use cairo_lang_parser::db::ParserDatabase; -use cairo_lang_plugins::get_default_plugins; +use cairo_lang_plugins::get_base_plugins; use cairo_lang_syntax::node::db::{SyntaxDatabase, SyntaxGroup}; use cairo_lang_syntax::node::kind::SyntaxKind; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; @@ -50,7 +50,7 @@ impl Default for DatabaseForTesting { fn default() -> Self { let mut res = Self { storage: Default::default() }; init_files_group(&mut res); - res.set_macro_plugins(get_default_plugins()); + res.set_macro_plugins(get_base_plugins()); res } } @@ -96,7 +96,8 @@ pub fn test_expand_plugin_inner( let crate_id = db.intern_crate(CrateLongId::Real("test".into())); let root = Directory::Real("test_src".into()); - db.set_crate_root(crate_id, Some(root)); + + db.set_crate_config(crate_id, Some(CrateConfiguration::default_for_root(root))); // Main module file. let file_id = db.intern_file(FileLongId::OnDisk("test_src/lib.cairo".into())); diff --git a/crates/dojo-lang/src/plugin_test_data/introspect b/crates/dojo-lang/src/plugin_test_data/introspect index 7ee8be69f3..15fd1b5f07 100644 --- a/crates/dojo-lang/src/plugin_test_data/introspect +++ b/crates/dojo-lang/src/plugin_test_data/introspect @@ -4,7 +4,7 @@ test_expand_plugin //! > cairo_code -use serde::Serde; +use core::serde::Serde; #[derive(Copy, Drop, Serde, Introspect)] struct Vec2 { @@ -51,7 +51,7 @@ struct GenericStruct { } //! > expanded_cairo_code -use serde::Serde; +use core::serde::Serde; #[derive(Copy, Drop, Serde, Introspect)] struct Vec2 { @@ -96,17 +96,17 @@ struct Position { struct GenericStruct { t: T, } -impl Vec2Copy of Copy::; -impl Vec2Drop of Drop::; -impl Vec2Serde of Serde:: { - fn serialize(self: @Vec2, ref output: array::Array) { - serde::Serde::serialize(self.x, ref output); - serde::Serde::serialize(self.y, ref output) - } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Vec2 { - x: serde::Serde::deserialize(ref serialized)?, - y: serde::Serde::deserialize(ref serialized)?, +impl Vec2Copy of core::traits::Copy::; +impl Vec2Drop of core::traits::Drop::; +impl Vec2Serde of core::serde::Serde:: { + fn serialize(self: @Vec2, ref output: core::array::Array) { + core::serde::Serde::serialize(self.x, ref output); + core::serde::Serde::serialize(self.y, ref output) + } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Vec2 { + x: core::serde::Serde::deserialize(ref serialized)?, + y: core::serde::Serde::deserialize(ref serialized)?, }) } } @@ -141,24 +141,24 @@ layout.append(32); }) } } -impl PlainEnumSerde of Serde:: { - fn serialize(self: @PlainEnum, ref output: array::Array) { +impl PlainEnumSerde of core::serde::Serde:: { + fn serialize(self: @PlainEnum, ref output: core::array::Array) { match self { - PlainEnum::Left(x) => { serde::Serde::serialize(@0, ref output); serde::Serde::serialize(x, ref output); }, - PlainEnum::Right(x) => { serde::Serde::serialize(@1, ref output); serde::Serde::serialize(x, ref output); }, + PlainEnum::Left(x) => { core::serde::Serde::serialize(@0, ref output); core::serde::Serde::serialize(x, ref output); }, + PlainEnum::Right(x) => { core::serde::Serde::serialize(@1, ref output); core::serde::Serde::serialize(x, ref output); }, } } - fn deserialize(ref serialized: array::Span) -> Option { - let idx: felt252 = serde::Serde::deserialize(ref serialized)?; - Option::Some( - if idx == 0 { PlainEnum::Left(serde::Serde::deserialize(ref serialized)?) } - else if idx == 1 { PlainEnum::Right(serde::Serde::deserialize(ref serialized)?) } - else { return Option::None; } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + let idx: felt252 = core::serde::Serde::deserialize(ref serialized)?; + core::option::Option::Some( + if idx == 0 { PlainEnum::Left(core::serde::Serde::deserialize(ref serialized)?) } + else if idx == 1 { PlainEnum::Right(core::serde::Serde::deserialize(ref serialized)?) } + else { return core::option::Option::None; } ) } } -impl PlainEnumCopy of Copy::; -impl PlainEnumDrop of Drop::; +impl PlainEnumCopy of core::traits::Copy::; +impl PlainEnumDrop of core::traits::Drop::; impl PlainEnumIntrospect<> of dojo::database::introspect::Introspect> { #[inline(always)] @@ -192,24 +192,24 @@ impl PlainEnumIntrospect<> of dojo::database::introspect::Introspect ) } } -impl EnumPrimitiveSerde of Serde:: { - fn serialize(self: @EnumPrimitive, ref output: array::Array) { +impl EnumPrimitiveSerde of core::serde::Serde:: { + fn serialize(self: @EnumPrimitive, ref output: core::array::Array) { match self { - EnumPrimitive::Left(x) => { serde::Serde::serialize(@0, ref output); serde::Serde::serialize(x, ref output); }, - EnumPrimitive::Right(x) => { serde::Serde::serialize(@1, ref output); serde::Serde::serialize(x, ref output); }, + EnumPrimitive::Left(x) => { core::serde::Serde::serialize(@0, ref output); core::serde::Serde::serialize(x, ref output); }, + EnumPrimitive::Right(x) => { core::serde::Serde::serialize(@1, ref output); core::serde::Serde::serialize(x, ref output); }, } } - fn deserialize(ref serialized: array::Span) -> Option { - let idx: felt252 = serde::Serde::deserialize(ref serialized)?; - Option::Some( - if idx == 0 { EnumPrimitive::Left(serde::Serde::deserialize(ref serialized)?) } - else if idx == 1 { EnumPrimitive::Right(serde::Serde::deserialize(ref serialized)?) } - else { return Option::None; } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + let idx: felt252 = core::serde::Serde::deserialize(ref serialized)?; + core::option::Option::Some( + if idx == 0 { EnumPrimitive::Left(core::serde::Serde::deserialize(ref serialized)?) } + else if idx == 1 { EnumPrimitive::Right(core::serde::Serde::deserialize(ref serialized)?) } + else { return core::option::Option::None; } ) } } -impl EnumPrimitiveCopy of Copy::; -impl EnumPrimitiveDrop of Drop::; +impl EnumPrimitiveCopy of core::traits::Copy::; +impl EnumPrimitiveDrop of core::traits::Drop::; impl EnumPrimitiveIntrospect<> of dojo::database::introspect::Introspect> { #[inline(always)] @@ -248,24 +248,24 @@ layout.append(16); ) } } -impl EnumTupleSerde of Serde:: { - fn serialize(self: @EnumTuple, ref output: array::Array) { +impl EnumTupleSerde of core::serde::Serde:: { + fn serialize(self: @EnumTuple, ref output: core::array::Array) { match self { - EnumTuple::Left(x) => { serde::Serde::serialize(@0, ref output); serde::Serde::serialize(x, ref output); }, - EnumTuple::Right(x) => { serde::Serde::serialize(@1, ref output); serde::Serde::serialize(x, ref output); }, + EnumTuple::Left(x) => { core::serde::Serde::serialize(@0, ref output); core::serde::Serde::serialize(x, ref output); }, + EnumTuple::Right(x) => { core::serde::Serde::serialize(@1, ref output); core::serde::Serde::serialize(x, ref output); }, } } - fn deserialize(ref serialized: array::Span) -> Option { - let idx: felt252 = serde::Serde::deserialize(ref serialized)?; - Option::Some( - if idx == 0 { EnumTuple::Left(serde::Serde::deserialize(ref serialized)?) } - else if idx == 1 { EnumTuple::Right(serde::Serde::deserialize(ref serialized)?) } - else { return Option::None; } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + let idx: felt252 = core::serde::Serde::deserialize(ref serialized)?; + core::option::Option::Some( + if idx == 0 { EnumTuple::Left(core::serde::Serde::deserialize(ref serialized)?) } + else if idx == 1 { EnumTuple::Right(core::serde::Serde::deserialize(ref serialized)?) } + else { return core::option::Option::None; } ) } } -impl EnumTupleCopy of Copy::; -impl EnumTupleDrop of Drop::; +impl EnumTupleCopy of core::traits::Copy::; +impl EnumTupleDrop of core::traits::Drop::; impl EnumTupleIntrospect<> of dojo::database::introspect::Introspect> { #[inline(always)] @@ -309,24 +309,24 @@ layout.append(8); ) } } -impl EnumCustomSerde of Serde:: { - fn serialize(self: @EnumCustom, ref output: array::Array) { +impl EnumCustomSerde of core::serde::Serde:: { + fn serialize(self: @EnumCustom, ref output: core::array::Array) { match self { - EnumCustom::Left(x) => { serde::Serde::serialize(@0, ref output); serde::Serde::serialize(x, ref output); }, - EnumCustom::Right(x) => { serde::Serde::serialize(@1, ref output); serde::Serde::serialize(x, ref output); }, + EnumCustom::Left(x) => { core::serde::Serde::serialize(@0, ref output); core::serde::Serde::serialize(x, ref output); }, + EnumCustom::Right(x) => { core::serde::Serde::serialize(@1, ref output); core::serde::Serde::serialize(x, ref output); }, } } - fn deserialize(ref serialized: array::Span) -> Option { - let idx: felt252 = serde::Serde::deserialize(ref serialized)?; - Option::Some( - if idx == 0 { EnumCustom::Left(serde::Serde::deserialize(ref serialized)?) } - else if idx == 1 { EnumCustom::Right(serde::Serde::deserialize(ref serialized)?) } - else { return Option::None; } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + let idx: felt252 = core::serde::Serde::deserialize(ref serialized)?; + core::option::Option::Some( + if idx == 0 { EnumCustom::Left(core::serde::Serde::deserialize(ref serialized)?) } + else if idx == 1 { EnumCustom::Right(core::serde::Serde::deserialize(ref serialized)?) } + else { return core::option::Option::None; } ) } } -impl EnumCustomCopy of Copy::; -impl EnumCustomDrop of Drop::; +impl EnumCustomCopy of core::traits::Copy::; +impl EnumCustomDrop of core::traits::Drop::; impl EnumCustomIntrospect<> of dojo::database::introspect::Introspect> { #[inline(always)] @@ -365,8 +365,8 @@ dojo::database::introspect::Introspect::::layout(ref layout); ) } } -impl PositionCopy of Copy::; -impl PositionDrop of Drop::; +impl PositionCopy of core::traits::Copy::; +impl PositionDrop of core::traits::Drop::; impl PositionIntrospect<> of dojo::database::introspect::Introspect> { #[inline(always)] @@ -407,15 +407,15 @@ layout.append(16); }) } } -impl GenericStructCopy> of Copy::>; -impl GenericStructDrop> of Drop::>; -impl GenericStructSerde, impl TDestruct: Destruct> of Serde::> { - fn serialize(self: @GenericStruct, ref output: array::Array) { - serde::Serde::serialize(self.t, ref output) - } - fn deserialize(ref serialized: array::Span) -> Option> { - Option::Some(GenericStruct { - t: serde::Serde::deserialize(ref serialized)?, +impl GenericStructCopy> of core::traits::Copy::>; +impl GenericStructDrop> of core::traits::Drop::>; +impl GenericStructSerde, +core::traits::Destruct> of core::serde::Serde::> { + fn serialize(self: @GenericStruct, ref output: core::array::Array) { + core::serde::Serde::serialize(self.t, ref output) + } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option> { + core::option::Option::Some(GenericStruct { + t: core::serde::Serde::deserialize(ref serialized)?, }) } } diff --git a/crates/dojo-lang/src/plugin_test_data/model b/crates/dojo-lang/src/plugin_test_data/model index 00a549b83f..2072615d16 100644 --- a/crates/dojo-lang/src/plugin_test_data/model +++ b/crates/dojo-lang/src/plugin_test_data/model @@ -4,7 +4,7 @@ test_expand_plugin //! > cairo_code -use serde::Serde; +use core::serde::Serde; #[derive(Introspect, Copy, Drop, Serde)] struct Vec3 { @@ -56,7 +56,7 @@ struct Player { } //! > generated_cairo_code -use serde::Serde; +use core::serde::Serde; #[derive(Introspect, Copy, Drop, Serde)] @@ -139,7 +139,7 @@ impl PositionModel of dojo::model::Model { #[inline(always)] fn values(self: @Position) -> Span { let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.v, ref serialized); + core::serde::Serde::serialize(self.v, ref serialized); array::ArrayTrait::span(@serialized) } @@ -284,7 +284,7 @@ impl RolesModel of dojo::model::Model { #[inline(always)] fn values(self: @Roles) -> Span { let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.role_ids, ref serialized); + core::serde::Serde::serialize(self.role_ids, ref serialized); array::ArrayTrait::span(@serialized) } @@ -403,7 +403,7 @@ impl PlayerModel of dojo::model::Model { fn keys(self: @Player) -> Span { let mut serialized = ArrayTrait::new(); array::ArrayTrait::append(ref serialized, *self.game); - serde::Serde::serialize(self.player, ref serialized); + core::serde::Serde::serialize(self.player, ref serialized); array::ArrayTrait::span(@serialized) } @@ -631,7 +631,7 @@ error: Unsupported attribute. ^*************^ //! > expanded_cairo_code -use serde::Serde; +use core::serde::Serde; #[derive(Introspect, Copy, Drop, Serde)] struct Vec3 { @@ -681,19 +681,19 @@ struct Player { name: felt252, } -impl Vec3Copy of Copy::; -impl Vec3Drop of Drop::; -impl Vec3Serde of Serde:: { - fn serialize(self: @Vec3, ref output: array::Array) { - serde::Serde::serialize(self.x, ref output); - serde::Serde::serialize(self.y, ref output); - serde::Serde::serialize(self.z, ref output) - } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Vec3 { - x: serde::Serde::deserialize(ref serialized)?, - y: serde::Serde::deserialize(ref serialized)?, - z: serde::Serde::deserialize(ref serialized)?, +impl Vec3Copy of core::traits::Copy::; +impl Vec3Drop of core::traits::Drop::; +impl Vec3Serde of core::serde::Serde:: { + fn serialize(self: @Vec3, ref output: core::array::Array) { + core::serde::Serde::serialize(self.x, ref output); + core::serde::Serde::serialize(self.y, ref output); + core::serde::Serde::serialize(self.z, ref output) + } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Vec3 { + x: core::serde::Serde::deserialize(ref serialized)?, + y: core::serde::Serde::deserialize(ref serialized)?, + z: core::serde::Serde::deserialize(ref serialized)?, }) } } @@ -733,17 +733,17 @@ layout.append(8); }) } } -impl PositionCopy of Copy::; -impl PositionDrop of Drop::; -impl PositionSerde of Serde:: { - fn serialize(self: @Position, ref output: array::Array) { - serde::Serde::serialize(self.id, ref output); - serde::Serde::serialize(self.v, ref output) - } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Position { - id: serde::Serde::deserialize(ref serialized)?, - v: serde::Serde::deserialize(ref serialized)?, +impl PositionCopy of core::traits::Copy::; +impl PositionDrop of core::traits::Drop::; +impl PositionSerde of core::serde::Serde:: { + fn serialize(self: @Position, ref output: core::array::Array) { + core::serde::Serde::serialize(self.id, ref output); + core::serde::Serde::serialize(self.v, ref output) + } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Position { + id: core::serde::Serde::deserialize(ref serialized)?, + v: core::serde::Serde::deserialize(ref serialized)?, }) } } @@ -856,13 +856,13 @@ impl PositionIntrospect<> of dojo::database::introspect::Introspect> dojo::database::introspect::Introspect::::ty() } } -impl RolesSerde of Serde:: { - fn serialize(self: @Roles, ref output: array::Array) { - serde::Serde::serialize(self.role_ids, ref output) +impl RolesSerde of core::serde::Serde:: { + fn serialize(self: @Roles, ref output: core::array::Array) { + core::serde::Serde::serialize(self.role_ids, ref output) } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Roles { - role_ids: serde::Serde::deserialize(ref serialized)?, + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Roles { + role_ids: core::serde::Serde::deserialize(ref serialized)?, }) } } @@ -971,19 +971,19 @@ impl RolesIntrospect<> of dojo::database::introspect::Introspect> { dojo::database::introspect::Introspect::::ty() } } -impl PlayerCopy of Copy::; -impl PlayerDrop of Drop::; -impl PlayerSerde of Serde:: { - fn serialize(self: @Player, ref output: array::Array) { - serde::Serde::serialize(self.game, ref output); - serde::Serde::serialize(self.player, ref output); - serde::Serde::serialize(self.name, ref output) - } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Player { - game: serde::Serde::deserialize(ref serialized)?, - player: serde::Serde::deserialize(ref serialized)?, - name: serde::Serde::deserialize(ref serialized)?, +impl PlayerCopy of core::traits::Copy::; +impl PlayerDrop of core::traits::Drop::; +impl PlayerSerde of core::serde::Serde:: { + fn serialize(self: @Player, ref output: core::array::Array) { + core::serde::Serde::serialize(self.game, ref output); + core::serde::Serde::serialize(self.player, ref output); + core::serde::Serde::serialize(self.name, ref output) + } + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Player { + game: core::serde::Serde::deserialize(ref serialized)?, + player: core::serde::Serde::deserialize(ref serialized)?, + name: core::serde::Serde::deserialize(ref serialized)?, }) } } diff --git a/crates/dojo-lang/src/plugin_test_data/print b/crates/dojo-lang/src/plugin_test_data/print index f8611ec183..394b02bad8 100644 --- a/crates/dojo-lang/src/plugin_test_data/print +++ b/crates/dojo-lang/src/plugin_test_data/print @@ -126,45 +126,45 @@ struct Player { name: felt252, } -impl PositionCopy of Copy::; -impl PositionDrop of Drop::; -impl PositionSerde of Serde:: { - fn serialize(self: @Position, ref output: array::Array) { - serde::Serde::serialize(self.id, ref output); - serde::Serde::serialize(self.x, ref output); - serde::Serde::serialize(self.y, ref output) +impl PositionCopy of core::traits::Copy::; +impl PositionDrop of core::traits::Drop::; +impl PositionSerde of core::serde::Serde:: { + fn serialize(self: @Position, ref output: core::array::Array) { + core::serde::Serde::serialize(self.id, ref output); + core::serde::Serde::serialize(self.x, ref output); + core::serde::Serde::serialize(self.y, ref output) } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Position { - id: serde::Serde::deserialize(ref serialized)?, - x: serde::Serde::deserialize(ref serialized)?, - y: serde::Serde::deserialize(ref serialized)?, + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Position { + id: core::serde::Serde::deserialize(ref serialized)?, + x: core::serde::Serde::deserialize(ref serialized)?, + y: core::serde::Serde::deserialize(ref serialized)?, }) } } -impl RolesSerde of Serde:: { - fn serialize(self: @Roles, ref output: array::Array) { - serde::Serde::serialize(self.role_ids, ref output) +impl RolesSerde of core::serde::Serde:: { + fn serialize(self: @Roles, ref output: core::array::Array) { + core::serde::Serde::serialize(self.role_ids, ref output) } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Roles { - role_ids: serde::Serde::deserialize(ref serialized)?, + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Roles { + role_ids: core::serde::Serde::deserialize(ref serialized)?, }) } } -impl PlayerCopy of Copy::; -impl PlayerDrop of Drop::; -impl PlayerSerde of Serde:: { - fn serialize(self: @Player, ref output: array::Array) { - serde::Serde::serialize(self.game, ref output); - serde::Serde::serialize(self.player, ref output); - serde::Serde::serialize(self.name, ref output) +impl PlayerCopy of core::traits::Copy::; +impl PlayerDrop of core::traits::Drop::; +impl PlayerSerde of core::serde::Serde:: { + fn serialize(self: @Player, ref output: core::array::Array) { + core::serde::Serde::serialize(self.game, ref output); + core::serde::Serde::serialize(self.player, ref output); + core::serde::Serde::serialize(self.name, ref output) } - fn deserialize(ref serialized: array::Span) -> Option { - Option::Some(Player { - game: serde::Serde::deserialize(ref serialized)?, - player: serde::Serde::deserialize(ref serialized)?, - name: serde::Serde::deserialize(ref serialized)?, + fn deserialize(ref serialized: core::array::Span) -> core::option::Option { + core::option::Option::Some(Player { + game: core::serde::Serde::deserialize(ref serialized)?, + player: core::serde::Serde::deserialize(ref serialized)?, + name: core::serde::Serde::deserialize(ref serialized)?, }) } } diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index 13d26fa0c8..605557666b 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -499,7 +499,7 @@ mod testcomponent2 { #[substorage(v0)] upgradeable: dojo::components::upgradeable::upgradeable::Storage, } -impl EventDrop of Drop::; +impl EventDrop of core::traits::Drop::; } @@ -541,7 +541,7 @@ impl EventDrop of Drop::; #[substorage(v0)] upgradeable: dojo::components::upgradeable::upgradeable::Storage, } -impl EventDrop of Drop::; +impl EventDrop of core::traits::Drop::; } @@ -586,7 +586,7 @@ impl EventDrop of Drop::; #[substorage(v0)] upgradeable: dojo::components::upgradeable::upgradeable::Storage, } -impl EventDrop of Drop::; +impl EventDrop of core::traits::Drop::; } @@ -636,8 +636,8 @@ impl EventDrop of Drop::; #[substorage(v0)] upgradeable: dojo::components::upgradeable::upgradeable::Storage, } -impl EventDrop of Drop::; -impl TestEventDrop of Drop::; +impl EventDrop of core::traits::Drop::; +impl TestEventDrop of core::traits::Drop::; } @@ -682,6 +682,6 @@ impl TestEventDrop of Drop::; testcomponent1_event: testcomponent1::Event, testcomponent2_event: testcomponent2::Event } -impl EventDrop of Drop::; +impl EventDrop of core::traits::Drop::; } diff --git a/crates/dojo-lang/src/scarb_internal/mod.rs b/crates/dojo-lang/src/scarb_internal/mod.rs new file mode 100644 index 0000000000..bf104e7457 --- /dev/null +++ b/crates/dojo-lang/src/scarb_internal/mod.rs @@ -0,0 +1,100 @@ +// I have copied source code from https://github.com/software-mansion/scarb/blob/main/scarb/src/compiler/db.rs +// since build_scarb_root_database is not public. +// +// NOTE: This files needs to be updated whenever scarb version is updated. +// NOTE: This file was moved here from `sozo` as we need to compile here too, +// and `sozo` has `dojo-lang` as dependency. +use anyhow::Result; +use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent}; +use cairo_lang_filesystem::ids::Directory; +use cairo_lang_project::{AllCratesConfig, SingleCrateConfig}; +use cairo_lang_starknet::starknet_plugin_suite; +use cairo_lang_test_plugin::test_plugin_suite; +use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use scarb::compiler::CompilationUnit; +use scarb::core::Config; +use scarb::ops::CompileOpts; +use smol_str::SmolStr; +use tracing::trace; + +use crate::plugin::dojo_plugin_suite; + +pub fn crates_config_for_compilation_unit(unit: &CompilationUnit) -> AllCratesConfig { + let crates_config: OrderedHashMap = unit + .components + .iter() + .map(|component| { + ( + component.cairo_package_name(), + SingleCrateConfig { edition: component.package.manifest.edition }, + ) + }) + .collect(); + + AllCratesConfig { override_map: crates_config, ..Default::default() } +} + +// TODO(mkaput): ScarbDatabase? +pub fn build_scarb_root_database(unit: &CompilationUnit) -> Result { + let mut b = RootDatabase::builder(); + b.with_project_config(build_project_config(unit)?); + b.with_cfg(unit.cfg_set.clone()); + + // TODO: Is it fair to consider only those plugins at the moment? + b.with_plugin_suite(test_plugin_suite()); + b.with_plugin_suite(dojo_plugin_suite()); + b.with_plugin_suite(starknet_plugin_suite()); + + b.build() +} + +/// This function is an alternative to `ops::compile`, it's doing the same job. +/// However, we can control the injection of the plugins, required to have dojo plugin present +/// for each compilation. +pub fn compile_workspace(config: &Config, opts: CompileOpts) -> Result<()> { + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + let packages: Vec = ws.members().map(|p| p.id).collect(); + let resolve = scarb::ops::resolve_workspace(&ws)?; + let compilation_units = scarb::ops::generate_compilation_units(&resolve, &ws)? + .into_iter() + .filter(|cu| !opts.exclude_targets.contains(&cu.target().kind)) + .filter(|cu| { + opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind) + }) + .filter(|cu| packages.contains(&cu.main_package_id)) + .collect::>(); + + for unit in compilation_units { + let mut db = build_scarb_root_database(&unit).unwrap(); + + if let Err(err) = ws.config().compilers().compile(unit.clone(), &mut (db), &ws) { + ws.config().ui().anyhow(&err) + } + } + + Ok(()) +} + +fn build_project_config(unit: &CompilationUnit) -> Result { + let crate_roots = unit + .components + .iter() + .filter(|model| !model.package.id.is_core()) + .map(|model| (model.cairo_package_name(), model.target.source_root().into())) + .collect(); + + let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into())); + + let content = ProjectConfigContent { + crate_roots, + crates_config: crates_config_for_compilation_unit(unit), + }; + + let project_config = + ProjectConfig { base_path: unit.main_component().package.root().into(), corelib, content }; + + trace!(?project_config); + + Ok(project_config) +} diff --git a/crates/dojo-lang/src/semantics/test_data/get b/crates/dojo-lang/src/semantics/test_data/get index da5f04e60e..f08b775721 100644 --- a/crates/dojo-lang/src/semantics/test_data/get +++ b/crates/dojo-lang/src/semantics/test_data/get @@ -34,11 +34,6 @@ error: Plugin diagnostic: Invalid arguments. Expected "get!(world, keys, (models get!() ^****^ -error: Inline macro `get` failed. - --> lib.cairo:12:1 -get!() -^****^ - //! > ========================================================================== //! > Test wrong params @@ -77,11 +72,6 @@ error: Plugin diagnostic: Invalid arguments. Expected "get!(world, keys, (models get!(world) ^*********^ -error: Inline macro `get` failed. - --> lib.cairo:12:1 -get!(world) -^*********^ - //! > ========================================================================== //! > Test world and keys diff --git a/crates/dojo-lang/src/semantics/test_data/set b/crates/dojo-lang/src/semantics/test_data/set index 4633cf5ef8..cca850737a 100644 --- a/crates/dojo-lang/src/semantics/test_data/set +++ b/crates/dojo-lang/src/semantics/test_data/set @@ -29,14 +29,9 @@ Missing( //! > semantic_diagnostics error: Plugin diagnostic: Invalid arguments. Expected "(world, (models,))" - --> lib.cairo:11:1 + --> lib.cairo:11:6 set!() -^****^ - -error: Inline macro `set` failed. - --> lib.cairo:11:1 -set!() -^****^ + ^ //! > ========================================================================== @@ -71,14 +66,9 @@ Missing( //! > semantic_diagnostics error: Plugin diagnostic: Invalid arguments. Expected "(world, (models,))" - --> lib.cairo:11:1 -set!(world) -^*********^ - -error: Inline macro `set` failed. - --> lib.cairo:11:1 + --> lib.cairo:11:6 set!(world) -^*********^ + ^***^ //! > ========================================================================== diff --git a/crates/dojo-lang/src/semantics/test_utils.rs b/crates/dojo-lang/src/semantics/test_utils.rs index ad74412d33..6ab997153b 100644 --- a/crates/dojo-lang/src/semantics/test_utils.rs +++ b/crates/dojo-lang/src/semantics/test_utils.rs @@ -6,19 +6,18 @@ use cairo_lang_defs::db::{DefsDatabase, DefsGroup}; use cairo_lang_defs::ids::{FunctionWithBodyId, ModuleId}; use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder}; use cairo_lang_filesystem::db::{ - init_dev_corelib, init_files_group, AsFilesGroupMut, FilesDatabase, FilesGroup, FilesGroupEx, + init_dev_corelib, init_files_group, AsFilesGroupMut, CrateConfiguration, FilesDatabase, + FilesGroup, FilesGroupEx, }; use cairo_lang_filesystem::ids::{ CrateId, CrateLongId, Directory, FileKind, FileLongId, VirtualFile, }; use cairo_lang_parser::db::ParserDatabase; -use cairo_lang_plugins::get_default_plugins; use cairo_lang_semantic::db::{SemanticDatabase, SemanticGroup}; -use cairo_lang_semantic::inline_macros::get_default_inline_macro_plugins; +use cairo_lang_semantic::inline_macros::get_default_plugin_suite; use cairo_lang_semantic::items::functions::GenericFunctionId; use cairo_lang_semantic::{ConcreteFunctionWithBodyId, SemanticDiagnostic}; -use cairo_lang_starknet::inline_macros::selector::SelectorMacro; -use cairo_lang_starknet::plugin::StarkNetPlugin; +use cairo_lang_starknet::starknet_plugin_suite; use cairo_lang_syntax::node::db::{SyntaxDatabase, SyntaxGroup}; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use cairo_lang_utils::{extract_matches, OptionFrom, Upcast}; @@ -26,10 +25,7 @@ use camino::Utf8PathBuf; use dojo_test_utils::compiler::corelib; use once_cell::sync::Lazy; -use crate::inline_macros::emit::EmitMacro; -use crate::inline_macros::get::GetMacro; -use crate::inline_macros::set::SetMacro; -use crate::plugin::BuiltinDojoPlugin; +use crate::plugin::dojo_plugin_suite; #[salsa::database(SemanticDatabase, DefsDatabase, ParserDatabase, SyntaxDatabase, FilesDatabase)] pub struct DojoSemanticDatabase { @@ -46,25 +42,21 @@ impl DojoSemanticDatabase { pub fn new_empty() -> Self { let mut db = DojoSemanticDatabase { storage: Default::default() }; init_files_group(&mut db); - let mut plugins = get_default_plugins(); - plugins.push(Arc::new(StarkNetPlugin::default())); - plugins.push(Arc::new(BuiltinDojoPlugin)); - db.set_macro_plugins(plugins); - let mut inline_plugins = get_default_inline_macro_plugins(); - inline_plugins.insert(SelectorMacro::NAME.into(), Arc::new(SelectorMacro)); - inline_plugins.insert(GetMacro::NAME.into(), Arc::new(GetMacro)); - inline_plugins.insert(SetMacro::NAME.into(), Arc::new(SetMacro)); - inline_plugins.insert(EmitMacro::NAME.into(), Arc::new(EmitMacro)); + let mut suite = get_default_plugin_suite(); + suite.add(starknet_plugin_suite()); + suite.add(dojo_plugin_suite()); - db.set_inline_macro_plugins(inline_plugins.into()); + db.set_macro_plugins(suite.plugins); + db.set_inline_macro_plugins(suite.inline_macro_plugins.into()); init_dev_corelib(&mut db, corelib()); let dojo_path = Utf8PathBuf::from_path_buf("../../crates/dojo-core/src".into()).unwrap(); let dojo_path: PathBuf = dojo_path.canonicalize_utf8().unwrap().into(); let core_crate = db.intern_crate(CrateLongId::Real("dojo".into())); let core_root_dir = Directory::Real(dojo_path); - db.set_crate_root(core_crate, Some(core_root_dir)); + + db.set_crate_config(core_crate, Some(CrateConfiguration::default_for_root(core_root_dir))); db } diff --git a/crates/dojo-language-server/Cargo.toml b/crates/dojo-language-server/Cargo.toml index 9726b11355..5d742aa035 100644 --- a/crates/dojo-language-server/Cargo.toml +++ b/crates/dojo-language-server/Cargo.toml @@ -1,6 +1,6 @@ [package] edition.workspace = true -name = "dojo-languge-server" +name = "dojo-language-server" version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,4 +24,4 @@ log = "0.4.14" salsa = "0.16.1" smol_str.workspace = true tokio = { version = "1.18.2", features = [ "full", "sync" ] } -tower-lsp = "0.19.0" +tower-lsp = "0.20.0" diff --git a/crates/dojo-language-server/src/bin/language_server.rs b/crates/dojo-language-server/src/bin/language_server.rs index d9a78c4316..0539a0bf27 100644 --- a/crates/dojo-language-server/src/bin/language_server.rs +++ b/crates/dojo-language-server/src/bin/language_server.rs @@ -1,17 +1,11 @@ -use std::sync::Arc; - use cairo_lang_compiler::db::RootDatabase; use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; use cairo_lang_language_server::Backend; -use cairo_lang_starknet::inline_macros::selector::SelectorMacro; -use cairo_lang_starknet::plugin::StarkNetPlugin; -use cairo_lang_test_plugin::TestPlugin; +use cairo_lang_starknet::starknet_plugin_suite; +use cairo_lang_test_plugin::test_plugin_suite; use cairo_lang_utils::logging::init_logging; use clap::Parser; -use dojo_lang::inline_macros::emit::EmitMacro; -use dojo_lang::inline_macros::get::GetMacro; -use dojo_lang::inline_macros::set::SetMacro; -use dojo_lang::plugin::BuiltinDojoPlugin; +use dojo_lang::plugin::dojo_plugin_suite; use tower_lsp::{LspService, Server}; /// Dojo Language Server @@ -34,13 +28,9 @@ async fn main() { let db = RootDatabase::builder() .with_cfg(CfgSet::from_iter([Cfg::name("test")])) - .with_macro_plugin(Arc::new(BuiltinDojoPlugin)) - .with_macro_plugin(Arc::new(StarkNetPlugin::default())) - .with_macro_plugin(Arc::new(TestPlugin::default())) - .with_inline_macro_plugin(EmitMacro::NAME, Arc::new(EmitMacro)) - .with_inline_macro_plugin(GetMacro::NAME, Arc::new(GetMacro)) - .with_inline_macro_plugin(SetMacro::NAME, Arc::new(SetMacro)) - .with_inline_macro_plugin(SelectorMacro::NAME, Arc::new(SelectorMacro)) + .with_plugin_suite(dojo_plugin_suite()) + .with_plugin_suite(test_plugin_suite()) + .with_plugin_suite(starknet_plugin_suite()) .build() .unwrap_or_else(|error| { panic!("Problem creating language database: {error:?}"); diff --git a/crates/dojo-test-utils/build.rs b/crates/dojo-test-utils/build.rs index 64b0e8db1f..3f6b9592f8 100644 --- a/crates/dojo-test-utils/build.rs +++ b/crates/dojo-test-utils/build.rs @@ -5,9 +5,10 @@ fn main() { use camino::{Utf8Path, Utf8PathBuf}; use dojo_lang::compiler::DojoCompiler; use dojo_lang::plugin::CairoPluginRepository; + use dojo_lang::scarb_internal::compile_workspace; use scarb::compiler::CompilerRepository; use scarb::core::{Config, TargetKind}; - use scarb::ops::{self, CompileOpts}; + use scarb::ops::CompileOpts; use scarb_ui::Verbosity; let project_paths = ["../../examples/spawn-and-move", "../torii/graphql/src/tests/types-test"]; @@ -39,12 +40,9 @@ fn main() { .build() .unwrap(); - let ws = ops::read_workspace(config.manifest_path(), &config).unwrap(); - let packages = ws.members().map(|p| p.id).collect(); - ops::compile( - packages, + compile_workspace( + &config, CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - &ws, ) .unwrap(); } diff --git a/crates/dojo-world/Cargo.toml b/crates/dojo-world/Cargo.toml index ef0cc482e8..d6da3ff3b0 100644 --- a/crates/dojo-world/Cargo.toml +++ b/crates/dojo-world/Cargo.toml @@ -27,7 +27,7 @@ tracing.workspace = true dojo-types = { path = "../dojo-types", optional = true } http = { version = "0.2.9", optional = true } ipfs-api-backend-hyper = { git = "https://github.com/ferristseng/rust-ipfs-api", rev = "af2c17f7b19ef5b9898f458d97a90055c3605633", features = [ "with-hyper-rustls" ], optional = true } -scarb = { git = "https://github.com/software-mansion/scarb", rev = "0c8def3", optional = true } +scarb = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0", optional = true } tokio = { version = "1.32.0", features = [ "time" ], default-features = false, optional = true } url = { version = "2.2.2", optional = true } diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs index 887310faf0..cd235080da 100644 --- a/crates/dojo-world/src/contracts/model_test.rs +++ b/crates/dojo-world/src/contracts/model_test.rs @@ -63,7 +63,7 @@ async fn test_model() { assert_eq!( position.class_hash(), FieldElement::from_hex_be( - "0x052a1da1853c194683ca5d6d154452d0654d23f2eacd4267c555ff2338e144d6" + "0x02b233bba9a232a5e891c85eca9f67beedca7a12f9768729ff017bcb62d25c9d" ) .unwrap() ); diff --git a/crates/katana/core/src/backend/config.rs b/crates/katana/core/src/backend/config.rs index 78d32b31ad..4513f2cbed 100644 --- a/crates/katana/core/src/backend/config.rs +++ b/crates/katana/core/src/backend/config.rs @@ -1,4 +1,4 @@ -use blockifier::block_context::BlockContext; +use blockifier::block_context::{BlockContext, FeeTokenAddresses, GasPrices}; use starknet_api::block::{BlockNumber, BlockTimestamp}; use starknet_api::core::ChainId; use url::Url; @@ -26,9 +26,19 @@ impl StarknetConfig { chain_id: ChainId(self.env.chain_id.clone()), block_timestamp: BlockTimestamp::default(), sequencer_address: (*SEQUENCER_ADDRESS).into(), - fee_token_address: (*FEE_TOKEN_ADDRESS).into(), + // As the fee has two currencies, we also have to adjust their addresses. + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L34 + fee_token_addresses: FeeTokenAddresses { + eth_fee_token_address: (*FEE_TOKEN_ADDRESS).into(), + strk_fee_token_address: Default::default(), + }, vm_resource_fee_cost: get_default_vm_resource_fee_cost().into(), - gas_price: self.env.gas_price, + // Gas prices are dual too. + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L49 + gas_prices: GasPrices { + eth_l1_gas_price: self.env.gas_price, + strk_l1_gas_price: Default::default(), + }, validate_max_n_steps: self.env.validate_max_steps, invoke_tx_max_n_steps: self.env.invoke_max_steps, max_recursion_depth: 1000, diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 3dcf35c6c0..32343541e0 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -149,7 +149,7 @@ impl Backend { let partial_header = PartialHeader { parent_hash: prev_hash, - gas_price: block_context.gas_price, + gas_price: block_context.gas_prices.eth_l1_gas_price, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 545e06a453..49b59c8b3a 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -66,8 +66,9 @@ impl Blockchain { pub fn new_with_genesis(provider: impl Database, block_context: &BlockContext) -> Result { let header = PartialHeader { + // TODO: need to be adjusted, eth is used for compatibility for now. parent_hash: 0u8.into(), - gas_price: block_context.gas_price, + gas_price: block_context.gas_prices.eth_l1_gas_price, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: *SEQUENCER_ADDRESS, @@ -94,7 +95,7 @@ impl Blockchain { let header = Header { state_root, parent_hash, - gas_price: block_context.gas_price, + gas_price: block_context.gas_prices.eth_l1_gas_price, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: *SEQUENCER_ADDRESS, @@ -124,7 +125,7 @@ impl Blockchain { #[cfg(test)] mod tests { - use blockifier::block_context::BlockContext; + use blockifier::block_context::{BlockContext, FeeTokenAddresses, GasPrices}; use katana_primitives::block::FinalityStatus; use katana_primitives::FieldElement; use katana_provider::providers::in_memory::InMemoryProvider; @@ -145,7 +146,7 @@ mod tests { fn blockchain_from_genesis_states() { let provider = InMemoryProvider::new(); let block_context = BlockContext { - gas_price: 0, + gas_prices: GasPrices { eth_l1_gas_price: 0, strk_l1_gas_price: 0 }, max_recursion_depth: 0, validate_max_n_steps: 0, invoke_tx_max_n_steps: 0, @@ -153,7 +154,10 @@ mod tests { chain_id: ChainId("test".into()), block_timestamp: BlockTimestamp(0), sequencer_address: Default::default(), - fee_token_address: Default::default(), + fee_token_addresses: FeeTokenAddresses { + eth_fee_token_address: Default::default(), + strk_fee_token_address: Default::default(), + }, vm_resource_fee_cost: Default::default(), }; @@ -176,7 +180,7 @@ mod tests { let provider = InMemoryProvider::new(); let block_context = BlockContext { - gas_price: 9090, + gas_prices: GasPrices { eth_l1_gas_price: 9090, strk_l1_gas_price: 0 }, max_recursion_depth: 0, validate_max_n_steps: 0, invoke_tx_max_n_steps: 0, @@ -184,7 +188,10 @@ mod tests { block_number: BlockNumber(23), block_timestamp: BlockTimestamp(6868), sequencer_address: Default::default(), - fee_token_address: Default::default(), + fee_token_addresses: FeeTokenAddresses { + eth_fee_token_address: Default::default(), + strk_fee_token_address: Default::default(), + }, vm_resource_fee_cost: Default::default(), }; diff --git a/crates/katana/core/src/env.rs b/crates/katana/core/src/env.rs index 951e65d2d9..464b317831 100644 --- a/crates/katana/core/src/env.rs +++ b/crates/katana/core/src/env.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use blockifier::block_context::BlockContext; +use blockifier::block_context::{BlockContext, FeeTokenAddresses, GasPrices}; use cairo_vm::vm::runners::builtin_runner::{ BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, KECCAK_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, @@ -33,9 +33,15 @@ impl Default for Env { block_number: BlockNumber::default(), block_timestamp: BlockTimestamp::default(), sequencer_address: (*SEQUENCER_ADDRESS).into(), - fee_token_address: (*FEE_TOKEN_ADDRESS).into(), + fee_token_addresses: FeeTokenAddresses { + eth_fee_token_address: (*FEE_TOKEN_ADDRESS).into(), + strk_fee_token_address: Default::default(), + }, vm_resource_fee_cost: get_default_vm_resource_fee_cost().into(), - gas_price: DEFAULT_GAS_PRICE, + gas_prices: GasPrices { + eth_l1_gas_price: DEFAULT_GAS_PRICE, + strk_l1_gas_price: Default::default(), + }, invoke_tx_max_n_steps: 1_000_000, validate_max_n_steps: 1_000_000, max_recursion_depth: 100, diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 53d688d20f..b5b648cc0c 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::Result; use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError}; +use blockifier::transaction::errors::TransactionExecutionError; use katana_executor::blockifier::state::StateRefDb; use katana_executor::blockifier::utils::EntryPointCall; use katana_executor::blockifier::PendingState; @@ -268,11 +269,13 @@ impl KatanaSequencer { let retdata = katana_executor::blockifier::utils::call(request, block_context, state) .map_err(|e| match e { - EntryPointExecutionError::PreExecutionError( - PreExecutionError::UninitializedStorageAddress(addr), - ) => SequencerError::ContractNotFound(addr.into()), - - _ => SequencerError::EntryPointExecution(e), + TransactionExecutionError::ExecutionError(exe) => match exe { + EntryPointExecutionError::PreExecutionError( + PreExecutionError::UninitializedStorageAddress(addr), + ) => SequencerError::ContractNotFound(addr.into()), + _ => SequencerError::EntryPointExecution(exe), + }, + _ => SequencerError::TransactionExecution(e), })?; Ok(retdata) diff --git a/crates/katana/executor/src/blockifier/mod.rs b/crates/katana/executor/src/blockifier/mod.rs index 0dc61e0aaf..124a74197c 100644 --- a/crates/katana/executor/src/blockifier/mod.rs +++ b/crates/katana/executor/src/blockifier/mod.rs @@ -143,6 +143,9 @@ fn execute_tx( block_context: &BlockContext, charge_fee: bool, ) -> TxExecutionResult { + // TODO: check how this value must be controlled. + let validate = true; + let sierra = if let ExecutableTx::Declare(DeclareTxWithClass { transaction, sierra_class: Some(sierra_class), @@ -156,10 +159,10 @@ fn execute_tx( let res = match BlockifierTx::from(tx).0 { Transaction::AccountTransaction(tx) => { - tx.execute(&mut state.inner(), block_context, charge_fee) + tx.execute(&mut state.inner(), block_context, charge_fee, validate) } Transaction::L1HandlerTransaction(tx) => { - tx.execute(&mut state.inner(), block_context, charge_fee) + tx.execute(&mut state.inner(), block_context, charge_fee, validate) } }; diff --git a/crates/katana/executor/src/blockifier/state.rs b/crates/katana/executor/src/blockifier/state.rs index efba599a7f..1037b3a7f6 100644 --- a/crates/katana/executor/src/blockifier/state.rs +++ b/crates/katana/executor/src/blockifier/state.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use blockifier::state::cached_state::CachedState; +use blockifier::state::cached_state::{CachedState, GlobalContractCache}; use blockifier::state::errors::StateError; use blockifier::state::state_api::StateReader; use katana_primitives::contract::SierraClass; @@ -99,11 +99,14 @@ pub struct CachedStateWrapper { impl CachedStateWrapper { pub fn new(db: S) -> Self { - Self { sierra_class: Default::default(), inner: Mutex::new(CachedState::new(db)) } + Self { + sierra_class: Default::default(), + inner: Mutex::new(CachedState::new(db, GlobalContractCache::default())), + } } pub fn reset_with_new_state(&self, db: S) { - *self.inner() = CachedState::new(db); + *self.inner() = CachedState::new(db, GlobalContractCache::default()); self.sierra_class_mut().clear(); } diff --git a/crates/katana/executor/src/blockifier/transactions.rs b/crates/katana/executor/src/blockifier/transactions.rs index 70f4b0c37d..04722e3505 100644 --- a/crates/katana/executor/src/blockifier/transactions.rs +++ b/crates/katana/executor/src/blockifier/transactions.rs @@ -1,14 +1,16 @@ use std::sync::Arc; use ::blockifier::transaction::transaction_execution::Transaction; -use ::blockifier::transaction::transactions::DeployAccountTransaction; +use ::blockifier::transaction::transactions::{DeployAccountTransaction, InvokeTransaction}; use blockifier::transaction::account_transaction::AccountTransaction; use blockifier::transaction::transactions::{DeclareTransaction, L1HandlerTransaction}; use katana_primitives::transaction::{DeclareTx, ExecutableTx, ExecutableTxWithHash}; use starknet_api::core::{ClassHash, CompiledClassHash, EntryPointSelector, Nonce}; use starknet_api::transaction::{ - Calldata, ContractAddressSalt, DeclareTransactionV0V1, DeclareTransactionV2, Fee, - InvokeTransaction, InvokeTransactionV1, TransactionHash, TransactionSignature, + Calldata, ContractAddressSalt, DeclareTransaction as ApiDeclareTransaction, + DeclareTransactionV0V1, DeclareTransactionV2, + DeployAccountTransaction as ApiDeployAccountTransaction, DeployAccountTransactionV1, Fee, + InvokeTransaction as ApiInvokeTransaction, TransactionHash, TransactionSignature, TransactionVersion, }; @@ -23,16 +25,17 @@ impl From for BlockifierTx { ExecutableTx::Invoke(tx) => { let calldata = tx.calldata.into_iter().map(|f| f.into()).collect(); let signature = tx.signature.into_iter().map(|f| f.into()).collect(); - Transaction::AccountTransaction(AccountTransaction::Invoke(InvokeTransaction::V1( - InvokeTransactionV1 { + Transaction::AccountTransaction(AccountTransaction::Invoke(InvokeTransaction { + tx: ApiInvokeTransaction::V1(starknet_api::transaction::InvokeTransactionV1 { max_fee: Fee(tx.max_fee), nonce: Nonce(tx.nonce.into()), sender_address: tx.sender_address.into(), signature: TransactionSignature(signature), calldata: Calldata(Arc::new(calldata)), - transaction_hash: TransactionHash(hash.into()), - }, - ))) + }), + tx_hash: TransactionHash(hash.into()), + only_query: false, + })) } ExecutableTx::DeployAccount(tx) => { @@ -41,18 +44,18 @@ impl From for BlockifierTx { Transaction::AccountTransaction(AccountTransaction::DeployAccount( DeployAccountTransaction { contract_address: tx.contract_address.into(), - tx: starknet_api::transaction::DeployAccountTransaction { + tx: ApiDeployAccountTransaction::V1(DeployAccountTransactionV1 { max_fee: Fee(tx.max_fee), nonce: Nonce(tx.nonce.into()), - version: TransactionVersion(1u128.into()), signature: TransactionSignature(signature), class_hash: ClassHash(tx.class_hash.into()), - transaction_hash: TransactionHash(hash.into()), constructor_calldata: Calldata(Arc::new(calldata)), contract_address_salt: ContractAddressSalt( tx.contract_address_salt.into(), ), - }, + }), + tx_hash: TransactionHash(hash.into()), + only_query: false, }, )) } @@ -63,31 +66,30 @@ impl From for BlockifierTx { let tx = match tx.transaction { DeclareTx::V1(tx) => { let signature = tx.signature.into_iter().map(|f| f.into()).collect(); - starknet_api::transaction::DeclareTransaction::V1(DeclareTransactionV0V1 { + ApiDeclareTransaction::V1(DeclareTransactionV0V1 { max_fee: Fee(tx.max_fee), nonce: Nonce(tx.nonce.into()), sender_address: tx.sender_address.into(), signature: TransactionSignature(signature), class_hash: ClassHash(tx.class_hash.into()), - transaction_hash: TransactionHash(hash.into()), }) } DeclareTx::V2(tx) => { let signature = tx.signature.into_iter().map(|f| f.into()).collect(); - starknet_api::transaction::DeclareTransaction::V2(DeclareTransactionV2 { + ApiDeclareTransaction::V2(DeclareTransactionV2 { max_fee: Fee(tx.max_fee), nonce: Nonce(tx.nonce.into()), sender_address: tx.sender_address.into(), signature: TransactionSignature(signature), class_hash: ClassHash(tx.class_hash.into()), - transaction_hash: TransactionHash(hash.into()), compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.into()), }) } }; - let tx = DeclareTransaction::new(tx, contract_class).expect("class mismatch"); + let tx = DeclareTransaction::new(tx, TransactionHash(hash.into()), contract_class) + .expect("class mismatch"); Transaction::AccountTransaction(AccountTransaction::Declare(tx)) } @@ -100,9 +102,9 @@ impl From for BlockifierTx { calldata: Calldata(Arc::new(calldata)), version: TransactionVersion(1u128.into()), contract_address: tx.contract_address.into(), - transaction_hash: TransactionHash(hash.into()), entry_point_selector: EntryPointSelector(tx.entry_point_selector.into()), }, + tx_hash: TransactionHash(hash.into()), }) } }; diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs index cf8edbffb8..f0607992c9 100644 --- a/crates/katana/executor/src/blockifier/utils.rs +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -2,16 +2,20 @@ use std::collections::HashMap; use std::sync::Arc; use ::blockifier::block_context::BlockContext; +use ::blockifier::execution::call_info::CallInfo; +use ::blockifier::execution::common_hints::ExecutionMode; use ::blockifier::execution::entry_point::{ - CallEntryPoint, CallInfo, EntryPointExecutionContext, ExecutionResources, + CallEntryPoint, EntryPointExecutionContext, ExecutionResources, }; use ::blockifier::execution::errors::EntryPointExecutionError; -use ::blockifier::state::cached_state::{CachedState, MutRefState}; +use ::blockifier::state::cached_state::{CachedState, GlobalContractCache, MutRefState}; use ::blockifier::transaction::objects::AccountTransactionContext; use blockifier::fee::fee_utils::{calculate_l1_gas_by_vm_usage, extract_l1_gas_and_vm_usage}; use blockifier::state::state_api::State; use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; +use blockifier::transaction::objects::{ + DeprecatedAccountTransactionContext, ResourcesMapping, TransactionExecutionInfo, +}; use convert_case::{Case, Casing}; use katana_primitives::contract::ContractAddress; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; @@ -43,8 +47,8 @@ pub fn call( request: EntryPointCall, block_context: BlockContext, state: Box, -) -> Result, EntryPointExecutionError> { - let res = raw_call(request, block_context, state, 1_000_000_000, 1_000_000_000)?; +) -> Result, TransactionExecutionError> { + let res = raw_call(request, block_context, state, 1_000_000_000)?; let retdata = res.execution.retdata.0; let retdata = retdata.into_iter().map(|f| f.into()).collect::>(); Ok(retdata) @@ -83,10 +87,9 @@ pub fn raw_call( block_context: BlockContext, state: Box, initial_gas: u64, - max_n_steps: usize, -) -> Result { - let mut state = CachedState::new(StateRefDb::from(state)); - let mut state = CachedState::new(MutRefState::new(&mut state)); +) -> Result { + let mut state = CachedState::new(StateRefDb::from(state), GlobalContractCache::default()); + let mut state = CachedState::new(MutRefState::new(&mut state), GlobalContractCache::default()); let call = CallEntryPoint { initial_gas, @@ -96,15 +99,28 @@ pub fn raw_call( ..Default::default() }; + // TODO: this must be false if fees are disabled I assume. + let limit_steps_by_resources = true; + + // Now, the max step is not given directly to this function. + // It's computed by a new function max_steps, and it tooks the values + // from teh block context itself instead of the input give. + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/execution/entry_point.rs#L165 + // The blockifier patch must be adjusted to modify this function to return + // the limit we have into the block context without min applied: + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/execution/entry_point.rs#L215 call.execute( &mut state, &mut ExecutionResources::default(), &mut EntryPointExecutionContext::new( - block_context, - AccountTransactionContext::default(), - max_n_steps, - ), + &block_context, + // TODO: the current does not have Default, let's use the old one for now. + &AccountTransactionContext::Deprecated(DeprecatedAccountTransactionContext::default()), + ExecutionMode::Execute, + limit_steps_by_resources, + )?, ) + .map_err(TransactionExecutionError::ExecutionError) } /// Calculate the fee of a transaction execution. @@ -117,7 +133,11 @@ pub fn calculate_execution_fee( let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; - let gas_price = block_context.gas_price as u64; + // Gas prices are now in two currencies: eth and strk. + // For now let's only consider eth to be compatible with V2. + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L19C26-L19C26 + // https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L49 + let gas_price = block_context.gas_prices.eth_l1_gas_price as u64; let gas_consumed = total_l1_gas_usage.ceil() as u64; let overall_fee = total_l1_gas_usage.ceil() as u64 * gas_price; @@ -126,8 +146,7 @@ pub fn calculate_execution_fee( pub(crate) fn warn_message_transaction_error_exec_error(err: &TransactionExecutionError) { match err { - TransactionExecutionError::EntryPointExecutionError(ref eperr) - | TransactionExecutionError::ExecutionError(ref eperr) => match eperr { + TransactionExecutionError::ExecutionError(ref eperr) => match eperr { EntryPointExecutionError::ExecutionFailed { error_data } => { let mut reasons: Vec = vec![]; error_data.iter().for_each(|felt| { diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index 7aac882eac..a10e6c56dd 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -122,7 +122,7 @@ impl StarknetApiServer for StarknetApi { let header = PartialHeader { parent_hash: latest_hash, - gas_price: block_context.gas_price, + gas_price: block_context.gas_prices.eth_l1_gas_price, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), @@ -191,7 +191,7 @@ impl StarknetApiServer for StarknetApi { let header = PartialHeader { parent_hash: latest_hash, - gas_price: block_context.gas_price, + gas_price: block_context.gas_prices.eth_l1_gas_price, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index 070f5b6c56..ed2b8238ee 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -231,7 +231,7 @@ mod test { fn default_block_context_from_args() { let args = KatanaArgs::parse_from(["katana"]); let block_context = args.starknet_config().block_context(); - assert_eq!(block_context.gas_price, DEFAULT_GAS_PRICE); + assert_eq!(block_context.gas_prices.eth_l1_gas_price, DEFAULT_GAS_PRICE); assert_eq!(block_context.chain_id.0, "KATANA".to_string()); assert_eq!(block_context.validate_max_n_steps, DEFAULT_VALIDATE_MAX_STEPS); assert_eq!(block_context.invoke_tx_max_n_steps, DEFAULT_INVOKE_MAX_STEPS); @@ -253,7 +253,7 @@ mod test { let block_context = args.starknet_config().block_context(); - assert_eq!(block_context.gas_price, 10); + assert_eq!(block_context.gas_prices.eth_l1_gas_price, 10); assert_eq!(block_context.chain_id.0, "SN_GOERLI".to_string()); assert_eq!(block_context.validate_max_n_steps, 100); assert_eq!(block_context.invoke_tx_max_n_steps, 200); diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index adf0975273..ef4fdd5526 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -20,4 +20,4 @@ thiserror.workspace = true [dependencies.libmdbx] git = "https://github.com/paradigmxyz/reth.git" package = "reth-libmdbx" -version = "=0.1.0-alpha.12" +version = "=0.1.0-alpha.13" diff --git a/crates/sozo/Cargo.toml b/crates/sozo/Cargo.toml index 24418dabef..d6fbb86796 100644 --- a/crates/sozo/Cargo.toml +++ b/crates/sozo/Cargo.toml @@ -18,6 +18,7 @@ cairo-lang-sierra.workspace = true cairo-lang-starknet.workspace = true cairo-lang-test-plugin.workspace = true cairo-lang-test-runner.workspace = true +cairo-lang-utils.workspace = true camino.workspace = true clap-verbosity-flag = "2.0.1" clap.workspace = true diff --git a/crates/sozo/src/commands/build.rs b/crates/sozo/src/commands/build.rs index 860978b49d..6c5695fd0f 100644 --- a/crates/sozo/src/commands/build.rs +++ b/crates/sozo/src/commands/build.rs @@ -1,19 +1,17 @@ use anyhow::Result; use clap::Args; +use dojo_lang::scarb_internal::compile_workspace; use scarb::core::{Config, TargetKind}; -use scarb::ops::{self, CompileOpts}; +use scarb::ops::CompileOpts; #[derive(Args, Debug)] pub struct BuildArgs; impl BuildArgs { pub fn run(self, config: &Config) -> Result<()> { - let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - let packages = ws.members().map(|p| p.id).collect(); - ops::compile( - packages, + compile_workspace( + config, CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - &ws, ) } } diff --git a/crates/sozo/src/commands/dev.rs b/crates/sozo/src/commands/dev.rs index 744bfffb99..b55a24b9af 100644 --- a/crates/sozo/src/commands/dev.rs +++ b/crates/sozo/src/commands/dev.rs @@ -8,6 +8,7 @@ use cairo_lang_compiler::db::RootDatabase; use cairo_lang_filesystem::db::{AsFilesGroupMut, FilesGroupEx, PrivRawFileContentQuery}; use cairo_lang_filesystem::ids::FileId; use clap::Args; +use dojo_lang::scarb_internal::build_scarb_root_database; use dojo_world::manifest::Manifest; use dojo_world::migration::world::WorldDiff; use notify_debouncer_mini::notify::RecursiveMode; @@ -23,7 +24,6 @@ use tracing_log::log; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; -use super::scarb_internal::build_scarb_root_database; use crate::ops::migration; #[derive(Args)] @@ -85,9 +85,11 @@ fn load_context(config: &Config) -> Result> { .into_iter() .filter(|cu| packages.contains(&cu.main_package_id)) .collect::>(); + // we have only 1 unit in projects + // TODO: double check if we always have one with the new version and the order if many. let unit = compilation_units.first().unwrap(); - let db = build_scarb_root_database(unit, &ws).unwrap(); + let db = build_scarb_root_database(unit).unwrap(); Ok(DevContext { db, unit: unit.clone(), ws }) } diff --git a/crates/sozo/src/commands/migrate.rs b/crates/sozo/src/commands/migrate.rs index a37e578fa2..7664d5bb8d 100644 --- a/crates/sozo/src/commands/migrate.rs +++ b/crates/sozo/src/commands/migrate.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::Args; +use dojo_lang::scarb_internal::compile_workspace; use scarb::core::{Config, TargetKind}; use scarb::ops::CompileOpts; @@ -49,11 +50,9 @@ impl MigrateArgs { let target_dir = target_dir.join(ws.config().profile().as_str()); if !target_dir.join("manifest.json").exists() { - let packages = ws.members().map(|p| p.id).collect(); - scarb::ops::compile( - packages, + compile_workspace( + config, CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - &ws, )?; } diff --git a/crates/sozo/src/commands/mod.rs b/crates/sozo/src/commands/mod.rs index e093ab8ab8..8281d5cc41 100644 --- a/crates/sozo/src/commands/mod.rs +++ b/crates/sozo/src/commands/mod.rs @@ -16,9 +16,6 @@ pub(crate) mod options; pub(crate) mod register; pub(crate) mod test; -// copy of non pub functions from scarb -pub(crate) mod scarb_internal; - pub fn run(command: Commands, config: &Config) -> Result<()> { match command { Commands::Init(args) => args.run(config), diff --git a/crates/sozo/src/commands/scarb_internal/mod.rs b/crates/sozo/src/commands/scarb_internal/mod.rs deleted file mode 100644 index a7f3379580..0000000000 --- a/crates/sozo/src/commands/scarb_internal/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -// I have copied source code from https://github.com/software-mansion/scarb/blob/main/scarb/src/compiler/db.rs -// since build_scarb_root_database is not public -// -// NOTE: This files needs to be updated whenever scarb version is updated -use anyhow::Result; -use cairo_lang_compiler::db::RootDatabase; -use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent}; -use cairo_lang_filesystem::ids::Directory; -use scarb::compiler::CompilationUnit; -use scarb::core::Workspace; -use tracing::trace; - -// TODO(mkaput): ScarbDatabase? -pub(crate) fn build_scarb_root_database( - unit: &CompilationUnit, - ws: &Workspace<'_>, -) -> Result { - let mut b = RootDatabase::builder(); - b.with_project_config(build_project_config(unit)?); - b.with_cfg(unit.cfg_set.clone()); - - for plugin_info in &unit.cairo_plugins { - let package_id = plugin_info.package.id; - let plugin = ws.config().cairo_plugins().fetch(package_id)?; - let instance = plugin.instantiate()?; - for macro_plugin in instance.macro_plugins() { - b.with_macro_plugin(macro_plugin); - } - for (name, inline_macro_plugin) in instance.inline_macro_plugins() { - b.with_inline_macro_plugin(&name, inline_macro_plugin); - } - } - - b.build() -} - -fn build_project_config(unit: &CompilationUnit) -> Result { - let crate_roots = unit - .components - .iter() - .filter(|model| !model.package.id.is_core()) - .map(|model| (model.cairo_package_name(), model.target.source_root().into())) - .collect(); - - let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into())); - - let content = ProjectConfigContent { crate_roots }; - - let project_config = - ProjectConfig { base_path: unit.main_component().package.root().into(), corelib, content }; - - trace!(?project_config); - - Ok(project_config) -} diff --git a/crates/sozo/src/commands/test.rs b/crates/sozo/src/commands/test.rs index ff73c9ab3a..b464a78e76 100644 --- a/crates/sozo/src/commands/test.rs +++ b/crates/sozo/src/commands/test.rs @@ -1,23 +1,17 @@ //! Compiles and runs tests for a Dojo project. - -use std::sync::Arc; - use anyhow::{bail, Result}; use cairo_lang_compiler::db::RootDatabase; use cairo_lang_compiler::diagnostics::DiagnosticsReporter; use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent}; use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; use cairo_lang_filesystem::ids::Directory; -use cairo_lang_starknet::inline_macros::selector::SelectorMacro; -use cairo_lang_starknet::plugin::StarkNetPlugin; -use cairo_lang_test_plugin::TestPlugin; +use cairo_lang_starknet::starknet_plugin_suite; +use cairo_lang_test_plugin::test_plugin_suite; use cairo_lang_test_runner::{CompiledTestRunner, TestCompiler, TestRunConfig}; use clap::Args; use dojo_lang::compiler::{collect_core_crate_ids, collect_external_crate_ids, Props}; -use dojo_lang::inline_macros::emit::EmitMacro; -use dojo_lang::inline_macros::get::GetMacro; -use dojo_lang::inline_macros::set::SetMacro; -use dojo_lang::plugin::BuiltinDojoPlugin; +use dojo_lang::plugin::dojo_plugin_suite; +use dojo_lang::scarb_internal::crates_config_for_compilation_unit; use scarb::compiler::helpers::collect_main_crate_ids; use scarb::compiler::CompilationUnit; use scarb::core::Config; @@ -94,14 +88,9 @@ pub(crate) fn build_root_database(unit: &CompilationUnit) -> Result Result { .collect(); let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into())); + let crates_config = crates_config_for_compilation_unit(unit); - let content = ProjectConfigContent { crate_roots }; + let content = ProjectConfigContent { crate_roots, crates_config }; let project_config = ProjectConfig { base_path: unit.main_component().package.root().into(), corelib, content }; diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs index fe9d700efb..7b1c04adcc 100644 --- a/crates/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/src/ops/migration/mod.rs @@ -52,7 +52,7 @@ where // Load local and remote World manifests. let (local_manifest, remote_manifest) = - load_world_manifests(&target_dir, &account, world_address, ui).await?; + load_world_manifests(&target_dir, &account, world_address, &ui).await?; // Calculate diff between local and remote World manifests. @@ -129,7 +129,7 @@ where S: Signer + Sync + Send + 'static, { let ui = ws.config().ui(); - let strategy = prepare_migration(target_dir, diff, name, world_address, ui)?; + let strategy = prepare_migration(target_dir, diff, name, world_address, &ui)?; println!(" "); @@ -288,7 +288,7 @@ where match &strategy.executor { Some(executor) => { ui.print_header("# Executor"); - deploy_contract(executor, "executor", vec![], migrator, ui, &txn_config).await?; + deploy_contract(executor, "executor", vec![], migrator, &ui, &txn_config).await?; // There is no world migration, so it exists already. if strategy.world.is_none() { @@ -336,7 +336,9 @@ where strategy.executor.as_ref().unwrap().contract_address, strategy.base.as_ref().unwrap().diff.local, ]; - deploy_contract(world, "world", calldata.clone(), migrator, ui, &txn_config).await?; + deploy_contract(world, "world", calldata.clone(), migrator, &ui, &txn_config) + .await + .map_err(|e| anyhow!("Failed to deploy world: {e}"))?; ui.print_sub(format!("Contract address: {:#x}", world.contract_address)); @@ -362,8 +364,8 @@ where None => {} }; - register_models(strategy, migrator, ui, txn_config.clone()).await?; - deploy_contracts(strategy, migrator, ui, txn_config).await?; + register_models(strategy, migrator, &ui, txn_config.clone()).await?; + deploy_contracts(strategy, migrator, &ui, txn_config).await?; // This gets current block numder if helpful // let block_height = migrator.provider().block_number().await.ok(); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 927faf3a0a..667a47ea17 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -252,7 +252,7 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); // Execute `create` and insert 10 records into storage - let records_contract = "0x2e6254aaf7e47502319f35de01376cece263f9b83afe6169a4b3a76ef47c8a3"; + let records_contract = "0x414e9776a1626963ba21c4f45ddd7b4d2ac907d20c230e9c6a2e6119c2e5d"; let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index 6a1758f1d7..e229ddf091 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -10,8 +10,7 @@ dependencies = [ [[package]] name = "dojo_plugin" -version = "0.3.11" -source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" +version = "0.4.0" [[package]] name = "types_test" diff --git a/examples/spawn-and-move/.tool-versions b/examples/spawn-and-move/.tool-versions new file mode 100644 index 0000000000..21cfc80772 --- /dev/null +++ b/examples/spawn-and-move/.tool-versions @@ -0,0 +1 @@ +scarb 2.4.0 diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 97759bbd25..0e594f379c 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.3.1" +cairo-version = "2.4.0" name = "dojo_examples" version = "0.4.0-rc0" From 078bd8400c8c904f626b2254c153167bb4868529 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Sat, 9 Dec 2023 14:05:56 -0500 Subject: [PATCH 109/192] Prepare v0.4.0-rc1 --- Cargo.lock | 42 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 318961f903..ac3197b40e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2544,7 +2544,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2592,7 +2592,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2614,7 +2614,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "starknet", @@ -2622,7 +2622,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "assert_fs", @@ -2653,7 +2653,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "crypto-bigint", "hex", @@ -2668,7 +2668,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "assert_fs", @@ -5248,7 +5248,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "assert_matches", "clap", @@ -5266,7 +5266,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "assert_matches", @@ -5300,7 +5300,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5315,7 +5315,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "blockifier", @@ -5331,7 +5331,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "blockifier", @@ -5348,7 +5348,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "auto_impl", @@ -5365,7 +5365,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "assert_matches", @@ -5397,7 +5397,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "derive_more", @@ -5410,7 +5410,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "katana-executor", @@ -8088,7 +8088,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "assert_fs", @@ -9272,7 +9272,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "async-trait", "camino", @@ -9298,7 +9298,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "async-trait", @@ -9334,7 +9334,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "async-graphql", @@ -9373,7 +9373,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "bytes", "crypto-bigint", @@ -9411,7 +9411,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f1b8d8955f..037ca5f196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.0-rc0" +version = "0.4.0-rc1" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 9e87daaffa..ea08cb38bc 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 8790f62c3d..a20f23a2e7 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.0-rc0" +version = "0.4.0-rc1" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index b6861d3661..b14f7dd921 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.3.1" name = "test_crate" -version = "0.4.0-rc0" +version = "0.4.0-rc1" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index b5b1412454..09cafa61e4 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "dojo_plugin", ] From 210a1375c0692d60ae4739bbcc7306f1b16375ed Mon Sep 17 00:00:00 2001 From: glihm Date: Sat, 9 Dec 2023 18:52:27 -0600 Subject: [PATCH 110/192] tests(dojo-lang): add compiler test for cairo v2.4.0 (#1249) * add new test for cairo v2.4.0 compatibility * fix: fmt --- crates/dojo-core/src/lib.cairo | 2 +- crates/dojo-lang/src/compiler_test.rs | 35 + .../src/manifest_test_data/cairo_v240 | 973 ++++++++++++++++++ .../compiler_cairo_v240/.gitignore | 1 + .../compiler_cairo_v240/Scarb.lock | 20 + .../compiler_cairo_v240/Scarb.toml | 23 + .../compiler_cairo_v240/src/lib.cairo | 57 + .../graphql/src/tests/types-test/Scarb.lock | 5 +- 8 files changed, 1113 insertions(+), 3 deletions(-) create mode 100644 crates/dojo-lang/src/manifest_test_data/cairo_v240 create mode 100644 crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/.gitignore create mode 100644 crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock create mode 100644 crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.toml create mode 100644 crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/src/lib.cairo diff --git a/crates/dojo-core/src/lib.cairo b/crates/dojo-core/src/lib.cairo index 8a8ba0918d..6f4abe6691 100644 --- a/crates/dojo-core/src/lib.cairo +++ b/crates/dojo-core/src/lib.cairo @@ -21,4 +21,4 @@ mod test_utils; #[cfg(test)] mod benchmarks; -mod components; \ No newline at end of file +mod components; diff --git a/crates/dojo-lang/src/compiler_test.rs b/crates/dojo-lang/src/compiler_test.rs index e124ac014d..97772627c2 100644 --- a/crates/dojo-lang/src/compiler_test.rs +++ b/crates/dojo-lang/src/compiler_test.rs @@ -196,3 +196,38 @@ pub fn test_manifest_file( generated_file, )])) } + +cairo_lang_test_utils::test_file_test!( + compiler_cairo_v240, + "src/manifest_test_data/", + { + cairo_v240: "cairo_v240", + }, + test_compiler_cairo_v240 +); + +pub fn test_compiler_cairo_v240( + _inputs: &OrderedHashMap, + _args: &OrderedHashMap, +) -> TestRunnerResult { + let config = + build_test_config("./src/manifest_test_data/compiler_cairo_v240/Scarb.toml").unwrap(); + + scarb_internal::compile_workspace( + &config, + CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, + ) + .unwrap_or_else(|err| panic!("Error compiling: {err:?}")); + + let target_dir = config.target_dir_override().unwrap(); + + let generated_manifest_path = + Path::new(target_dir).join(config.profile().as_str()).join("manifest.json"); + + let generated_file = fs::read_to_string(generated_manifest_path).unwrap(); + + TestRunnerResult::success(OrderedHashMap::from([( + "expected_manifest_file".into(), + generated_file, + )])) +} diff --git a/crates/dojo-lang/src/manifest_test_data/cairo_v240 b/crates/dojo-lang/src/manifest_test_data/cairo_v240 new file mode 100644 index 0000000000..8841c3876a --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/cairo_v240 @@ -0,0 +1,973 @@ +//! > Test for cairo v2.4.0 compatibility + +//! > test_runner_name +test_compiler_cairo_v240 + +//! > expected_manifest_file +{ + "world": { + "name": "world", + "address": null, + "class_hash": "0x5ac623f0c96059936bd2d0904bdd31799e430fe08a0caff7a5f497260b16497", + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::IWorld" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::felt252" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorld", + "items": [ + { + "type": "function", + "name": "metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "uri", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "model", + "inputs": [ + { + "name": "name", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "deploy_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit", + "inputs": [ + { + "name": "keys", + "type": "core::array::Array::" + }, + { + "name": "values", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "length", + "type": "core::integer::u32" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entities", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "index", + "type": "core::option::Option::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "values_length", + "type": "core::integer::u32" + }, + { + "name": "values_layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "(core::array::Span::, core::array::Span::>)" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity_ids", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_executor", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "executor", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "base", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "executor", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "contract_base", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractDeployed", + "kind": "struct", + "members": [ + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "uri", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "prev_class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "offset", + "type": "core::integer::u8", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "model", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ExecutorUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "ContractDeployed", + "type": "dojo::world::world::ContractDeployed", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world::OwnerUpdated", + "kind": "nested" + }, + { + "name": "ExecutorUpdated", + "type": "dojo::world::world::ExecutorUpdated", + "kind": "nested" + } + ] + } + ], + "reads": [], + "writes": [], + "computed": [] + }, + "executor": { + "name": "executor", + "address": null, + "class_hash": "0x585507fa2818fe78e66da6ea4c5915376739f4abf509d41153f60a16cb1f68d", + "abi": [ + { + "type": "impl", + "name": "Executor", + "interface_name": "dojo::executor::IExecutor" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "interface", + "name": "dojo::executor::IExecutor", + "items": [ + { + "type": "function", + "name": "call", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + }, + { + "name": "entrypoint", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "dojo::executor::executor::Event", + "kind": "enum", + "variants": [] + } + ], + "reads": [], + "writes": [], + "computed": [] + }, + "base": { + "name": "base", + "class_hash": "0x6c458453d35753703ad25632deec20a29faf8531942ec109e6eb0650316a2bc", + "abi": [ + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::base::base::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + } + ] + } + ] + }, + "contracts": [ + { + "name": "cairo_v240", + "address": null, + "class_hash": "0x714353d2a54cfd74820f89c19d8911a379214d5d4ccf369d74b5f5844dc565f", + "abi": [ + { + "type": "event", + "name": "cairo_v240::cairo_v240::Event", + "kind": "enum", + "variants": [] + } + ], + "reads": [], + "writes": [], + "computed": [] + } + ], + "models": [] +} diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/.gitignore b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/.gitignore @@ -0,0 +1 @@ +target diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock new file mode 100644 index 0000000000..c4e6511205 --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock @@ -0,0 +1,20 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cairo_v240" +version = "0.1.0" +dependencies = [ + "dojo", +] + +[[package]] +name = "dojo" +version = "0.4.0-rc0" +dependencies = [ + "dojo_plugin", +] + +[[package]] +name = "dojo_plugin" +version = "0.4.0" diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.toml new file mode 100644 index 0000000000..d04b4e8e47 --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.toml @@ -0,0 +1,23 @@ +[package] +name = "cairo_v240" +version = "0.1.0" +edition = "2023_10" +cairo-version = "2.4.0" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[cairo] +sierra-replace-ids = true + +[dependencies] +dojo = { path = "../../../../dojo-core" } + +[[target.dojo]] +build-external-contracts = [ ] + +[tool.dojo.world] +description = "Cairo 240 features" +name = "compiler_cairo_240" + +[tool.dojo.env] +rpc_url = "http://localhost:5050/" diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/src/lib.cairo b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/src/lib.cairo new file mode 100644 index 0000000000..e3a0cb13f3 --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/src/lib.cairo @@ -0,0 +1,57 @@ +//! Cairo 2.4.0 feature testing. +#[starknet::contract] +mod cairo_v240 { + use core::fmt::Formatter; + + #[storage] + struct Storage {} + + fn byte_array(self: @ContractState) -> ByteArray { + let mut ba: ByteArray = ""; + ba.append_word('ABCDEFGHIJKLMNOPQRSTUVWXYZ12345', 31); + ba.append_byte(0x65); + + let mut bc: ByteArray = ""; + bc.append(@ba); + + bc + } + + fn formatter(self: @ContractState) { + let var = 5; + let mut formatter: Formatter = Default::default(); + write!(formatter, "test"); + write!(formatter, "{var:?}"); + println!("{}", formatter.buffer); //prints test5 + } + + fn format(self: @ContractState) { + let var1 = 5; + let var2: ByteArray = "hello"; + let var3 = 5_u32; + let ba = format!("{},{},{}", var1, var2, var3); + let ba = format!("{var1}{var2}{var3}"); + let ba = format!("{var1:?}{var2:?}{var3:?}"); + } + + fn long_panic(self: @ContractState) { + panic!("this should not be reached, but at least I'm not limited to 31 characters anymore") + } + + #[derive(Drop, Debug, PartialEq)] + struct MyStruct { + a: u8, + b: u8 + } + + fn asserts(self: @ContractState) { + let var1 = 5; + let var2 = 6; + assert!(var1 != var2, "should not be equal"); + assert!(var1 != var2, "{},{} should not be equal", var1, var2); + + let first = MyStruct { a: 1, b: 2 }; + let second = MyStruct { a: 1, b: 2 }; + assert_eq!(first, second, "{:?},{:?} should be equal", first, second); + } +} diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/graphql/src/tests/types-test/Scarb.lock index e229ddf091..a61afd0460 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/graphql/src/tests/types-test/Scarb.lock @@ -3,14 +3,15 @@ version = 1 [[package]] name = "dojo" -version = "0.3.15" +version = "0.4.0-rc0" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.4.0" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" [[package]] name = "types_test" From a08f2cbd7ea7bacc5e40f83eff0e69b3183501ca Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Sat, 9 Dec 2023 20:32:27 -0500 Subject: [PATCH 111/192] Dojoup in devcontainer --- .devcontainer/Dockerfile | 30 +++++++--------- .devcontainer/devcontainer.json | 2 +- .github/workflows/devcontainer.yml | 16 ++++----- README.md | 58 +----------------------------- 4 files changed, 21 insertions(+), 85 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b82f63fde9..34193ffd83 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust/.devcontainer/base.Dockerfile -# [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye +# [Choice] Debian OS version (use bookworm on local arm64/Apple Silicon): buster, bullseye, bookworm ARG VARIANT FROM mcr.microsoft.com/vscode/devcontainers/rust:${VARIANT} @@ -10,27 +10,21 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ RUN apt install -y libgmp3-dev -RUN rustup toolchain install 1.70.0 && \ - rustup default 1.70.0 && rustup component add clippy && rustup component add rustfmt +COPY rust-toolchain.toml . -# Install Python -ARG PYTHON_PATH=/usr/local/python -ENV PIPX_HOME=/usr/local/py-utils \ - PIPX_BIN_DIR=/usr/local/py-utils/bin -ENV PATH=${PYTHON_PATH}/bin:${PATH}:${PIPX_BIN_DIR} -COPY .devcontainer/library-scripts/python-debian.sh /tmp/library-scripts/ -RUN apt-get update && bash /tmp/library-scripts/python-debian.sh "3.9.6" "${PYTHON_PATH}" "${PIPX_HOME}" +# Install cargo-binstall +RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash -# Install nodejs -ENV NVM_DIR="/usr/local/share/nvm" -ENV NVM_SYMLINK_CURRENT=true \ - PATH=${NVM_DIR}/current/bin:${PATH} -COPY .devcontainer/library-scripts/node-debian.sh /tmp/library-scripts/ -RUN apt-get update && bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" +RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" -f2) && \ + rustup default $(cat rust-toolchain.toml | grep channel | cut -d\" -f2) && \ + rustup component add clippy && \ + rustup component add rustfmt + +RUN cargo binstall cargo-nextest --secure -y # Install dojoup and scarb for vscode user USER vscode RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash - -ENV PATH=${PATH}:/workspaces/dojo/target/release +ENV PATH=${PATH}:/workspaces/dojo/target/release:/home/vscode/.dojo/bin +RUN /home/vscode/.dojo/bin/dojoup diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d07a3d1460..d5e2cd4424 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:72f7bc2220f0438e435052f807e58c09b7b61cbe", + "image": "ghcr.io/dojoengine/dojo-dev:affacdd", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index acd7b3f6ca..8e68287856 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -9,7 +9,7 @@ on: jobs: build-and-push: - runs-on: ubuntu-latest + runs-on: ubuntu-latest-4-cores env: DOCKER_TAG: latest @@ -42,7 +42,7 @@ jobs: run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c 1-7) echo "DOCKER_TAG=$SHORT_SHA" >> $GITHUB_ENV - + - name: Set outputs id: release_info run: | @@ -55,10 +55,9 @@ jobs: file: .devcontainer/Dockerfile tags: ghcr.io/${{ github.repository }}-dev:latest,ghcr.io/${{ github.repository }}-dev:${{ env.DOCKER_TAG }} build-args: | - VARIANT=bullseye + VARIANT=bookworm platforms: linux/amd64,linux/arm64 cache-from: type=registry,ref=ghcr.io/${{ github.repository }}-dev:latest - cache-to: type=registry,ref=ghcr.io/${{ github.repository }}-dev:cache propose-update-pr: needs: build-and-push @@ -70,8 +69,7 @@ jobs: uses: actions/checkout@v2 - name: Update devcontainer.json - run: | - sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json + run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - name: Setup Git credentials run: | @@ -83,14 +81,14 @@ jobs: git add .devcontainer/devcontainer.json git commit -m "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" git checkout -b devcontainer-${{ needs.build-and-push.outputs.tag_name }} - git push --set-upstream origin devcontainer--${{ needs.build-and-push.outputs.tag_name }} + git push --set-upstream origin devcontainer-${{ needs.build-and-push.outputs.tag_name }} - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: token: ${{ secrets.GITHUB_TOKEN }} title: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" - commit-message: "Update devcontainer image hash" - branch: update-devcontainer-image-hash + commit-message: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" + branch: devcontainer-${{ needs.build-and-push.outputs.tag_name }} base: main delete-branch: true diff --git a/README.md b/README.md index 849598b475..2fdde944ff 100644 --- a/README.md +++ b/README.md @@ -67,60 +67,4 @@ We welcome contributions of all kinds from anyone. See our [Contribution Guide]( ## ✏️ Enviroment -See our [Enviroment setup](https://book.dojoengine.org/getting-started/setup.html) for more information. - -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Tarrence van As
Tarrence van As

💻
Mathieu
Mathieu

💻
Shramee Srivastav
Shramee Srivastav

💻
omahs
omahs

💻
Larko
Larko

💻
Loaf
Loaf

💻
Milan Cermak
Milan Cermak

💻
drspacemn
drspacemn

💻
greged93
greged93

💻
Junichi Sugiura
Junichi Sugiura

💻
Thomas Belloc
Thomas Belloc

💻
Yun
Yun

💻
Ammar Arif
Ammar Arif

💻
ftupas
ftupas

💻
whatthedev.eth
whatthedev.eth

💻
raschel
raschel

💻
sparqet
sparqet

💻
Pia
Pia

💻
bing
bing

💻
ptisserand
ptisserand

💻
glihm
glihm

💻
Caspar Oostendorp
Caspar Oostendorp

💻
Jonathan LEI
Jonathan LEI

💻
Paweł
Paweł

💻
lambda-0x
lambda-0x

💻
Harsh Bajpai
Harsh Bajpai

💻
johann bestowrous
johann bestowrous

💻
- - - - - - -This project follows the -[all-contributors](https://github.com/all-contributors/all-contributors) -specification. Contributions of any kind welcome! +See our [Enviroment setup](https://book.dojoengine.org/getting-started/setup.html) for more information. \ No newline at end of file From 3ce6d2699127e9a0b39f8e1653382e3b66495098 Mon Sep 17 00:00:00 2001 From: Neo <128649481+neotheprogramist@users.noreply.github.com> Date: Sun, 10 Dec 2023 14:01:43 +0100 Subject: [PATCH 112/192] Implement Katana Runner in Dojo (#1205) --- .devcontainer/devcontainer.json | 7 +- .github/workflows/ci.yml | 14 +-- Cargo.lock | 76 ++++++++---- Cargo.toml | 1 + crates/benches/contracts/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- crates/katana/core/src/backend/config.rs | 4 +- crates/katana/runner/.gitignore | 1 + crates/katana/runner/Cargo.toml | 14 +++ crates/katana/runner/src/lib.rs | 108 ++++++++++++++++++ .../graphql/src/tests/types-test/Scarb.toml | 2 +- 11 files changed, 190 insertions(+), 41 deletions(-) create mode 100644 crates/katana/runner/.gitignore create mode 100644 crates/katana/runner/Cargo.toml create mode 100644 crates/katana/runner/src/lib.rs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d5e2cd4424..c453c291e3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:affacdd", + "image": "ghcr.io/dojoengine/dojo-dev:a08f2cb", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", @@ -39,6 +39,7 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "vscode", "remoteEnv": { - "PATH": "${containerEnv:PATH}:/workspace/dojo/target/release" - } + "PATH": "${containerEnv:PATH}:/workspace/dojo/target/release:/home/vscode/.dojo/bin" + }, + "postCreateCommand": "dojoup/dojoup && cargo install cargo-nextest" } \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f0c610c66..d076ef8318 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,21 +13,15 @@ env: jobs: test: runs-on: ubuntu-latest-4-cores + container: + image: ghcr.io/dojoengine/dojo-dev:a08f2cb steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install nextest test runner - uses: taiki-e/install-action@nextest - - run: | - cargo nextest run --all-features + - run: dojoup + - run: cargo nextest run --all-features ensure-wasm: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index ac3197b40e..49d428564e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ dependencies = [ "futures-lite 2.1.0", "parking", "polling 3.3.1", - "rustix 0.38.27", + "rustix 0.38.28", "slab", "tracing", "windows-sys 0.52.0", @@ -3352,7 +3352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ "async-trait", - "rustix 0.38.27", + "rustix 0.38.28", "tokio", "windows-sys 0.48.0", ] @@ -3950,7 +3950,7 @@ dependencies = [ "itoa", "libc", "memmap2", - "rustix 0.38.27", + "rustix 0.38.28", "smallvec", "thiserror", ] @@ -4113,7 +4113,7 @@ dependencies = [ "gix-command", "gix-config-value", "parking_lot 0.12.1", - "rustix 0.38.27", + "rustix 0.38.28", "thiserror", ] @@ -5065,7 +5065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.27", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -5420,6 +5420,18 @@ dependencies = [ "starknet", ] +[[package]] +name = "katana-runner" +version = "0.1.0" +dependencies = [ + "anyhow", + "home", + "lazy_static", + "starknet", + "tokio", + "url", +] + [[package]] name = "keccak" version = "0.1.4" @@ -6615,7 +6627,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.27", + "rustix 0.38.28", "tracing", "windows-sys 0.52.0", ] @@ -7136,7 +7148,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#cf5006108c5ed2b774f09fbf02a694ec67e95ba0" +source = "git+https://github.com/paradigmxyz/reth.git#8a670d57ec1c8f3cdf617e09f939d7d2624b6ded" dependencies = [ "bitflags 2.4.1", "byteorder", @@ -7151,7 +7163,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#cf5006108c5ed2b774f09fbf02a694ec67e95ba0" +source = "git+https://github.com/paradigmxyz/reth.git#8a670d57ec1c8f3cdf617e09f939d7d2624b6ded" dependencies = [ "bindgen", "cc", @@ -7291,9 +7303,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.27" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfeae074e687625746172d639330f1de242a178bf3189b51e35a7a21573513ac" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", @@ -8395,7 +8407,7 @@ checksum = "351ffff1bcf6a1dc569a1b330dfd85779e16506e7d4a87baa8be3744cb5415a6" dependencies = [ "starknet-accounts", "starknet-contract", - "starknet-core", + "starknet-core 0.7.2", "starknet-crypto 0.6.1", "starknet-ff", "starknet-macros", @@ -8411,7 +8423,7 @@ checksum = "c7062b020f65d9da7f9dd9f1d97bfb644e881cda8ddb999799a799e6f2e408dd" dependencies = [ "async-trait", "auto_impl", - "starknet-core", + "starknet-core 0.7.2", "starknet-providers", "starknet-signers", "thiserror", @@ -8427,7 +8439,7 @@ dependencies = [ "serde_json", "serde_with", "starknet-accounts", - "starknet-core", + "starknet-core 0.7.2", "starknet-providers", "thiserror", ] @@ -8450,6 +8462,24 @@ dependencies = [ "starknet-ff", ] +[[package]] +name = "starknet-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50e281d4fdb97988a3d5c7b7cae22b9d67bb2ef9be2cfafc17a1e35542cb53" +dependencies = [ + "base64 0.21.5", + "flate2", + "hex", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with", + "sha3", + "starknet-crypto 0.6.1", + "starknet-ff", +] + [[package]] name = "starknet-crypto" version = "0.5.2" @@ -8536,11 +8566,11 @@ dependencies = [ [[package]] name = "starknet-macros" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66fe05edab7ee6752a0aff3e14508001191083f3c6d0b6fa14f7008a96839b0" +checksum = "840be1a7eb5735863eee47d3a3f26df45b9be2c519e8da294e74b4d0524d77d1" dependencies = [ - "starknet-core", + "starknet-core 0.8.0", "syn 2.0.39", ] @@ -8559,7 +8589,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "starknet-core", + "starknet-core 0.7.2", "thiserror", "url", ] @@ -8575,7 +8605,7 @@ dependencies = [ "crypto-bigint", "eth-keystore", "rand", - "starknet-core", + "starknet-core 0.7.2", "starknet-crypto 0.6.1", "thiserror", ] @@ -8790,7 +8820,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.27", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -10072,7 +10102,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.27", + "rustix 0.38.28", ] [[package]] @@ -10084,7 +10114,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.27", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -10390,9 +10420,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "fbc6ab6ec1907d1a901cdbcd2bd4cb9e7d64ce5c9739cbb97d3c391acd8c7fae" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 037ca5f196..28956f6620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/katana/rpc/rpc-types-builder", "crates/katana/storage/db", "crates/katana/storage/provider", + "crates/katana/runner", "crates/sozo", "crates/torii/client", "crates/torii/server", diff --git a/crates/benches/contracts/Scarb.toml b/crates/benches/contracts/Scarb.toml index 81f563c57f..dda6203781 100644 --- a/crates/benches/contracts/Scarb.toml +++ b/crates/benches/contracts/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.2.0" +cairo-version = "2.4.0" name = "benches" version = "0.3.2" diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index b14f7dd921..6de23a835d 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.3.1" +cairo-version = "2.4.0" name = "test_crate" version = "0.4.0-rc1" diff --git a/crates/katana/core/src/backend/config.rs b/crates/katana/core/src/backend/config.rs index 4513f2cbed..54f1d4ba49 100644 --- a/crates/katana/core/src/backend/config.rs +++ b/crates/katana/core/src/backend/config.rs @@ -9,7 +9,7 @@ use crate::constants::{ }; use crate::env::{get_default_vm_resource_fee_cost, BlockContextGenerator}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct StarknetConfig { pub seed: [u8; 32], pub total_accounts: u8, @@ -63,7 +63,7 @@ impl Default for StarknetConfig { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Environment { pub chain_id: String, pub gas_price: u128, diff --git a/crates/katana/runner/.gitignore b/crates/katana/runner/.gitignore new file mode 100644 index 0000000000..cd3d225360 --- /dev/null +++ b/crates/katana/runner/.gitignore @@ -0,0 +1 @@ +logs \ No newline at end of file diff --git a/crates/katana/runner/Cargo.toml b/crates/katana/runner/Cargo.toml new file mode 100644 index 0000000000..e74d612b43 --- /dev/null +++ b/crates/katana/runner/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "katana-runner" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +home = "0.5.5" +lazy_static.workspace = true +starknet.workspace = true +tokio.workspace = true +url.workspace = true diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs new file mode 100644 index 0000000000..060e1c8fcb --- /dev/null +++ b/crates/katana/runner/src/lib.rs @@ -0,0 +1,108 @@ +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpListener; +use std::path::Path; +use std::process::{Child, ChildStdout, Command, Stdio}; +use std::sync::mpsc::{self, Sender}; +use std::thread; +use std::time::Duration; + +use anyhow::{Context, Result}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +#[cfg(test)] +use starknet::providers::Provider; +use url::Url; + +#[derive(Debug)] +pub struct KatanaRunner { + child: Child, +} + +fn find_free_port() -> u16 { + TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port() // This might need to me mutexed +} + +impl KatanaRunner { + pub fn new() -> Result<(Self, JsonRpcClient)> { + Self::new_with_port(find_free_port()) + } + + pub fn new_with_port(port: u16) -> Result<(Self, JsonRpcClient)> { + let log_filename = format!("logs/katana-{}.log", port); + + let mut child = Command::new("katana") + .args(["-p", &port.to_string()]) + .args(["--json-log"]) + .stdout(Stdio::piped()) + .spawn() + .context("failed to start subprocess")?; + + let stdout = child.stdout.take().context("failed to take subprocess stdout")?; + + let (sender, receiver) = mpsc::channel(); + + thread::spawn(move || { + KatanaRunner::wait_for_server_started_and_signal( + Path::new(&log_filename), + stdout, + sender, + ); + }); + + receiver + .recv_timeout(Duration::from_secs(5)) + .context("timeout waiting for server to start")?; + + let url = + Url::parse(&format!("http://127.0.0.1:{}/", port)).context("Failed to parse url")?; + let provider = JsonRpcClient::new(HttpTransport::new(url)); + + Ok((KatanaRunner { child }, provider)) + } + + fn wait_for_server_started_and_signal(path: &Path, stdout: ChildStdout, sender: Sender<()>) { + let reader = BufReader::new(stdout); + + if let Some(dir_path) = path.parent() { + if !dir_path.exists() { + fs::create_dir_all(dir_path).unwrap(); + } + } + let mut log_writer = File::create(path).expect("failed to create log file"); + + for line in reader.lines() { + let line = line.expect("failed to read line from subprocess stdout"); + writeln!(log_writer, "{}", line).expect("failed to write to log file"); + + if line.contains(r#""target":"katana""#) { + sender.send(()).expect("failed to send start signal"); + } + } + } +} + +impl Drop for KatanaRunner { + fn drop(&mut self) { + if let Err(e) = self.child.kill() { + eprintln!("Failed to kill katana subprocess: {}", e); + } + if let Err(e) = self.child.wait() { + eprintln!("Failed to wait for katana subprocess: {}", e); + } + } +} + +#[tokio::test] +async fn test_run() { + let (_katana_guard, long_lived_provider) = + KatanaRunner::new_with_port(21370).expect("failed to start katana"); + + for _ in 0..10 { + let (_katana_guard, provider) = + KatanaRunner::new().expect("failed to start another katana"); + + let _block_number = provider.block_number().await.unwrap(); + let _other_block_number = long_lived_provider.block_number().await.unwrap(); + } +} diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.toml b/crates/torii/graphql/src/tests/types-test/Scarb.toml index 1ad05db050..2b149d7306 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.toml +++ b/crates/torii/graphql/src/tests/types-test/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.1.1" +cairo-version = "2.4.0" name = "types_test" version = "0.1.0" From 30ed6498bf2c6581ef99d5432cd609448b58d115 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 11 Dec 2023 10:54:34 -0500 Subject: [PATCH 113/192] Update readme with feature matrix (#1253) --- .github/feature_matrix.png | Bin 0 -> 496740 bytes README.md | 18 ++++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) create mode 100644 .github/feature_matrix.png diff --git a/.github/feature_matrix.png b/.github/feature_matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..f83b094e6ef4eb30c30ce0ec39db02a23914455e GIT binary patch literal 496740 zcmeFZby(By+dn)70R;pMhNQs9q`N}|QISw7sS(oM%}_#Rghj_NKxs$k2w{kH$LIm0 zMhFbXfIaVlpZmVQzvKDy`Qtf`-~B!g>e~B?^SsV{o!7-19W8bGbFAk;AQ1h7`>Ibs zp!32Y5cO9YDxl@wKb1znkF##~pLv2n%-6_Yrye}{X9H+F<@rQi1ytC@z6AV1X?sWW z4hV#Zqdl-V1ET&wc%XVm*YDKoq4Qf)-{2ejN5m>)27zY@*2adSs98(PMjZrw^qVOf z5327PmeTPf-(23keb|!rd!XTKRG{G8lKwSrVX^VwFLa-PpAInkiAX=Sy87shXd_aH zow#A%-FfOtD#FRCjk%cLxcf<7&9(GHW1N7|gd@((ad&JYaQ!IgdrbaB-msZ=pySRm z$v2oMlBX{cFe>7zJoR0pF24x&2%c=j_5;}HP z_&-N2yJ$}NKi>d3+-RWrkA?xQP^SNA_@4g13;F+yBlrJ{wK`JμtuG+8#4sp;s> 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

BS21H25n~}&U$B0A; zPdFpdc2rkLmhIFSQ*yB@lgaV0>*x|hh-nF3y4t+B?Dgxx^EvVqBS<_(b_F~8`xrbl zANv#9Z0gS z_Ei%KJ8wLp|Ldl4dw<{4E}Q7H00Ud=E?wST@wYz3wodZ(0Y0wzf>7KpHHP5U>7yQ+bPF_C#rva9+S`~2HCM}*s+7aL0tTnsuR zvf?8uGk>lP;*HeB&yI{THIx1dmvz3YUsBaNp84P`6eJg+&MEJ;=8}xqKdoN=(tE;6 z9w!6Ujkh|1bry@DT`XVzIq+jF0T8X6(oy8i4_~UYbZ7=S9TPp5lcMSJifWMv+M8OI z)5negK-V%Qcqn7JM*gGN9!BN5UyMrdeu-3!{Yyieb)7QVvJ(8I{X)R{=VpCvAot?| z?0TdMi@leB%DwxfmLP5lPSkjvWktB&6_G`cy8S(IaP%{mo)Se?!quQe5mOoWui1ld zB-*(;h`n+ElIZ_xYa{#AATlrLxb3DN-1G}T4N@tRSkKGs?5e6TMY8IZlhHw94gfgK zS|Xxa=wiJ0mlCw^H#dLCRdX;&&c@}ihS4!yeg1U(S0UIFOX#@!(%g5&!s%2!u@k8=1xyhp9JGbTj|dGi1wL*BG)ACND5*j@sXl&}a5YqeDlda0 z;sM3m(kg+b5}~Xn$f3}%yXGHhjdlYjI>wfuF-%7SntoG-;rYQ97k+N|29eG{u4nD8 zZ_i|H;p{u&?Zv}E9_BA<+~KAnJh?a8!UrYS3r~Cw2CI@;cHcHU;=u%?M;DG3$1S($ z&Mt)LetoZ=kehzlF1%9yxMZBd7T6lnoJ9@1H1+s|{pa2Kvm=U4 zQH`kRWCl~~%ZQXZP&U1U%gTH(u|Dgly@Nq^f?&StNIjNCDP=(r2Eb{ z7BfXlmTKHr6m)MV2x_-;iMcqhHTDBfWk?Z?iKI>tGh@rlKea%Wund`1Ac2`64 zwvcPzw~@(mApj2m?&V89ZjDHdefMRPA9emI_p#h!!;U&L`qL&brZ*50ugVy1^awvV zAPd@@(AZs-N-)S<0u-=rtcyXR?qXx#y0STqj73*48 zrk97&c?9SbWdhd{O?0v$z2!=N70tyL^7`kJK-Ldf)(}rIEs#$)%Kb&$U*o3R zp2vouW;11#9+$gMxI1S5GaSbWmyA+RmKCb?&TCWW-&G0x$5>`4mPh2tqq|R-vDQ~qgpXWDH!%~P5HY{jvWRd+% zyi)PJ>ZZHmQh)s_jWUY3D#9^AqY=o<{YYLVZIQ#&BCjERdF_~1{D#p*fT7>;p%w2wO~++L#wz;ATJV!g!$O{w@%bu?JjP&SaJ$Lo>! z>r`TELOMA|Un_lCPz)1uv#C%+CuC7mx42d~zrxK)hBP(*V1N@CaiBLwA<%_aKeGd2 zAZ@_n?)hniU186D>lhoxmSY#jFX|lz(gbdYDhjdhHjIG=XwkjlF;*{!g;G=Hg$@Gq z5MDiUjacN=Q1J#v{@YBqM8q+tTR!dS#y@c6GQi&REAMl-K5O994IgrRX+L@!s{0*z%s%O#Uj|2m=eXg5wX51b}MpQf&(3G+d?}nJ1D-( z#c6B~N{LE4J}9=~b1OJmDt+P}csD)SA2n1a<2DM=XtUX)$z#ZNqq=gc?s6Gdj% zXzj{96Z6U4Ns=-(dVX+>UcYKx5ie(242^=~S1Ke25`+?~d%;-~MENgH;d0E;sqs5B z=^CjJ+V~q(>Y;aUTG5&vPW|M&Y&vY%PJ)?B8=6!Z!-@_4FGrII{-b=JXDuAF!ZJtp zHFZe3w8&c})>y-9F8`zmEk3#BDkjuglRDTZ&|k&43A_`1mkR=gqpKckGk43Uvs#LU zSngIc@L0%TA-OJ_IjQyeb_TI#xk@Fv_&bbLZi8iGSfWK5ypK)!Y<7h@qgu>PUZv$vd?o_ODfBGX2-+6BS}&eIE$4W#W^?m^|C;gGm27=kl{Xz1 zA27nm+wdkIVduoKzRK_?7alJfKP$|6FZf@&Gw3em)j!9rdXwBIh8lyu_v`EssV4-b z#M`-89hLKqIBF=BPOxZL-Zg7xa<}2S2brn$trUr@pQ!@3^79!{I0aYjup9D%m{%d! zmFFu8`0#E?S?cbNPLbvoE1orK-)(e!r)1h>QT1N`xJvii`E3Yd>W4jTFlRUmt(S^1 zy5`t=FkT5i!9b|;*<#h}DNw{K#q|#8!KKu9dIxBpj^Pt(dOyuiJ2Tu*FT_O6OlA0R z_!+Z73n}&COYve^QV=_<`y|c-Hqn2<;1UdhuR`yiGC?S z+JJ!i(=CxvJ7PDmB@7t_xAQw0xUD_28(r_E%t>yh;Eul@wECe|@Yrnj?Fd|!s6laF zN3kIyf56yT8LiyJ5^(N8y7k74Tk$r|3!+My6$u4qh2k6IGLU;q!35!w!8$YjUVbym z-?qi4WY+1g>VLAq8Hk;4JYQAvX}1D8I3=GtCkbZ*E9Qnyewu|U>tsn}tq}_BsK9J9 zsEzXwnTIgn*>iXvu#;{tS}Ht`q^+<=+^fXwEv&`R+#sH^P!mq?w?9pNd$#TC<`7WQ zymp;=%y*+v{OW7tab$^LiuK4cSNQqurpe#4@q*ZS*Dpu z)_O&(1Jq-6Ruj0!)@;L1c10>vRt%HoJe6REz38}coiwGp5|444UV9~5j5dkLbrPrP zN}-(8Ip9t5Af~b6kxj)WPRHolK;%FoADDLZ-;29FUFOf43N9n>w9nJI*vqb*ws>Ho z;oR2~+;vvXK}ms#J7RBGRl_U17fqWG*Znj3xFM`W_S9EY=^j;Pg*h2M)J(*74sJ#&CxN{)Hq;u`WzX#E}~?u%~O&v=5;)m%R6cGsA?^0KZ%z04oq@<(<9{L7Z^ zSpV^?dLmX_@y&DlP-nJr&Sc^o!pY#Fs5=HJ=Qd|=pIt0K^UiIZu`Gq;|8DPz40-ea z*&lvjVZ*n&Rbfg271VE*$(W!c2MuMR$;qPLj}}6a4E;9VW7KRPJ%APISDsHW6)HvX zjpEJpRPscxCG~8QtrD;2zN7}#T8*`98YV8g2xGxd?e14dD){g8rs@LU6pCmc!hwan z75T7LbeM6~dr-QUee|N#==?R{`i{@^RK&kQnOX#7c~otozPr&Heu-jmtM=sfi>nFC?W3m`xLui`EykH$r5t!i>p65qo6COo z?aK#7_MqPO!PY6bGmFYicBjr#alA|5(p}hp6HXS{*frN81wRhPco2h^@XvuNvPWzX zX4_1RExFIlKImf#zWL)2yrN6=8%TYHomr(u&dbw2p z-jpdntXJDNQ!3tXu0#lOgD+cCxz<^YD~b@W5iA8Y3d>2Vs5hFpGr+Op+^Jy1?MmL- z9s4Qf_oDt&;UPtRLKLD>{%(f+0KowGWp zri#BrJeYljq1~Hx!>=?7(ch(PC1LY_s|u1^Ad)Z*&DiRn%|;G_&sQDcz;FxBv8s}h!}>-ZJU1(=gzb<=XeI}E}U83ddxR+IpuFU-L8EgU(CtmA^1qZ=zF@Q4bCb;Y?Ya;XFOn zhNqU=5j75;(j2&`N;qjG+llerC=tKS)QlJGH=;ds&xH&_TNHGiP&)IdgWC%T{@ac7wIdb=0;u!dyOA(6gPEsf|G zFww4Wt?`pn8Q#JKJV6B~A&(A>gt)vKf4|@@p&Dtl^-ngKTrb_XHoO(dAjAfZre;s} z83fn;R}O?cinlnL?UHklMPbX9w0cSxlXV~I*DnaXtg%Mek{tR<@9JXtTH=J{u*grb zSdCP*Xk{4`>907Z*z{Gx$>X4dxr{`_&b>M5?`e^2FCqEMGp(4Ndi~ zDejw=bK6~%?L1xb*gNuD8eShh=|S85*Je`d;eC^N=j)ZLPS#kgHN6BB7_T;&r6t#(8U$#nY)o?qM;p~hBQcd*o^G4Abk8+YUhXFm9aY_y zGSaKPymrh<3^PIC7B&9P(hH$~xK((@Cs(ekJB)mM4nR!-NVTGOkV zZ^3E??X|?}kBgLILyffg$?iA`^q+7QFhCt&M8cnV6*sQ=uJpfm>a}^%CxU*!@`41n zmYo|u%de2;m3szWB)+~(d0mjixb*d6mN;bf_bu^odhQuq665Q4A!lFguhU;nG+`i~ z5`XBI1XJWf)gLrvD>(?lkHY#Xf~ofv(O-d&sfXQ95@w>It=XT`RH+OlSrD|5Bnkk* zf}}SB0w=cXm(`hb@|0$`c^W2E)MIzn^Mk{Qt$W=b`h4q_+6B_ngU=(D<%7+Xuq$v= zbG1WwsW(R1`HoVk!{-`;CX-Zj=M!S9CFWPd=>pkcCC;YmKUrF+puc_qIG>(<+?nrD< zGTiRXtb5JR^Ke9n8Fz_}G1sffQWvM7Em)(f`KvAZ_&>ArbhaFo^tRtRl=b2|7;wpKL{uMJdMl40K)+Sg9Y@Z_0tukb5*H z`pv=(pSZLEapa+*iU-kBU0T_*GLlyiGV;@ObOI|VTXO2}*1V_^H*V(LwD`Al-`oN8 zE(KH8&v&G-j6=k7-K%w8Yg;;B4Uc%@$%p(-73}x8G#N+W&Rqv0aQC6`iR-A>O`BMa zXr}c3IO3_xCeJdoWB9hyBN<@U!^8WY_HQJnXLgG#lj+R9O01%9)@HkuYiIcT*0l?E zA5sVcno{y;yG*>=d%G>}uCUwoZo?*!@mU&vI;8S6MK9ruer_9kmlIuv-0pj$psA^L zuVfco_O0klM!jwCksCcViH<|SZrnE^hs`{7);bTs6v}CjEsX|q=Q8HehkO*o!@ExH zf{1)7+0?HW$sy03N-k7?CVUdhVdIopREi*95NPQ)^tmWU1TiG4dv(eB@W#3-+k zLC)|l3^}{Tq2a!>nkc*d-)LIRfs);OFp7~TCg#6GZI818vr7_kKOfqgk=A$a4$ov44n=VN()w7|qLRel z$+_APH|)`bie~%0x=X;(OSfr&j7r3KPDe-6%@TMb2GzeP{3%cnqC9(!vxcGb!qCBF z>h5BXbWq!ySd?=LS>z*eXpHOke3GL#j|Wd50$D}KxdBVVnDJ)e=X_Z6X-S6)O@a@o<-;H!{${Jhc)>)!0>r|Pk};>1~#lj8Y! z*{$ySvkPY}&G-xK!&COZ*pyZ_hmr4`EM0L5P1kg9-p#)IDo^KXUEs1p(Q~eSC6@N` zLLWqW*a_RgUVkCiQ}@?Rf$fA=V^G$V{%Q99)7;YcBZPk*Igu-=q`lHT)@~{7W>v}M zY>1O#`NgYoFt^1w;6U*=D{fzz zvlEBO80P!P%Daiy;2ZE$qjTrSm)h)9@HzUlJx#0RF{F>uZ_0gd%}mN&DG0p$qGMf( zVGfD1ar|(F1a}{1S%EPN-fR|sH~T5>qQwn#+>MXW+7HrFS>t~cAOD87P=DNiU=-sc zeq7M+Sgv0Z=Q?Ga%eOYPcP?G;pzar#l5nCz)uGX;GOr<8!&DlskSa z9oSGR`qZFYwBiT$`+GX7|CX!1aL!hiD`Yn)^`9tW=hg{xMxMJatQk*_l??!&=bH_0 zeY4|3`xMz*s2WhYO)Z^I!VMDc*MCG>L}`Uj#kW6I3hWFG`!x1P;dc=D972d=tF5?0 zD{N4ZA2(FJ)osS2@G`bq*IzC0*|0xZ51|3CKqa3%$lLxvGJ^etq*;18YP_<<#WjXoC9t&A6Q&VS{?m+x*_5o&nPE z&wwPLgV*p%O?~3|mt#KI`WqJ#CN35)|DNjo_s<8h$&~H4m{jDrzzDc#wjKa&sM&N> zenhDeO<2NbpC6jI-?^MD6)u}1L*EiMJR$DFBFiW8<-u52G8aMkm&i%9H;JOHDOO>< zi6?I}{&Y*;w}7M9;*&3|Tm2;gM0)HlDeR-mY&+6p3hAd)z1vhMw2}AAc|LPJ$%`LC z|9;xclW&fwdWP9C3JN2>Wy`0e zO~zg5zwbR%=_hCHz;5jVZUx&FG+&qv0DvawwJQuH6rMNFsTf*oM>v+NrBS5+?eXx2 zXaRSRLcJm)@{^GeF{RhDMzno&ne+t(~ zy`wmx5jCq7-RWTf!Dfr=6ef=Zx$1wbeUoI#Jo=qNU%@z-S9C%+rukFlgO_B)d)#3j zV+5xgRzKm=Eix_VGAqZbe&J3uF#(wx9ygl>E+DqpDa^se{a+3bZX&yWr|{w>9!>(6Gx-4(g;ZV@{mzNzTyUTO{ANtCsYaA|SN zn>jw0CW%&P3BxGGmF#52qSLmWCibO_>Lu*Tty+^ElycfF66Rh$hjwzOu>H+1tk2;p z7=6+VT*9)#ME`EYJTSEJGBjl-X3ys(J@k`I>RDIKsY}%o`QR{{sQXbP?X~!`#LPj@ ztlwTEr4n5Q>n;V3(*UU!h#R*flyI!;EkLt;GL%)b?G8L@)h<14Hh9><@Y7 zqi!J`@d7|f%#rjY0WFP_jH~4I-Lu&9?7@7;JRua*Q~;*qc)~b^Hf*^;0d|i=l+? zU4hX-le3@57&6c5h2iGVI|iWvS$;`*(a!aOWAv_kd`EFhs!?PXh5&8Ii>j+r8jsuz z^PSn^T~eXb%|47gU4SL|u=Ge@OW#+iF<5`rc-fGkWAVDPy~kyoTS?35HlE)i<{^=0 z%wXR4;+H9$OZyL-dPYH_F1a<#YpcfLuQIw+K`KTYj*2<>^d(*M$@>Cm$ReEvWT8bbyw~Y!Z zH7*yqMM3Mar-r}z+r{z|IoQ)Im8edP3Z1;iKcwwzpwIquH93q7elL^F*!;zzC}GJq zBKu#0&?$u4>EzgH^5QPMNiLswm}H31S*rQpN|M-wisKavw&%3)8D=zlCq4453Tgqb zd1Ho7VdB;F4lx~d>+4TaaERpf?+4V6q8Io;j3wZnhJ^6nfGL^t3lKU5yoR#oro8~e z_*708kVZ?e)ES7C{lc9x|F#Zf!-$%TZ`@SPI75_A6y@Yhe-kZv%-UUL^h~{{B#DYv z92X8SdXK7Ev>?@aC#4lnv+TmK4&-*vvgPP{kF(;V8Rnge0&ymj_g-QVpw5` zp8eBsEYzOdN+a+N%RlXpXX+&|uOAMqgUea>W!ZoQ$VOofR60Fcud8Zkraw>ATKVAL zBEXkEP|d!Q%e%I?zb* zkSXFnQ`4iQ$L}6i!Wqaag_IkwQ`3GBX;RPvD+TY5s$?zVvhS=-TWOt9>x-k=cHZ-v z3bw#Rfno@xLVo@4X6TbYl=q6T>ucCw071c-tD?srfcYpp*qxYD({Qh=j>pD?3txcz z#-F8~TfA|Pyv2c@?+l`yAK!L-@9Gc@z3R^=Jgd+Rnv?&+JvaY0J}&dm?#+e6HTNVmwtnRUX07H0tD)mQ89@i;{MouevYG zV@f-cfeBf8BzFT3QtKtnK(U+^{a%yNd1G*N9ue{y>f(#6+81?*9q9g|&>AK=^tA7@ zXx>$s8k|v{Fgl~XXS4iQ|5Aqw_1#n$ zzAKjgXMAzb1@K@-Ysyw_!0e512FBoW1>8ohm{TV*}N(qJDO7b+{ z*q48?aZVh6YH0$4Kc3PkIhy~KRbBB!JdF?bT0>%Q0C^R{krZQ?)9JIDq2;74vqJY~ zk+d+|@2-R`(|mxKT%M~wqW>cz^=?o5A+A>|gnti$vS`CaT^Ei8rV}P>CaNKqzw0jM z5y92=Q%RM_4C6cL>+_p=^_E7j&ZO8W6pG0OAY|g9T82Y!(_*{BH6czsNyR$aPI_La zIq-jG_X-XeUkTCRGXC#mOz{ZUq@#R@gNx(nyP2o8ovBb(2h}z8~(fo?82rjE9;7A2B z;;JW0x#<9jr~ige9xDW{H|aCk;GTna2*f;Pu6TJ%KA+@?%Gc3{s*gK>wo~OZRBJ3) zHo28_=#*w#c<-~GXo3PBtEPcuMwA+6283Mmt~t;i2U%S4s~qiqr2CYx*;VpoRO?NL>Gi93wWyC6y&PFd*l)>-{w+>#?xUnT zkNW&nzsR^%3h}u&@@XJn8B#kNjPAsaZOh zX&1KHk^NzdpZSHN;M<`OP|Yo&d0M%{Z~o$%+2pKk6DXYZ7NK)sCbxL%rfl%hW$(*@ zDWBoDE&HgB_V37_&gZv6PVWcYjs{Z2PZtjt?M^Z4l$tHn{lwei8$sq{@x!mI&8SAb zuHf#weAS@g{JF-~*i)A~&;6eutr5#Nx3?DIxYp9D{3k^Hm3JO)f3@%Lw9+Y?c1`aL zz%fgN4XK-9J+VX(0{qn0=RB}^Li7~y>FPAueEP_6xrD^QNIbyFyuljd(O1t2fF)J^ zDV+_A3f{-5?o+QJ;wbC-yODKm`6QJoWB2|6X5~y>ssof=0XQs}8eP9pNG#y)pCQ9Iuo za6;>Y4JT+GJlc41$;@x4Rfnw>KP{L~3&!U){nF+Gu%cP)HUN$kYIAOZb6PfKl& z1~^|nbx!jaMJl$1e^HDxFs&QDKpm{gP4CG0y-thT@R$OtCguREN1Cre5N=7+bDcWu z2eTir?kLjc)BYE3Sy#I71m&!}^?nnalENsZfHNQVc$05uB9Y)*`)Ho?(L|4E2~T!+ z%NRuTeqxeN?BOLljM&GE=(pX^JPKv28btd0hR$vZtw`J}OaCuZcUa(Wr$`lE#<=;4 z{#-;|#=R6@8df=asrl_b-aRqkv0kdemLgRC8^0;R%=VYHxwj+!;wGFJ(&4XIqdn)d zIErKzNY*X|yI=ZCX|%@c+_QS%qyq$5n5ltt_ig<>kE6$Tf~lvk-MatWKECj%6x@L* zEiKPV3TM1>%X)y4VWp=3pMkH^yUFx)kQWM-Mp0e;=w!@FX>fb5Za8mEf71NRN%fZR z=XlIci(Xg_;1i;cGHNgr8s4w+O>@;_SH?|O9@r?s_5oaA)p1g&Fx;VJxbJHqnZ7n+_7@!Z-;*; z_$4DMo)=P52*dAubjog|9)I8Lw=r7SY8tO#2h>|;Pkl>VR>Xd?qnG(_RY|MdqU3xZ zI2Ukc(i{9ll|73Vt)Q6fd;UB3PCxULm zVwGoucZqYhAiZI`1uIT}o{f^; z^SePH`1-)&l?B~;ZUfZxP*0hyZZtai{B*`n?&ZS~Vd106VmBwgFL?Ld4#Z^`ET4VQ z3#a>mD3}MX91b>2!GoL9#llYM5ER*nsts@}{Cs$!0QfU6xn0*QUmb<4Q(94!D_Fcw zpeX6MLvNZDAUnKJ?df!e_oH4de7~-<-@fQR3BM<)8Av{OX%$@Bm`PFM-$?>R|#i!{h%9fyEdxqJ*7k@ zr=W&AtGhZ}JG}_DzvOmazvnl3gE}xZmCTgIX+n7?FrXVGlQ1ZdNdPC)k~ZPRlk)Nk zU;mbp=ChSQBbN>A-ENXX&b-I(jbNq<(p9ccq!5hF80;3pt!bb8`?9}2yb>S)24(r& zi5Chz*eV#m+0{`hA^OkL&r;_2AbSlbHLMa8SxNy$*Npc$n>y}^1fSpvG9|+)c*P~h zH$bwV1yArAu%E?P+SzVc zpVv;@CV?0VX7Ta=`SyI%yySShMwRsSh~vu)WM*JT+a5c55@!(6e~A@O3Jvz@ddiiH zj#eDhB10G*`-*9m;GVt;4Y|>69+i`}?_0mGMN4oKGp$BDeB6yVxgO)F5IsMuT@F$N7kwLGl8vk4l=;@Q!<7koRu^93;)y^q7Z>6L&Edzt#e#FjK2C zU&=t}I~;g%31>5wk~wCRw?Q3aq3Iu^z#X#FK%nPdz{t356nxXs4(O6?eGBxn3zUIP&<8n{)(%yM-hwW=D&t8Yn|+anP&d(lqsK* zi2JSqT(s=vD=rXcbpzqGJ+ZR+ z&s5V<@Y_1Gl~!3Jirlmb8*!kT1}fqaM_pFq{+EioYDEfT)Sxj~w)1Gt=lM^^{46;t zUn9M)g2iM4c_X34x7-uZ?QvLH_X{U7T}KIrT}1t`QrsHTImp48r3~tidJ>(z^q5ZZ&bb(W+FAU~CUm$9g!g-5 zcM~#*8@yFKi}mIsh8lU9QJs626SI~QO+TgnF?~r@uEy^OycxJQ<1t_B>VW2ya$O6j zm(1Z`Qog;ZFg#CmRSa4zv&=B>XK5Cxr)?2^1ANYkB?!O@3%z@N^1(UknYj1^MAwWM^& zJvTXMCeL{Vl@`xQhy|%SJ6xo4po)ic%)PJP1A)sOhhNhMf@w^wP@0dLLyBGAaO3zV zzKe~1kWs^#(Tjg|M52MhR0=wFp);3zBVA{seD3+Da^oga#z@5~TM+mDOp(wrb7E@U znLKhTJMfIJuPgi4fsC@~Y7ll9o^i7xSMfkDO#%Y*N?)P$;xuaQ^)eKXir{FbF|%RV z-k3-{y5!BK;5sx{C>U4SuFDe3#wWkCMwygd7$gMXJ<6bhJWd-;`@{G=rp9!jI%YaI zZ2tdWUbH%xdDRF{4CLvZLKPzmtL+c&KoG|IH1z}aeGUmL@~h<>eP!}U-wK`5F8$s8 z58qdBP@$B<^R^+!nu`J1p#=(@zbGuQ`-k$;Zqp-gv$e?2rmdT`q4RHdg>>C7CfHw) z^#=oZH8qCeTEn}l4{=D%D1Pf9*f4uZ!L!hom}YEOdL`ZC&bzdmg@Zq3JyfGP;+2kY z%UnVP_I8wG&gj*wxxf8!j^J%pm?U)WFaIqX4Hhv(l_#0TV5pKqyF+KONn}Yt_q)v& zl7LB>$`Oa=Lf8^%@p6x3yM84ZLIk5@|gImj|@ z+;J6plPx4rF6s^~_BaJUy5CNE=5?4-hQwr%WzdA4dmgFfEEc4YpC3t?p8+%3ul=}j zZ>#gTqY8a?JdZy-5`v1lM9-3GMhrG;f;X73^P9@kne&$yi@S7*$O&NJ5K3`xV}7?{ z%+u=X;S8+O8=u85p3Z+&77s(^FdI-dmb_ZzN*9tf;zM+UKzGJ+dXagv?pNQePW-Qu z?>Ewq&i2)vGPq(m6B-O`f|i+HsrT^DpcPC!myJHIx$OlUmbwm20k}tno*K7tj3*pA zL|DRnR_Ubv>eb_k4F#>`K%}c$!RRdBh~-e?aD9Mg-Soi9eL)UQntK9;KYh{7-vf zCqfF7B-kN2c3#=f;hiH61d$5VR8IG*^EgjO?EN2tlc$hnM`U|5f}y8p6UK3UF7U98dcm`nM% z>@iXWH2{qBvDLIjLOuMIZ`|;m#{_J@dqS#I^h6A?u9sI%!9{a$-N28})Wyfp)cIlx zDC&x}Uk|nFML3JM-Bc7t^OX7AjA)(}dQD;pPEIpj=fUHJ6qZa?%?Uut?^eC&W4*mv zg)h~#u0e5ND7Y&$FwOokU#a+t0$VrkD{MC4l(UZjW{j%m5sOL4pNd7vRG5I=mD@%g zl>s@6=G!G<4D~%)jVD2#S={hd?yoKi*KUaugo8stu`rVa8SL;g^%t6dYG!xDN7que zWV2e)=_Vh!kdKFV0Y@Ac3U|%eL*obssAB`n$F!zqK#8&4hJ2S!F*0sqctRX!@Z7J! zc#6Edw?M_O5Wcz_nt97yGgKQ_I#;_kzYq6AV-*FAZ4kB?1T}kV{OR%i8@O0 zhiUiVTEB*ay~U2h>Y0ATb~;0Hf$OhmU+C~riMW4$P6~`gbpLrGk)Gm>wd}=j>~u2B zI2jx7M3RnLdqdoD|Fed!*lJVY>fWt)W$qEg`THh-VFU1%FK$T^zRGx!o2stT`6!rd zl=}`3lvb12^Ty-c;zT^WAXU$YuvcA4m;=Cew*M2BXJL$N3la@2cc~t4=*y?XByqOZ$BA3>GSspMqjL7 z9}+8{$cxYec?*#2GT+{bvy1PzOS?Q8kX2Ciq27{H&po!tgQxa5*GHFPnx)4fImclv zPr1Zc+_jwFA%^V7{)c2c1B(ZOJuISEI5EHavm(hx!T6>yt$xxsLpB5?7!SylRggWy zoP42IV-KwKa%wE>OcCK$LfP9%o*53aHLXOW9XB}*x8jm#1UVgibA+?Us2>|vkl^iH z!?6{hk0oIDZqth9B+?t&vX{V}>Qs<#iiSt)z0;-qNsu#}3Vt?HW*gr+)ZxT@r=@&u z>a0TrC%_H~4R-Z6zZun4%Wlp;OxJw>*#na|yCdyMvCgrv01=%UT5h1VEhkgpc(LxC zqc0ZdCAlkvKR^5?XrySEm?fIxJ5&ot~*!h9aANnrC;+fL^ZS-&FS<`eZ`QF}d{;QCD@T;zSe{kg5U(JG(tQ>ywd zNj`1L>>O`(*oChMm1M985%V=Xxrb>i7V?MCh{M+#xMLc%VzKx!)N{KdR|o-aZPP!k z1+r$84g_Jd@n!Y_1L7T=vSy8!E2Mw_83WyAC6n4Yv83NfyT1yYsES5&keyJY& zzBzd^j25$cr?7nmjCPg=xTj4={XGnqj1<;GX3lhMcGTKWJ<56fDFr%*Avg_L2?ni} zcD-nAtLxPJl6*XpFIcyJwuBkq8QSmuDa1INn9Xj)Mx0_v3uUVg9^E(W$-00k-|1vy z03MpahhuQIuH3D!#4qzRavU)Pxp;=m^X-hs!3kPR1$iMxosIhEzI4ReRBj4$mU#EB zUtgK)m=wckiLo95u3S|)#)+j5Dn!<>CimJc4c_^1eUu8FTjttt9R>-i zE-uzZj;5z4lqNHq0~h2!>b+u(^~|YCjs2FI3ny6btriC@u_&(0U<yq6l$ zZDjpNQGDPb8BC4ZF;l@;#68d~^woy2VMRHp@voznPr5fBzxsPj0ZNNq8z;JALR8bW2b zY&kl)92g)6N=9iTObkz&5ZsIyWLy%qNCm02!%6b$={WWu_hRJ_JtQOhq&7(S+PY*G zuW!~H(qVf6`SDbUD%vGVUW`@&PohEY+Cq%WV@e`o?rM?dp{=iq3Lf#_CQ8wS^|pPe z=faSc#8}N#ASHLiq9>A>f9G;pK4)b&hvlm6P^AQffKGI!S!yW6P=TZFe1t#WFV0e) zjO-Ngk%O0zK-~4}cmrUHl770Mr*?A@?o$H2%tz;NnAw`g1$51OAzR#>*Eb7}CR|SR z?EOz+x4qa`TyeZ*iQ1Wq4kG=1tJpqZ6JW6KD%}4~CWF}YzsMLddX?;p_dvqTbh~NQ zYh#yXbE;sUK}rpT1E+AHiib*67Pnq>ZTYY!!J@8EM5O5dK*`>4`nyi*+GC7I&8&q8 zS8W#v&>AJ!t|xy0Bxu{f_ctd1K^~yk-VATv&={Kn;xEaMo2?x34)bkRIf44$$a_@>yGm~0n`ZuwHp)ri)9fa9<@jM&#~ej%4LXth%{Jn` z{MF4Zh@A#9gRqD`VQbXtI+!%4L#PvE9Ce&F@&2GfNsTX@h6E!Qe-{&!d)A-Yad2KU z3&!67P}-+2YJ*NwqI1xIPkOqt(YMV*!kwFPD56H+egLiNqn#tI|GZ?pqRkoyUU55k zJUN`75EFgN1oBi&?Yw2FrHKUblL|hz;xGQo=nGw3*jV`baSD>WjSj@=2p61gE@Et* z$n3B^Mg%n?O*~lLHH$MQZ2Y2ycul6v)qboe(Y||Bc@n5se9cdVLvXUyiE{g%Vzl`8 z5440<4Q}Uqh@$FKfNkYaAeU%ZW`I{ONW>fJX*(kMHKu?QyRhl}fUtVhVYkj)w0g}s z7Ih)5tpumtm&Io(L1i>J=0s|&Ax0ZrN?Y$0f-AjF8F4;VtC;RkuLg`3qaxwXZ@H4Ssvc}K1DXc~n z=_oF;g_^*D`|xW*YpP&`xEF%-G|jm;c8>UgUD>uO{-|jl|GO&btkRttqnxA7y@Hix z%eh}t96t!m8SHe;-22Z25@!U*KWOIuHUg7}MxSZIew`BcQV2aoTf>)dR!oaWpqq-_ z5He17Wg4X-V0}suF41P2&PW_hxF+;AA8Up!8--VI)R-}-POqWeH=y1vZ-?F^in4s`Ek(G=tC8Rb4)SG#a*o(Brp3{9@O-tV< z20~(s`=41!+h_RLa*Ms{Zv5mOC%4M^b*a9)j?g5JdOM1SN(4$MrWt4w9SU45WnSA?Z4&WV_`|8(&K zpr?^JTt0g_JU+D?Xm_m9>fX5`BVQa@();)JsXG|e-0uJs@cjoC8jZb}SpHXsW?AE^ z3VfFi*3C!v(#2iSWZYikim^lx)KtBPP zKB=_89VWRiU|yJyf49%+JVk#WWIMvpWI2)60yp`ollf0bhhuADT6W4vpEB9qs49mt z!%ggu*-n5r5fvQ=wsPAVB8-kF1;c@chZ5Clo)pw8B0)pvaDj{n(pAM1<#?KjF1*NH zJT0M8R;E?bl+YeHwy^H+4G7z6vu(09#_+ApBzg8<$B`AwkOFKD)??7lCyVGZ?*dXo zvCh3`DaW{DpD`)@fzBJ-y(!U`U&)P#nC|8NOJhh&gZW?78+yaIKFK4vniS{TQxOni z$(-S|V{KG`P;=AamG5MB?@7|*iLJ2oV<#}j;o+y8jb|j!*d}T>5B=L51O|yYR`If? zBUIrOi35q`I^S^KCAaD9n{dWw-<%vpBkf|y`{P~ugDUxW@^Y=L4&{~Gwp`)cX-TJ1 znI%8fFLZ@)3d%A6ZN+-S`Vg#KJE|sMJi)U+>*2Qkrudi+injhPpJtbp|H^(JB+~tUnwzC{NNmfxwd){-^}07oc8@-ZNDHx z#$k&1I93L2%}cOR|EEk&F#3LI_tvzllFR^BrcCxO>ggq{u2ZVyy>+J%`k0Ljmvg|8 z>*KD4OTxy~WBJ5Ib_UHq5mHlZj9%8ACiWD?~ zPG#g=yswirYp6MY$XE}_H-~Y%^8n067!SLE1zH6?6lA=;fx#T2$+T=jA{nYupTEiF zGVR%?br{w)-y8p479-5b^oShq!)qmCG%qQw!*-OfMq@a}UwS-+KERbK)(KUQIyLx{ z>T~nMx!#EZ$%cL+p)~fbfD6?HlIijZ+4U6AdV)!PT1&w5_&I=_LO6xE_SBqY6K&PK zQd1<$pDX5Y+7{VJ3iR8oDSbD}Ww!UlrQc(N?(^7$I>+=&AxtBwT~&cxE6S`9(c&P|3Rs|#_M;|Kgzk(Uk13L zjidKsP69=`@%usVd=v1@Y1!Z6zm=it6i+$S!~$>kv9Fq3!MI165!K*ZGgmw^QNLks zcgWzy>Kkgx-4dKR9UMdNJGVr&!okvD+sNGc%0M1;4o;zKYc$U>ExG6|ZM>IX>2WZ( zcd8&VEFB?Iu_UE?y7~@-#0F{;bLID-M+O>9vRGGD%2_kPEB#gk4vN-}I}W%$B^Zd7 z`mf*U?U8b0^+>;ub}8LGZ*T76Cl-U`7Bd$!(mCuI0pY-kv@|+_!(EqWAsT*B@lSx} zELv)VF&TwhoSf@8%=19P*woLRFTyFq)avnTKrXXuB?w_Q~em? z&TLZt7LS6JL(gR$UEx@notEJ+pJ|2Tk*>r74nTz}kyf6p1s_$=e-8`qyJ|RoR_e7E zeuQ`w$$H1g=9}Zes*YD%$Rbz||8!Y_U|?b1>LVgV#vH0SRB3lUc(I=pg?-%=r7cDf zPF#fH2gGqbm95L|EPl_k=B4GsAYdvz{qS#5ZyVa4L4>9ta;uJHlB+bn))1DKF zb9OlgZN%2g2G_6%+7G`76$f0bJN>4$e&d;o1e2nEr`h3k-m!%`E}GSxB?#s4mma5I z(E&!FW*uA~2DP&jRdGi-Ti8#b4X^$6d^_m7 z2vP8W#v&AeYfCu*hH6>vxDnV4a~!JD`}&U>aouZ>^|Mj&D4Lk@S)BPz@B-7YZ2fNa zDCL}a5HABEDh5oEF$N%^&JwsaTTVbW`vr&BCkyY*s-FiyU#}^p zsMF4sU>vf#=QBbnI~`Q{tJR#>zUpp+=vmrpn9{*wQ={Il1yt)En7@GgD=7@oqJ%mt zU_=IC`*x%kaqTkuo1ft=O2GKjt3%8QthzWbKZniS+3Y|sL^A1#DazO+_-gtbIPVN^gO!I%>Y9Qd?q>RF9Uc(cbCm@BHx+A0QKYRSojERI< zp+=k+0&V#*Irvvup22Ci;bNgD0p5vY9X}*wSs#I#*k@dOa?i8-Ki<;S(d70t*l0M^ zfxW`fzsb*b7`vqirvm(<@&En^ynA%*qn}hi6);|Vaf->!i8PW_&n+RB3g244ozPDq zUjOjM{m<&9d|kz#jB?p6#5O13DH1i2+K)S6kA)&A1Eki2J>aVA`TyJALCZA++@=m< zRBm=u3*4QUNQ;nd7B~BCGHo)C1H$9_Fdr~q-wE=$q({M zZWj|h4j;^|(mJ>859oXU_X$eOQ>c5WN)}8vV__seQs@pB^7In}WF$j~P|kCO%0AC_ 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 z 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

>fG3;nZEr3& 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 2fdde944ff..8854b69cf3 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@
-
- + + @@ -24,19 +24,9 @@ # Dojo: The Provable Game Engine -**Dojo is a community driven open-source, Provable Game Engine, providing a comprehensive toolkit for building verifiable games and autonomous worlds.** +Dojo provides a developer friendly framework for developing and scaling onchain games and autonomous worlds that are composable, extensible, permissionless and persistent. We do so by providing a ~zero-cost abstraction for developers to succinctly define their onchain world using [Cairo](https://github.com/starkware-libs/cairo) and a robust toolchain for building, migrating, deploying, proving and settling these worlds in production. Leveraging execution sharding and fractal scaling, we're able to scale computation to meet the needs of complex, interactive experiences, while maintaining the security properties of Ethereum. -### 🚀 Launch Your Onchain Game Faster with Dojo - -Dojo offers a comprehensive suite of onchain game development tools, harnessing the capabilities of Rust and Cairo 🦀, to deliver unparalleled speed and scalability. - -### 🔑 Highlighted Features of Dojo: - -1. **Default Entity Component System (ECS)**: Crafted in [Cairo](https://github.com/starkware-libs/cairo), it provides a solid foundation to structure your game. -2. **[Torii Indexer](/crates/torii/README.md)**: Say goodbye to manually creating indexers. Torii does it automatically for you! -3. **[Katana Network](/crates/katana/README.md)**: An RPC development network to streamline and expedite your game's iterative processes. -4. **[Sozo CLI](/crates/sozo/README.md)**: Your trusty CLI tool to oversee and upkeep your in-game universes. -5. **Typed SDKs**: For a smoother, error-free coding experience. +![Dojo Feature Matrix](.github/feature_matrix.png) ## 🚀 Quick Start From 4f5861ac1ed03da9343900fdceed28da218521d4 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 11 Dec 2023 10:57:25 -0500 Subject: [PATCH 114/192] Update README --- README.md | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8854b69cf3..e310d4ab83 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,13 @@ - -

-
-
- - -
- - - - - - -[![discord](https://img.shields.io/badge/join-dojo-green?logo=discord&logoColor=white)](https://discord.gg/PwDa2mKhR4) -![Github Actions][gha-badge] [![Telegram Chat][tg-badge]][tg-url] +![Dojo Feature Matrix](.github/feature_matrix.png) + +# Dojo: Provable Game Engine [![discord](https://img.shields.io/badge/join-dojo-green?logo=discord&logoColor=white)](https://discord.gg/PwDa2mKhR4) [![Telegram Chat][tg-badge]][tg-url] ![Github Actions][gha-badge] [gha-badge]: https://img.shields.io/github/actions/workflow/status/dojoengine/dojo/ci.yml?branch=main [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fdojoengine [tg-url]: https://t.me/dojoengine -
- -# Dojo: The Provable Game Engine - Dojo provides a developer friendly framework for developing and scaling onchain games and autonomous worlds that are composable, extensible, permissionless and persistent. We do so by providing a ~zero-cost abstraction for developers to succinctly define their onchain world using [Cairo](https://github.com/starkware-libs/cairo) and a robust toolchain for building, migrating, deploying, proving and settling these worlds in production. Leveraging execution sharding and fractal scaling, we're able to scale computation to meet the needs of complex, interactive experiences, while maintaining the security properties of Ethereum. -![Dojo Feature Matrix](.github/feature_matrix.png) - ## 🚀 Quick Start See the [installation guide](https://book.dojoengine.org/getting-started/quick-start.html) in the Dojo book. From 79317c30cd47e82bbddd909fcdbb47b08eae8398 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 12 Dec 2023 04:08:51 +0800 Subject: [PATCH 115/192] refactor(katana-db): update mdbx implementations (#1250) * refactor(katana-db): update mdbx implementations * fix doc --- crates/katana/storage/db/src/codecs.rs | 2 +- crates/katana/storage/db/src/lib.rs | 4 +- crates/katana/storage/db/src/mdbx/cursor.rs | 237 +++++++++++++++++--- crates/katana/storage/db/src/mdbx/mod.rs | 28 +-- crates/katana/storage/db/src/mdbx/tx.rs | 75 +++---- crates/katana/storage/db/src/tables.rs | 11 +- 6 files changed, 257 insertions(+), 100 deletions(-) diff --git a/crates/katana/storage/db/src/codecs.rs b/crates/katana/storage/db/src/codecs.rs index f562816655..0b880e4072 100644 --- a/crates/katana/storage/db/src/codecs.rs +++ b/crates/katana/storage/db/src/codecs.rs @@ -6,7 +6,7 @@ use crate::error::CodecError; /// A trait for encoding the key of a table. pub trait Encode { - type Encoded: AsRef<[u8]>; + type Encoded: AsRef<[u8]> + Into>; fn encode(self) -> Self::Encoded; } diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 4be76880cd..18fc97da04 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -11,7 +11,7 @@ pub mod models; pub mod tables; pub mod utils; -use mdbx::{DbEnv, EnvKind}; +use mdbx::{DbEnv, DbEnvKind}; use utils::is_database_empty; /// Initialize the database at the given path and returning a handle to the its @@ -34,7 +34,7 @@ pub fn init_db>(path: P) -> anyhow::Result { /// Open the database at the given `path` in read-write mode. pub fn open_db>(path: P) -> anyhow::Result { - DbEnv::open(path.as_ref(), EnvKind::RW).with_context(|| { + DbEnv::open(path.as_ref(), DbEnvKind::RW).with_context(|| { format!("Opening database in read-write mode at path {}", path.as_ref().display()) }) } diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index 01ac1e8a69..943d92c3df 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -1,5 +1,6 @@ //! Cursor wrapper for libmdbx-sys. +use std::borrow::Cow; use std::marker::PhantomData; use libmdbx::{self, TransactionKind, WriteFlags, RW}; @@ -7,13 +8,20 @@ use libmdbx::{self, TransactionKind, WriteFlags, RW}; use crate::codecs::{Compress, Encode}; use crate::error::DatabaseError; use crate::tables::{DupSort, Table}; -use crate::utils::{decode_one, decode_value, KeyValue}; +use crate::utils::{decode_one, decode_value, decoder, KeyValue}; -/// Cursor wrapper to access KV items. +/// Takes key/value pair from the database and decodes it appropriately. +macro_rules! decode { + ($v:expr) => { + $v.map_err($crate::error::DatabaseError::Read)?.map($crate::utils::decoder::).transpose() + }; +} + +/// Cursor for navigating the items within a database. #[derive(Debug)] pub struct Cursor { /// Inner `libmdbx` cursor. - pub(crate) inner: libmdbx::Cursor, + inner: libmdbx::Cursor, /// Phantom data to enforce encoding/decoding. _dbi: PhantomData, } @@ -24,58 +32,76 @@ impl Cursor { } } -/// Takes `(key, value)` from the database and decodes it appropriately. -#[macro_export] -macro_rules! decode { - ($v:expr) => { - $v.map_err($crate::error::DatabaseError::Read)?.map($crate::utils::decoder::).transpose() - }; -} - -#[allow(clippy::should_implement_trait)] -impl Cursor { +impl Cursor { + /// Retrieves the first key/value pair, positioning the cursor at the first key/value pair in + /// the table. pub fn first(&mut self) -> Result>, DatabaseError> { decode!(libmdbx::Cursor::first(&mut self.inner)) } - pub fn seek_exact( - &mut self, - key: ::Key, - ) -> Result>, DatabaseError> { - decode!(libmdbx::Cursor::set_key(&mut self.inner, key.encode().as_ref())) - } - - pub fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { - decode!(libmdbx::Cursor::set_range(&mut self.inner, key.encode().as_ref())) + /// Retrieves key/value pair at current cursor position. + pub fn current(&mut self) -> Result>, DatabaseError> { + decode!(libmdbx::Cursor::get_current(&mut self.inner)) } + /// Retrieves the next key/value pair, positioning the cursor at the next key/value pair in + /// the table. + #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result>, DatabaseError> { decode!(libmdbx::Cursor::next(&mut self.inner)) } + /// Retrieves the previous key/value pair, positioning the cursor at the previous key/value pair + /// in the table. pub fn prev(&mut self) -> Result>, DatabaseError> { decode!(libmdbx::Cursor::prev(&mut self.inner)) } + /// Retrieves the last key/value pair, positioning the cursor at the last key/value pair in + /// the table. pub fn last(&mut self) -> Result>, DatabaseError> { decode!(libmdbx::Cursor::last(&mut self.inner)) } - pub fn current(&mut self) -> Result>, DatabaseError> { - decode!(libmdbx::Cursor::get_current(&mut self.inner)) + /// Set the cursor to the specified key, returning and positioning the cursor at the item if + /// found. + pub fn set(&mut self, key: ::Key) -> Result>, DatabaseError> { + decode!(libmdbx::Cursor::set_key(&mut self.inner, key.encode().as_ref())) } - /// Returns the next `(key, value)` pair of a DUPSORT table. - pub fn next_dup(&mut self) -> Result>, DatabaseError> { - decode!(libmdbx::Cursor::next_dup(&mut self.inner)) + /// Search for a `key` in a table, returning and positioning the cursor at the first item whose + /// key is greater than or equal to `key`. + pub fn seek(&mut self, key: ::Key) -> Result>, DatabaseError> { + decode!(libmdbx::Cursor::set_range(&mut self.inner, key.encode().as_ref())) } - /// Returns the next `(key, value)` pair skipping the duplicates. - pub fn next_no_dup(&mut self) -> Result>, DatabaseError> { - decode!(libmdbx::Cursor::next_nodup(&mut self.inner)) + /// Creates a walker to iterate over the table items. + /// + /// If `start_key` is `None`, the walker will start at the first item of the table. Otherwise, + /// it will start at the first item whose key is greater than or equal to `start_key`. + pub fn walk(&mut self, start_key: Option) -> Result, DatabaseError> { + let start = if let Some(start_key) = start_key { + self.inner + .set_range(start_key.encode().as_ref()) + .map_err(DatabaseError::Read)? + .map(decoder::) + } else { + self.first().transpose() + }; + + Ok(Walker::new(self, start)) + } +} + +impl Cursor { + /// Positions the cursor at next data item of current key, returning the next `key-value` + /// pair of a DUPSORT table. + pub fn next_dup(&mut self) -> Result>, DatabaseError> { + decode!(libmdbx::Cursor::next_dup(&mut self.inner)) } - /// Returns the next `value` of a duplicate `key`. + /// Similar to [`Self::next_dup()`], but instead of returning a `key-value` pair, it returns + /// only the `value`. pub fn next_dup_val(&mut self) -> Result::Value>, DatabaseError> { libmdbx::Cursor::next_dup(&mut self.inner) .map_err(DatabaseError::Read)? @@ -83,6 +109,13 @@ impl Cursor { .transpose() } + /// Returns the next key/value pair skipping the duplicates. + pub fn next_no_dup(&mut self) -> Result>, DatabaseError> { + decode!(libmdbx::Cursor::next_nodup(&mut self.inner)) + } + + /// Search for a `key` and `subkey` pair in a DUPSORT table. Positioning the cursor at the first + /// item whose `subkey` is greater than or equal to the specified `subkey`. pub fn seek_by_key_subkey( &mut self, key: ::Key, @@ -97,16 +130,63 @@ impl Cursor { .map(decode_one::) .transpose() } + + /// Depending on its arguments, returns an iterator starting at: + /// - Some(key), Some(subkey): a `key` item whose data is >= than `subkey` + /// - Some(key), None: first item of a specified `key` + /// - None, Some(subkey): like first case, but in the first key + /// - None, None: first item in the table + /// of a DUPSORT table. + pub fn walk_dup( + &mut self, + key: Option, + subkey: Option, + ) -> Result, DatabaseError> { + let start = match (key, subkey) { + (Some(key), Some(subkey)) => { + // encode key and decode it after. + let key: Vec = key.encode().into(); + self.inner + .get_both_range(key.as_ref(), subkey.encode().as_ref()) + .map_err(DatabaseError::Read)? + .map(|val| decoder::((Cow::Owned(key), val))) + } + + (Some(key), None) => { + let key: Vec = key.encode().into(); + self.inner + .set(key.as_ref()) + .map_err(DatabaseError::Read)? + .map(|val| decoder::((Cow::Owned(key), val))) + } + + (None, Some(subkey)) => { + if let Some((key, _)) = self.first()? { + let key: Vec = key.encode().into(); + self.inner + .get_both_range(key.as_ref(), subkey.encode().as_ref()) + .map_err(DatabaseError::Read)? + .map(|val| decoder::((Cow::Owned(key), val))) + } else { + Some(Err(DatabaseError::Read(libmdbx::Error::NotFound))) + } + } + + (None, None) => self.first().transpose(), + }; + + Ok(DupWalker::new(self, start)) + } } impl Cursor { /// Database operation that will update an existing row if a specified value already /// exists in a table, and insert a new row if the specified value doesn't already exist /// - /// For a DUPSORT table, `upsert` will not actually update-or-insert. If the key already exists, - /// it will append the value to the subkey, even if the subkeys are the same. So if you want - /// to properly upsert, you'll need to `seek_exact` & `delete_current` if the key+subkey was - /// found, before calling `upsert`. + /// For a `DUPSORT` table, `upsert` will not actually update-or-insert. If the key already + /// exists, it will append the value to the subkey, even if the subkeys are the same. So if + /// you want to properly upsert, you'll need to `seek_exact` & `delete_current` if the + /// key+subkey was found, before calling `upsert`. pub fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); @@ -119,6 +199,8 @@ impl Cursor { }) } + /// Puts a key/value pair into the database. The cursor will be positioned at the new data item, + /// or on failure, usually near it. pub fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = Encode::encode(key); let value = Compress::compress(value); @@ -150,12 +232,17 @@ impl Cursor { }) } + /// Deletes the current key/value pair. pub fn delete_current(&mut self) -> Result<(), DatabaseError> { libmdbx::Cursor::del(&mut self.inner, WriteFlags::CURRENT).map_err(DatabaseError::Delete) } } impl Cursor { + /// Deletes all values for the current key. + /// + /// This will delete all values for the current duplicate key of a `DUPSORT` table, including + /// the current item. pub fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> { libmdbx::Cursor::del(&mut self.inner, WriteFlags::NO_DUP_DATA) .map_err(DatabaseError::Delete) @@ -173,3 +260,83 @@ impl Cursor { }) } } + +/// A key-value pair coming from an iterator. +/// +/// The `Result` represents that the operation might fail, while the `Option` represents whether or +/// not there is an entry. +pub type IterPairResult = Option, DatabaseError>>; + +/// Provides an iterator to a `Cursor` when handling `Table`. +#[derive(Debug)] +pub struct Walker<'c, K: TransactionKind, T: Table> { + /// Cursor to be used to walk through the table. + cursor: &'c mut Cursor, + /// Initial position of the dup walker. The value (key/value pair) where to start the walk. + start: IterPairResult, +} + +impl<'c, K, T> Walker<'c, K, T> +where + K: TransactionKind, + T: Table, +{ + /// Create a new [`Walker`] from a [`Cursor`] and a [`IterPairResult`]. + pub(super) fn new(cursor: &'c mut Cursor, start: IterPairResult) -> Self { + Self { cursor, start } + } +} + +impl Walker<'_, RW, T> { + /// Delete the `key/value` pair item at the current position of the walker. + pub fn delete_current(&mut self) -> Result<(), DatabaseError> { + self.cursor.delete_current() + } +} + +impl std::iter::Iterator for Walker<'_, K, T> { + type Item = Result, DatabaseError>; + fn next(&mut self) -> Option { + if let value @ Some(_) = self.start.take() { value } else { self.cursor.next().transpose() } + } +} + +/// A cursor iterator for `DUPSORT` table. +/// +/// Similar to [`Walker`], but for `DUPSORT` table. +#[derive(Debug)] +pub struct DupWalker<'c, K: TransactionKind, T: DupSort> { + /// Cursor to be used to walk through the table. + cursor: &'c mut Cursor, + /// Initial position of the dup walker. The value (key/value pair) where to start the walk. + start: IterPairResult, +} + +impl<'c, K, T> DupWalker<'c, K, T> +where + K: TransactionKind, + T: DupSort, +{ + /// Creates a new [`DupWalker`] from a [`Cursor`] and a [`IterPairResult`]. + pub(super) fn new(cursor: &'c mut Cursor, start: IterPairResult) -> Self { + Self { cursor, start } + } +} + +impl DupWalker<'_, RW, T> { + /// Delete the item at the current position of the walker. + pub fn delete_current(&mut self) -> Result<(), DatabaseError> { + self.cursor.delete_current() + } +} + +impl std::iter::Iterator for DupWalker<'_, K, T> { + type Item = Result, DatabaseError>; + fn next(&mut self) -> Option { + if let value @ Some(_) = self.start.take() { + value + } else { + self.cursor.next_dup().transpose() + } + } +} diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 9e2fbdec23..8a2535830e 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -1,14 +1,13 @@ //! MDBX backend for the database. +//! +//! The code is adapted from `reth` mdbx implementation: pub mod cursor; pub mod tx; -use std::ops::Deref; use std::path::Path; -use libmdbx::{ - DatabaseFlags, Environment, EnvironmentFlags, Geometry, Mode, PageSize, SyncMode, RO, RW, -}; +use libmdbx::{DatabaseFlags, EnvironmentFlags, Geometry, Mode, PageSize, SyncMode, RO, RW}; use self::tx::Tx; use crate::error::DatabaseError; @@ -23,7 +22,7 @@ const DEFAULT_MAX_READERS: u64 = 32_000; /// Environment used when opening a MDBX environment. RO/RW. #[derive(Debug)] -pub enum EnvKind { +pub enum DbEnvKind { /// Read-only MDBX environment. RO, /// Read-write MDBX environment. @@ -38,10 +37,10 @@ impl DbEnv { /// Opens the database at the specified path with the given `EnvKind`. /// /// It does not create the tables, for that call [`DbEnv::create_tables`]. - pub fn open(path: &Path, kind: EnvKind) -> Result { + pub fn open(path: impl AsRef, kind: DbEnvKind) -> Result { let mode = match kind { - EnvKind::RO => Mode::ReadOnly, - EnvKind::RW => Mode::ReadWrite { sync_mode: SyncMode::Durable }, + DbEnvKind::RO => Mode::ReadOnly, + DbEnvKind::RW => Mode::ReadWrite { sync_mode: SyncMode::Durable }, }; let mut builder = libmdbx::Environment::builder(); @@ -66,12 +65,12 @@ impl DbEnv { }) .set_max_readers(DEFAULT_MAX_READERS); - Ok(DbEnv(builder.open(path).map_err(DatabaseError::OpenEnv)?)) + Ok(DbEnv(builder.open(path.as_ref()).map_err(DatabaseError::OpenEnv)?)) } /// Creates all the defined tables in [`Tables`], if necessary. pub fn create_tables(&self) -> Result<(), DatabaseError> { - let tx = self.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?; + let tx = self.0.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?; for table in Tables::ALL { let flags = match table.table_type() { @@ -86,9 +85,7 @@ impl DbEnv { Ok(()) } -} -impl DbEnv { /// Begin a read-only transaction. pub fn tx(&self) -> Result, DatabaseError> { Ok(Tx::new(self.0.begin_ro_txn().map_err(DatabaseError::CreateROTx)?)) @@ -99,10 +96,3 @@ impl DbEnv { Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?)) } } - -impl Deref for DbEnv { - type Target = Environment; - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/crates/katana/storage/db/src/mdbx/tx.rs b/crates/katana/storage/db/src/mdbx/tx.rs index cad52bcdd0..f1fbb37d77 100644 --- a/crates/katana/storage/db/src/mdbx/tx.rs +++ b/crates/katana/storage/db/src/mdbx/tx.rs @@ -3,44 +3,49 @@ use std::str::FromStr; use libmdbx::ffi::DBI; -use libmdbx::{Transaction, TransactionKind, WriteFlags, RW}; +use libmdbx::{TransactionKind, WriteFlags, RW}; use parking_lot::RwLock; use super::cursor::Cursor; use crate::codecs::{Compress, Encode}; use crate::error::DatabaseError; -use crate::tables::{DupSort, Table, Tables, NUM_TABLES}; +use crate::tables::{Table, Tables, NUM_TABLES}; use crate::utils::decode_one; +/// Alias for read-only transaction. +pub type TxRO = Tx; +/// Alias for read-write transaction. +pub type TxRW = Tx; + +/// Database transaction. +/// /// Wrapper for a `libmdbx` transaction. #[derive(Debug)] pub struct Tx { /// Libmdbx-sys transaction. - pub inner: libmdbx::Transaction, + inner: libmdbx::Transaction, /// Database table handle cache. - pub(crate) db_handles: RwLock<[Option; NUM_TABLES]>, + db_handles: RwLock<[Option; NUM_TABLES]>, } impl Tx { /// Creates new `Tx` object with a `RO` or `RW` transaction. - pub fn new(inner: Transaction) -> Self { + pub fn new(inner: libmdbx::Transaction) -> Self { Self { inner, db_handles: Default::default() } } - /// Create db Cursor - pub fn new_cursor(&self) -> Result, DatabaseError> { - let inner = self - .inner + /// Creates a cursor to iterate over a table items. + pub fn cursor(&self) -> Result, DatabaseError> { + self.inner .cursor_with_dbi(self.get_dbi::()?) - .map_err(DatabaseError::CreateCursor)?; - - Ok(Cursor::new(inner)) + .map(Cursor::new) + .map_err(DatabaseError::CreateCursor) } /// Gets a table database handle if it exists, otherwise creates it. pub fn get_dbi(&self) -> Result { let mut handles = self.db_handles.write(); - let table = Tables::from_str(T::NAME).expect("Requested table should be part of `Tables`."); + let table = Tables::from_str(T::NAME).expect("requested table should be part of `Tables`."); let dbi_handle = handles.get_mut(table as usize).expect("should exist"); if dbi_handle.is_none() { @@ -63,25 +68,24 @@ impl Tx { /// Returns number of entries in the table using cheap DB stats invocation. pub fn entries(&self) -> Result { - Ok(self - .inner + self.inner .db_stat_with_dbi(self.get_dbi::()?) - .map_err(DatabaseError::Stat)? - .entries()) + .map(|stat| stat.entries()) + .map_err(DatabaseError::Stat) } - // Creates a cursor to iterate over a table values. - pub fn cursor(&self) -> Result, DatabaseError> { - self.new_cursor() - } - - // Creates a cursor to iterate over a `DUPSORT` table values. - pub fn cursor_dup(&self) -> Result, DatabaseError> { - self.new_cursor() + /// Commits the transaction. + pub fn commit(self) -> Result { + self.inner.commit().map_err(DatabaseError::Commit) } } impl Tx { + /// Inserts an item into a database. + /// + /// This function stores key/data pairs in the database. The default behavior is to enter the + /// new key/data pair, replacing any previously existing key if duplicates are disallowed, or + /// adding a duplicate data item if duplicates are allowed (DatabaseFlags::DUP_SORT). pub fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> { let key = key.encode(); let value = value.compress(); @@ -89,6 +93,12 @@ impl Tx { Ok(()) } + /// Delete items from a database, removing the key/data pair if it exists. + /// + /// If the data parameter is [Some] only the matching data item will be deleted. Otherwise, if + /// data parameter is [None], any/all value(s) for specified key will be deleted. + /// + /// Returns `true` if the key/value pair was present. pub fn delete( &self, key: T::Key, @@ -99,26 +109,13 @@ impl Tx { self.inner.del(self.get_dbi::()?, key.encode(), value).map_err(DatabaseError::Delete) } + /// Clears all entries in the given database. This will emtpy the database. pub fn clear(&self) -> Result<(), DatabaseError> { self.inner.clear_db(self.get_dbi::()?).map_err(DatabaseError::Clear) } - /// Commits the transaction. - pub fn commit(self) -> Result { - self.inner.commit().map_err(DatabaseError::Commit) - } - /// Aborts the transaction. pub fn abort(self) { drop(self.inner) } } - -impl From> for Tx -where - K: TransactionKind, -{ - fn from(inner: libmdbx::Transaction) -> Self { - Tx::new(inner) - } -} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index f9bfed8503..82db42334a 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -11,11 +11,14 @@ use serde::{Deserialize, Serialize}; use crate::codecs::{Compress, Decode, Decompress, Encode}; use crate::models::block::StoredBlockBodyIndices; -pub trait Key: Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone {} -pub trait Value: Compress + Decompress {} +pub trait Key: + Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone + std::fmt::Debug +{ +} +pub trait Value: Compress + Decompress + std::fmt::Debug {} -impl Key for T where T: Serialize + for<'a> Deserialize<'a> + Clone {} -impl Value for T where T: Serialize + for<'a> Deserialize<'a> {} +impl Key for T where T: Serialize + for<'a> Deserialize<'a> + Clone + std::fmt::Debug {} +impl Value for T where T: Serialize + for<'a> Deserialize<'a> + std::fmt::Debug {} /// An asbtraction for a table. pub trait Table { From 194a1ecbb22bd716e7d9d4a55d6a62150e941e99 Mon Sep 17 00:00:00 2001 From: glihm Date: Mon, 11 Dec 2023 15:27:59 -0600 Subject: [PATCH 116/192] fix(dojo-lang): ensure macro expansion does not depend on prelude (#1260) * ensure the macro expansion does not depend on prelude * ensure the macro expansion does not depend on prelude * fix cairo tests * fix tests print expansion --- crates/dojo-lang/src/inline_macros/emit.rs | 7 +- crates/dojo-lang/src/inline_macros/get.rs | 25 ++++---- .../compiler_cairo_v240/Scarb.lock | 5 +- crates/dojo-lang/src/model.rs | 22 +++---- crates/dojo-lang/src/plugin_test_data/model | 64 +++++++++---------- crates/dojo-lang/src/plugin_test_data/print | 34 +++++----- crates/dojo-lang/src/print.rs | 4 +- examples/spawn-and-move/Scarb.toml | 3 + examples/spawn-and-move/src/models.cairo | 4 +- 9 files changed, 88 insertions(+), 80 deletions(-) diff --git a/crates/dojo-lang/src/inline_macros/emit.rs b/crates/dojo-lang/src/inline_macros/emit.rs index 8cd306775d..e85834aa30 100644 --- a/crates/dojo-lang/src/inline_macros/emit.rs +++ b/crates/dojo-lang/src/inline_macros/emit.rs @@ -24,8 +24,8 @@ impl InlineMacroExprPlugin for EmitMacro { let mut builder = PatchBuilder::new(db); builder.add_str( "{ - let mut keys = Default::::default(); - let mut data = Default::::default();", + let mut keys = Default::::default(); + let mut data = Default::::default();", ); let args = arg_list.arguments(db).elements(db); @@ -44,7 +44,8 @@ impl InlineMacroExprPlugin for EmitMacro { let event = &args[1]; builder.add_str( - "\n starknet::Event::append_keys_and_data(@traits::Into::<_, Event>::into(", + "\n starknet::Event::append_keys_and_data(@core::traits::Into::<_, \ + Event>::into(", ); builder.add_node(event.as_syntax_node()); builder.add_str("), ref keys, ref data);"); diff --git a/crates/dojo-lang/src/inline_macros/get.rs b/crates/dojo-lang/src/inline_macros/get.rs index cc249627f9..c5e6e9d2cd 100644 --- a/crates/dojo-lang/src/inline_macros/get.rs +++ b/crates/dojo-lang/src/inline_macros/get.rs @@ -30,7 +30,7 @@ impl InlineMacroExprPlugin for GetMacro { let mut builder = PatchBuilder::new(db); builder.add_str( "{ - let mut __get_macro_keys__ = array::ArrayTrait::new();\n", + let mut __get_macro_keys__ = core::array::ArrayTrait::new();\n", ); let args = arg_list.arguments(db).elements(db); @@ -78,8 +78,8 @@ impl InlineMacroExprPlugin for GetMacro { }; builder.add_str(&format!( - "serde::Serde::serialize(@{args}, ref __get_macro_keys__); - let __get_macro_keys__ = array::ArrayTrait::span(@__get_macro_keys__);\n" + "core::serde::Serde::serialize(@{args}, ref __get_macro_keys__); + let __get_macro_keys__ = core::array::ArrayTrait::span(@__get_macro_keys__);\n" )); let mut system_reads = SYSTEM_READS.lock().unwrap(); @@ -107,20 +107,23 @@ impl InlineMacroExprPlugin for GetMacro { deser_err_msg.truncate(CAIRO_ERR_MSG_LEN); builder.add_str(&format!( - "\n let mut __{model}_layout__ = array::ArrayTrait::new(); + "\n let mut __{model}_layout__ = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::<{model}>::layout(ref __{model}_layout__); let mut __{model}_layout_clone__ = __{model}_layout__.clone(); - let mut __{model}_layout_span__ = array::ArrayTrait::span(@__{model}_layout__); + let mut __{model}_layout_span__ = \ + core::array::ArrayTrait::span(@__{model}_layout__); let mut __{model}_layout_clone_span__ = \ - array::ArrayTrait::span(@__{model}_layout_clone__); + core::array::ArrayTrait::span(@__{model}_layout_clone__); let mut __{model}_values__ = {}.entity('{model}', __get_macro_keys__, 0_u8, dojo::packing::calculate_packed_size(ref __{model}_layout_clone_span__), __{model}_layout_span__); - let mut __{model}_model__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __{model}_model__); - array::serialize_array_helper(__{model}_values__, ref __{model}_model__); - let mut __{model}_model_span__ = array::ArrayTrait::span(@__{model}_model__); - let __{model} = option::OptionTrait::expect(serde::Serde::<{model}>::deserialize( + let mut __{model}_model__ = core::array::ArrayTrait::new(); + core::array::serialize_array_helper(__get_macro_keys__, ref __{model}_model__); + core::array::serialize_array_helper(__{model}_values__, ref __{model}_model__); + let mut __{model}_model_span__ = \ + core::array::ArrayTrait::span(@__{model}_model__); + let __{model} = \ + core::option::OptionTrait::expect(core::serde::Serde::<{model}>::deserialize( ref __{model}_model_span__ ), '{deser_err_msg}');\n", world.as_syntax_node().get_text(db), diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock index c4e6511205..2d90330d2f 100644 --- a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock @@ -10,11 +10,12 @@ dependencies = [ [[package]] name = "dojo" -version = "0.4.0-rc0" +version = "0.4.0-rc1" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.4.0" +version = "0.3.11" +source = "git+https://github.com/dojoengine/dojo?tag=v0.3.11#1e651b5d4d3b79b14a7d8aa29a92062fcb9e6659" diff --git a/crates/dojo-lang/src/model.rs b/crates/dojo-lang/src/model.rs index 2250db615e..d201b18587 100644 --- a/crates/dojo-lang/src/model.rs +++ b/crates/dojo-lang/src/model.rs @@ -50,13 +50,13 @@ pub fn handle_model_struct( if m.ty == "felt252" { return Some(RewriteNode::Text(format!( - "array::ArrayTrait::append(ref serialized, *self.{});", + "core::array::ArrayTrait::append(ref serialized, *self.{});", m.name ))); } Some(RewriteNode::Text(format!( - "serde::Serde::serialize(self.{}, ref serialized);", + "core::serde::Serde::serialize(self.{}, ref serialized);", m.name ))) }; @@ -81,23 +81,23 @@ pub fn handle_model_struct( #[inline(always)] fn keys(self: @$type_name$) -> Span { - let mut serialized = ArrayTrait::new(); + let mut serialized = core::array::ArrayTrait::new(); $serialized_keys$ - array::ArrayTrait::span(@serialized) + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn values(self: @$type_name$) -> Span { - let mut serialized = ArrayTrait::new(); + let mut serialized = core::array::ArrayTrait::new(); $serialized_values$ - array::ArrayTrait::span(@serialized) + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn layout(self: @$type_name$) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[inline(always)] @@ -133,7 +133,7 @@ pub fn handle_model_struct( #[external(v0)] fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) @@ -141,9 +141,9 @@ pub fn handle_model_struct( #[external(v0)] fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::<$type_name$>::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[external(v0)] diff --git a/crates/dojo-lang/src/plugin_test_data/model b/crates/dojo-lang/src/plugin_test_data/model index 2072615d16..f524306fb1 100644 --- a/crates/dojo-lang/src/plugin_test_data/model +++ b/crates/dojo-lang/src/plugin_test_data/model @@ -756,23 +756,23 @@ impl PositionSerde of core::serde::Serde:: { #[inline(always)] fn keys(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.id); - array::ArrayTrait::span(@serialized) + let mut serialized = core::array::ArrayTrait::new(); + core::array::ArrayTrait::append(ref serialized, *self.id); + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn values(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.v, ref serialized); - array::ArrayTrait::span(@serialized) + let mut serialized = core::array::ArrayTrait::new(); + core::serde::Serde::serialize(self.v, ref serialized); + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn layout(self: @Position) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[inline(always)] @@ -838,7 +838,7 @@ impl PositionIntrospect<> of dojo::database::introspect::Introspect> #[external(v0)] fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) @@ -846,9 +846,9 @@ impl PositionIntrospect<> of dojo::database::introspect::Introspect> #[external(v0)] fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[external(v0)] @@ -875,23 +875,23 @@ impl RolesSerde of core::serde::Serde:: { #[inline(always)] fn keys(self: @Roles) -> Span { - let mut serialized = ArrayTrait::new(); + let mut serialized = core::array::ArrayTrait::new(); - array::ArrayTrait::span(@serialized) + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn values(self: @Roles) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.role_ids, ref serialized); - array::ArrayTrait::span(@serialized) + let mut serialized = core::array::ArrayTrait::new(); + core::serde::Serde::serialize(self.role_ids, ref serialized); + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn layout(self: @Roles) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[inline(always)] @@ -953,7 +953,7 @@ impl RolesIntrospect<> of dojo::database::introspect::Introspect> { #[external(v0)] fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) @@ -961,9 +961,9 @@ impl RolesIntrospect<> of dojo::database::introspect::Introspect> { #[external(v0)] fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[external(v0)] @@ -996,23 +996,23 @@ impl PlayerSerde of core::serde::Serde:: { #[inline(always)] fn keys(self: @Player) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.game);serde::Serde::serialize(self.player, ref serialized); - array::ArrayTrait::span(@serialized) + let mut serialized = core::array::ArrayTrait::new(); + core::array::ArrayTrait::append(ref serialized, *self.game);core::serde::Serde::serialize(self.player, ref serialized); + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn values(self: @Player) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.name); - array::ArrayTrait::span(@serialized) + let mut serialized = core::array::ArrayTrait::new(); + core::array::ArrayTrait::append(ref serialized, *self.name); + core::array::ArrayTrait::span(@serialized) } #[inline(always)] fn layout(self: @Player) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[inline(always)] @@ -1082,7 +1082,7 @@ impl PlayerIntrospect<> of dojo::database::introspect::Introspect> { #[external(v0)] fn packed_size(self: @ContractState) -> usize { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); let mut layout_span = layout.span(); dojo::packing::calculate_packed_size(ref layout_span) @@ -1090,9 +1090,9 @@ impl PlayerIntrospect<> of dojo::database::introspect::Introspect> { #[external(v0)] fn layout(self: @ContractState) -> Span { - let mut layout = ArrayTrait::new(); + let mut layout = core::array::ArrayTrait::new(); dojo::database::introspect::Introspect::::layout(ref layout); - array::ArrayTrait::span(@layout) + core::array::ArrayTrait::span(@layout) } #[external(v0)] diff --git a/crates/dojo-lang/src/plugin_test_data/print b/crates/dojo-lang/src/plugin_test_data/print index 394b02bad8..d7ed7801d2 100644 --- a/crates/dojo-lang/src/plugin_test_data/print +++ b/crates/dojo-lang/src/plugin_test_data/print @@ -46,14 +46,14 @@ struct Position { } #[cfg(test)] -impl PositionPrintImpl of debug::PrintTrait { +impl PositionPrintImpl of core::debug::PrintTrait { fn print(self: Position) { - debug::PrintTrait::print('id'); - debug::PrintTrait::print(self.id); - debug::PrintTrait::print('x'); - debug::PrintTrait::print(self.x); - debug::PrintTrait::print('y'); - debug::PrintTrait::print(self.y); + core::debug::PrintTrait::print('id'); + core::debug::PrintTrait::print(self.id); + core::debug::PrintTrait::print('x'); + core::debug::PrintTrait::print(self.x); + core::debug::PrintTrait::print('y'); + core::debug::PrintTrait::print(self.y); } } @@ -64,10 +64,10 @@ struct Roles { } #[cfg(test)] -impl RolesPrintImpl of debug::PrintTrait { +impl RolesPrintImpl of core::debug::PrintTrait { fn print(self: Roles) { - debug::PrintTrait::print('role_ids'); - debug::PrintTrait::print(self.role_ids); + core::debug::PrintTrait::print('role_ids'); + core::debug::PrintTrait::print(self.role_ids); } } @@ -85,14 +85,14 @@ struct Player { name: felt252, } #[cfg(test)] -impl PlayerPrintImpl of debug::PrintTrait { +impl PlayerPrintImpl of core::debug::PrintTrait { fn print(self: Player) { - debug::PrintTrait::print('game'); - debug::PrintTrait::print(self.game); - debug::PrintTrait::print('player'); - debug::PrintTrait::print(self.player); - debug::PrintTrait::print('name'); - debug::PrintTrait::print(self.name); + core::debug::PrintTrait::print('game'); + core::debug::PrintTrait::print(self.game); + core::debug::PrintTrait::print('player'); + core::debug::PrintTrait::print(self.player); + core::debug::PrintTrait::print('name'); + core::debug::PrintTrait::print(self.name); } } diff --git a/crates/dojo-lang/src/print.rs b/crates/dojo-lang/src/print.rs index a4dd13c112..06136b592e 100644 --- a/crates/dojo-lang/src/print.rs +++ b/crates/dojo-lang/src/print.rs @@ -17,7 +17,7 @@ pub fn derive_print(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> RewriteNode .iter() .map(|m| { format!( - "debug::PrintTrait::print('{}'); debug::PrintTrait::print(self.{});", + "core::debug::PrintTrait::print('{}'); core::debug::PrintTrait::print(self.{});", m.name(db).text(db).to_string(), m.name(db).text(db).to_string() ) @@ -26,7 +26,7 @@ pub fn derive_print(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> RewriteNode RewriteNode::interpolate_patched( "#[cfg(test)] - impl $type_name$PrintImpl of debug::PrintTrait<$type_name$> { + impl $type_name$PrintImpl of core::debug::PrintTrait<$type_name$> { fn print(self: $type_name$) { $print$ } diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 0e594f379c..ec78dec2d8 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -2,6 +2,9 @@ cairo-version = "2.4.0" name = "dojo_examples" version = "0.4.0-rc0" +# Use the prelude with the less imports as possible +# from corelib. +edition = "2023_10" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/src/models.cairo b/examples/spawn-and-move/src/models.cairo index 657c366fdb..8c8a275bed 100644 --- a/examples/spawn-and-move/src/models.cairo +++ b/examples/spawn-and-move/src/models.cairo @@ -1,4 +1,4 @@ -use array::ArrayTrait; +use core::array::ArrayTrait; use core::debug::PrintTrait; use starknet::ContractAddress; @@ -64,7 +64,7 @@ impl Vec2Impl of Vec2Trait { #[cfg(test)] mod tests { - use debug::PrintTrait; + use core::debug::PrintTrait; use super::{Position, Vec2, Vec2Trait}; #[test] From d806ec93754b2e9a01a6ea541417a727b3bfa553 Mon Sep 17 00:00:00 2001 From: Junichi Sugiura Date: Tue, 12 Dec 2023 18:07:50 +0100 Subject: [PATCH 117/192] Expose world contract address via metadatas connection (#1263) Adapt deployment query to lower camel case --- crates/torii/graphql/src/mapping.rs | 1 + .../torii/graphql/src/object/metadata/mod.rs | 30 ++++++++++++++----- crates/torii/graphql/src/query/data.rs | 6 ++++ .../torii/graphql/src/tests/metadata_test.rs | 2 ++ crates/torii/graphql/src/tests/mod.rs | 1 + 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 64c49aa0ab..70e1a9b85a 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -103,6 +103,7 @@ lazy_static! { pub static ref METADATA_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), (Name::new("uri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("worldAddress"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), ( Name::new("content"), TypeData::Nested((TypeRef::named(CONTENT_TYPE_NAME), IndexMap::new())) diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index 94e8171d4a..f1153eefcc 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -11,7 +11,7 @@ use crate::constants::{ ID_COLUMN, JSON_COLUMN, METADATA_NAMES, METADATA_TABLE, METADATA_TYPE_NAME, }; use crate::mapping::METADATA_TYPE_MAPPING; -use crate::query::data::{count_rows, fetch_multiple_rows}; +use crate::query::data::{count_rows, fetch_multiple_rows, fetch_world_address}; use crate::query::value_mapping_from_row; use crate::types::{TypeMapping, ValueMapping}; @@ -20,6 +20,14 @@ pub mod social; pub struct MetadataObject; +impl MetadataObject { + fn row_types(&self) -> TypeMapping { + let mut row_types = self.type_mapping().clone(); + row_types.remove("worldAddress"); + row_types + } +} + impl ObjectTrait for MetadataObject { fn name(&self) -> (&str, &str) { METADATA_NAMES @@ -42,14 +50,14 @@ impl ObjectTrait for MetadataObject { } fn resolve_many(&self) -> Option { - let type_mapping = self.type_mapping().clone(); let table_name = self.table_name().unwrap().to_string(); + let row_types = self.row_types(); let mut field = Field::new( self.name().1, TypeRef::named(format!("{}Connection", self.type_name())), move |ctx| { - let type_mapping = type_mapping.clone(); + let row_types = row_types.clone(); let table_name = table_name.to_string(); FieldFuture::new(async move { @@ -67,10 +75,16 @@ impl ObjectTrait for MetadataObject { total_count, ) .await?; + let world_address = fetch_world_address(&mut conn).await?; // convert json field to value_mapping expected by content object - let results = - metadata_connection_output(&data, &type_mapping, total_count, page_info)?; + let results = metadata_connection_output( + &data, + &row_types, + total_count, + page_info, + &world_address, + )?; Ok(Some(Value::Object(results))) }) @@ -87,16 +101,18 @@ impl ObjectTrait for MetadataObject { // objects AND dynamic model objects fn metadata_connection_output( data: &[SqliteRow], - types: &TypeMapping, + row_types: &TypeMapping, total_count: i64, page_info: PageInfo, + world_address: &String, ) -> sqlx::Result { let edges = data .iter() .map(|row| { let order = row.try_get::(ID_COLUMN)?; let cursor = cursor::encode(&order, &order); - let mut value_mapping = value_mapping_from_row(row, types, false)?; + let mut value_mapping = value_mapping_from_row(row, row_types, false)?; + value_mapping.insert(Name::new("worldAddress"), Value::from(world_address)); let json_str = row.try_get::(JSON_COLUMN)?; let serde_value = serde_json::from_str(&json_str).unwrap_or_default(); diff --git a/crates/torii/graphql/src/query/data.rs b/crates/torii/graphql/src/query/data.rs index 80e0c80dc0..aee42dd64f 100644 --- a/crates/torii/graphql/src/query/data.rs +++ b/crates/torii/graphql/src/query/data.rs @@ -24,6 +24,12 @@ pub async fn count_rows( Ok(result.0) } +pub async fn fetch_world_address(conn: &mut SqliteConnection) -> Result { + let query = "SELECT world_address FROM worlds".to_string(); + let res: (String,) = sqlx::query_as(&query).fetch_one(conn).await?; + Ok(res.0) +} + pub async fn fetch_single_row( conn: &mut SqliteConnection, table_name: &str, diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index 47c16e2949..3f62dd56c3 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -18,6 +18,7 @@ mod tests { cursor node { uri + worldAddress coverImg iconImg content { @@ -69,6 +70,7 @@ mod tests { let value = result.get("metadatas").ok_or("metadatas not found").unwrap().clone(); let connection: Connection = serde_json::from_value(value).unwrap(); let edge = connection.edges.first().unwrap(); + assert_eq!(edge.node.world_address, "0x0"); assert_eq!(connection.edges.len(), 1); assert_eq!(edge.node.cover_img, cover_img); assert_eq!( diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 667a47ea17..0f60db5237 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -145,6 +145,7 @@ pub struct Content { #[serde(rename_all = "camelCase")] pub struct Metadata { pub uri: String, + pub world_address: String, pub icon_img: String, pub cover_img: String, pub content: Content, From a05af9e86e6dba0a01eeb487503e0eb9ddd681db Mon Sep 17 00:00:00 2001 From: Junichi Sugiura Date: Tue, 12 Dec 2023 23:01:00 +0100 Subject: [PATCH 118/192] Change metadata content casing from lower camel to snake (#1264) * Change metadata content casing from lower camel to snake * Respect snake case on ipfs json --- crates/torii/graphql/src/mapping.rs | 4 ++-- crates/torii/graphql/src/object/metadata/mod.rs | 8 +++++--- crates/torii/graphql/src/tests/metadata_test.rs | 4 ++-- crates/torii/graphql/src/tests/mod.rs | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 70e1a9b85a..9e139e3b48 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -95,8 +95,8 @@ lazy_static! { (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("description"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("website"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("icon_uri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("cover_uri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("iconUri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("coverUri"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), (Name::new("socials"), TypeData::Simple(TypeRef::named_list(SOCIAL_TYPE_NAME))) ]); // Todo: refactor this to use the same type as the one in dojo-world diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index f1153eefcc..cbb46c8c79 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -1,6 +1,7 @@ use async_graphql::connection::PageInfo; use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; use async_graphql::{Name, Value}; +use convert_case::{Case, Casing}; use sqlx::sqlite::SqliteRow; use sqlx::{Pool, Row, Sqlite}; @@ -145,11 +146,12 @@ fn metadata_connection_output( } fn extract_str_mapping(name: &str, serde_value: &serde_json::Value) -> (Name, Value) { + let name_lower_camel = name.to_case(Case::Camel); if let Some(serde_json::Value::String(str)) = serde_value.get(name) { - return (Name::new(name), Value::String(str.to_owned())); + (Name::new(name_lower_camel), Value::String(str.to_owned())) + } else { + (Name::new(name_lower_camel), Value::Null) } - - (Name::new(name), Value::Null) } fn extract_socials_mapping(name: &str, serde_value: &serde_json::Value) -> (Name, Value) { diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index 3f62dd56c3..aa4714008d 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -24,8 +24,8 @@ mod tests { content { name description - cover_uri - icon_uri + coverUri + iconUri website socials { name diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 0f60db5237..b8df16fb89 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -132,6 +132,7 @@ pub struct Social { } #[derive(Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct Content { pub name: Option, pub description: Option, From 072a26b188a35801eebb7855028c9891fadf40f9 Mon Sep 17 00:00:00 2001 From: Yun Date: Tue, 12 Dec 2023 18:34:40 -0800 Subject: [PATCH 119/192] Update grpc entities/models subscriptions (#1262) * Torii client/grpc rename subscription * entity sub all * torii client updates * rename to model diff * use bytes instead of string for ids --- Cargo.toml | 2 +- crates/torii/client/src/client/mod.rs | 101 +++++------ crates/torii/client/src/client/storage.rs | 44 ++--- .../torii/client/src/client/subscription.rs | 96 +++++------ crates/torii/grpc/proto/types.proto | 10 +- crates/torii/grpc/proto/world.proto | 28 ++- crates/torii/grpc/src/client.rs | 65 +++++-- crates/torii/grpc/src/server/mod.rs | 91 ++++++---- .../grpc/src/server/subscriptions/entity.rs | 159 ++++++++++++++++++ .../src/server/{ => subscriptions}/error.rs | 0 .../grpc/src/server/subscriptions/mod.rs | 3 + .../model_diff.rs} | 54 +++--- crates/torii/grpc/src/types/mod.rs | 10 +- crates/torii/grpc/src/types/schema.rs | 2 +- 14 files changed, 449 insertions(+), 216 deletions(-) create mode 100644 crates/torii/grpc/src/server/subscriptions/entity.rs rename crates/torii/grpc/src/server/{ => subscriptions}/error.rs (100%) create mode 100644 crates/torii/grpc/src/server/subscriptions/mod.rs rename crates/torii/grpc/src/server/{subscription.rs => subscriptions/model_diff.rs} (84%) diff --git a/Cargo.toml b/Cargo.toml index 28956f6620..a8d28b933c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ salsa = "0.16.1" scarb = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } scarb-ui = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } semver = "1.0.5" -serde = { version = "1.0.156", features = [ "derive" ] } +serde = { version = "1.0.192", features = [ "derive" ] } serde_json = "1.0" serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index 08c2f1694d..a79ba11ac5 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -16,15 +16,16 @@ use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet_crypto::FieldElement; use tokio::sync::RwLock as AsyncRwLock; -use torii_grpc::client::EntityUpdateStreaming; +use torii_grpc::client::{EntityUpdateStreaming, ModelDiffsStreaming}; use torii_grpc::proto::world::RetrieveEntitiesResponse; use torii_grpc::types::schema::Entity; use torii_grpc::types::{KeysClause, Query}; -use self::error::{Error, ParseError}; -use self::storage::ModelStorage; -use self::subscription::{SubscribedEntities, SubscriptionClientHandle}; -use crate::client::subscription::SubscriptionService; +use crate::client::error::{Error, ParseError}; +use crate::client::storage::ModelStorage; +use crate::client::subscription::{ + SubscribedModels, SubscriptionClientHandle, SubscriptionService, +}; // TODO: remove reliance on RPC #[allow(unused)] @@ -33,10 +34,10 @@ pub struct Client { metadata: Arc>, /// The grpc client. inner: AsyncRwLock, - /// Entity storage + /// Model storage storage: Arc, - /// Entities the client are subscribed to. - subscribed_entities: Arc, + /// Models the client are subscribed to. + subscribed_models: Arc, /// The subscription client handle. sub_client_handle: OnceCell, /// World contract reader. @@ -49,7 +50,7 @@ impl Client { torii_url: String, rpc_url: String, world: FieldElement, - entities_keys: Option>, + models_keys: Option>, ) -> Result { let mut grpc_client = torii_grpc::client::WorldClient::new(torii_url, world).await?; @@ -57,23 +58,23 @@ impl Client { let shared_metadata: Arc<_> = RwLock::new(metadata).into(); let client_storage: Arc<_> = ModelStorage::new(shared_metadata.clone()).into(); - let subbed_entities: Arc<_> = SubscribedEntities::new(shared_metadata.clone()).into(); + let subbed_models: Arc<_> = SubscribedModels::new(shared_metadata.clone()).into(); // initialize the entities to be synced with the latest values let rpc_url = url::Url::parse(&rpc_url).map_err(ParseError::Url)?; let provider = JsonRpcClient::new(HttpTransport::new(rpc_url)); let world_reader = WorldContractReader::new(world, provider); - if let Some(keys) = entities_keys { - subbed_entities.add_entities(keys)?; + if let Some(keys) = models_keys { + subbed_models.add_models(keys)?; // TODO: change this to querying the gRPC url instead - let subbed_entities = subbed_entities.entities_keys.read().clone(); - for keys in subbed_entities { + let subbed_models = subbed_models.models_keys.read().clone(); + for keys in subbed_models { let model_reader = world_reader.model(&keys.model).await?; let values = model_reader.entity_storage(&keys.keys).await?; - client_storage.set_entity_storage( + client_storage.set_model_storage( cairo_short_string_to_felt(&keys.model).unwrap(), keys.keys, values, @@ -87,7 +88,7 @@ impl Client { metadata: shared_metadata, sub_client_handle: OnceCell::new(), inner: AsyncRwLock::new(grpc_client), - subscribed_entities: subbed_entities, + subscribed_models: subbed_models, }) } @@ -96,8 +97,8 @@ impl Client { self.metadata.read() } - pub fn subscribed_entities(&self) -> RwLockReadGuard<'_, HashSet> { - self.subscribed_entities.entities_keys.read() + pub fn subscribed_models(&self) -> RwLockReadGuard<'_, HashSet> { + self.subscribed_models.models_keys.read() } /// Retrieves entities matching query parameter. @@ -112,25 +113,35 @@ impl Client { Ok(entities.into_iter().map(TryInto::try_into).collect::, _>>()?) } - /// Returns the model value of an entity. + /// A direct stream to grpc subscribe entities + pub async fn on_entity_updated( + &self, + ids: Vec, + ) -> Result { + let mut grpc_client = self.inner.write().await; + let stream = grpc_client.subscribe_entities(ids).await?; + Ok(stream) + } + + /// Returns the value of a model. /// - /// This function will only return `None`, if `model` doesn't exist. If there is no entity with + /// This function will only return `None`, if `model` doesn't exist. If there is no model with /// the specified `keys`, it will return a [`Ty`] with the default values. /// - /// If the requested entity is not among the synced entities, it will attempt to fetch it from + /// If the requested model is not among the synced models, it will attempt to fetch it from /// the RPC. - pub async fn entity(&self, keys: &KeysClause) -> Result, Error> { + pub async fn model(&self, keys: &KeysClause) -> Result, Error> { let Some(mut schema) = self.metadata.read().model(&keys.model).map(|m| m.schema.clone()) else { return Ok(None); }; - if !self.subscribed_entities.is_synced(keys) { + if !self.subscribed_models.is_synced(keys) { let model = self.world_reader.model(&keys.model).await?; return Ok(Some(model.entity(&keys.keys).await?)); } - let Ok(Some(raw_values)) = self.storage.get_entity_storage( + let Ok(Some(raw_values)) = self.storage.get_model_storage( cairo_short_string_to_felt(&keys.model).map_err(ParseError::CairoShortStringToFelt)?, &keys.keys, ) else { @@ -152,17 +163,16 @@ impl Client { Ok(Some(schema)) } - /// Initiate the entity subscriptions and returns a [SubscriptionService] which when await'ed + /// Initiate the model subscriptions and returns a [SubscriptionService] which when await'ed /// will execute the subscription service and starts the syncing process. pub async fn start_subscription(&self) -> Result { - let entities_keys = - self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); - let sub_res_stream = self.initiate_subscription(entities_keys).await?; + let models_keys = self.subscribed_models.models_keys.read().clone().into_iter().collect(); + let sub_res_stream = self.initiate_subscription(models_keys).await?; let (service, handle) = SubscriptionService::new( Arc::clone(&self.storage), Arc::clone(&self.metadata), - Arc::clone(&self.subscribed_entities), + Arc::clone(&self.subscribed_models), sub_res_stream, ); @@ -173,16 +183,16 @@ impl Client { /// Adds entities to the list of entities to be synced. /// /// NOTE: This will establish a new subscription stream with the server. - pub async fn add_entities_to_sync(&self, entities_keys: Vec) -> Result<(), Error> { - for keys in &entities_keys { - self.initiate_entity(&keys.model, keys.keys.clone()).await?; + pub async fn add_models_to_sync(&self, models_keys: Vec) -> Result<(), Error> { + for keys in &models_keys { + self.initiate_model(&keys.model, keys.keys.clone()).await?; } - self.subscribed_entities.add_entities(entities_keys)?; + self.subscribed_models.add_models(models_keys)?; - let updated_entities = - self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); - let sub_res_stream = self.initiate_subscription(updated_entities).await?; + let updated_models = + self.subscribed_models.models_keys.read().clone().into_iter().collect(); + let sub_res_stream = self.initiate_subscription(updated_models).await?; match self.sub_client_handle.get() { Some(handle) => handle.update_subscription_stream(sub_res_stream), @@ -191,17 +201,14 @@ impl Client { Ok(()) } - /// Removes entities from the list of entities to be synced. + /// Removes models from the list of models to be synced. /// /// NOTE: This will establish a new subscription stream with the server. - pub async fn remove_entities_to_sync( - &self, - entities_keys: Vec, - ) -> Result<(), Error> { - self.subscribed_entities.remove_entities(entities_keys)?; + pub async fn remove_models_to_sync(&self, models_keys: Vec) -> Result<(), Error> { + self.subscribed_models.remove_models(models_keys)?; let updated_entities = - self.subscribed_entities.entities_keys.read().clone().into_iter().collect(); + self.subscribed_models.models_keys.read().clone().into_iter().collect(); let sub_res_stream = self.initiate_subscription(updated_entities).await?; match self.sub_client_handle.get() { @@ -218,16 +225,16 @@ impl Client { async fn initiate_subscription( &self, keys: Vec, - ) -> Result { + ) -> Result { let mut grpc_client = self.inner.write().await; - let stream = grpc_client.subscribe_entities(keys).await?; + let stream = grpc_client.subscribe_model_diffs(keys).await?; Ok(stream) } - async fn initiate_entity(&self, model: &str, keys: Vec) -> Result<(), Error> { + async fn initiate_model(&self, model: &str, keys: Vec) -> Result<(), Error> { let model_reader = self.world_reader.model(model).await?; let values = model_reader.entity_storage(&keys).await?; - self.storage.set_entity_storage( + self.storage.set_model_storage( cairo_short_string_to_felt(model).map_err(ParseError::CairoShortStringToFelt)?, keys, values, diff --git a/crates/torii/client/src/client/storage.rs b/crates/torii/client/src/client/storage.rs index d56b7db506..a647f6cf64 100644 --- a/crates/torii/client/src/client/storage.rs +++ b/crates/torii/client/src/client/storage.rs @@ -21,7 +21,7 @@ pub type StorageValue = FieldElement; pub struct ModelStorage { metadata: Arc>, storage: RwLock>, - // a map of model name to a set of entity keys. + // a map of model name to a set of model keys. model_index: RwLock>>, // listener for storage updates. @@ -40,11 +40,11 @@ impl ModelStorage { } } - /// Listen to entity changes. + /// Listen to model changes. /// /// # Arguments /// * `model` - the model name. - /// * `keys` - the keys of the entity. + /// * `keys` - the keys of the model. /// /// # Returns /// A receiver that will receive updates for the specified storage keys. @@ -53,7 +53,7 @@ impl ModelStorage { model: FieldElement, keys: &[FieldElement], ) -> Result, Error> { - let storage_addresses = self.get_entity_storage_addresses(model, keys)?; + let storage_addresses = self.get_model_storage_addresses(model, keys)?; let (sender, receiver) = channel(128); let listener_id = self.senders.lock().len() as u8; @@ -66,21 +66,21 @@ impl ModelStorage { Ok(receiver) } - /// Retrieves the raw values of an entity. - pub fn get_entity_storage( + /// Retrieves the raw values of an model. + pub fn get_model_storage( &self, model: FieldElement, raw_keys: &[FieldElement], ) -> Result>, Error> { - let storage_addresses = self.get_entity_storage_addresses(model, raw_keys)?; + let storage_addresses = self.get_model_storage_addresses(model, raw_keys)?; Ok(storage_addresses .into_iter() .map(|storage_address| self.storage.read().get(&storage_address).copied()) .collect::>>()) } - /// Set the raw values of an entity. - pub fn set_entity_storage( + /// Set the raw values of an model. + pub fn set_model_storage( &self, model: FieldElement, raw_keys: Vec, @@ -108,18 +108,18 @@ impl ModelStorage { Ordering::Equal => {} } - let storage_addresses = self.get_entity_storage_addresses(model, &raw_keys)?; + let storage_addresses = self.get_model_storage_addresses(model, &raw_keys)?; self.set_storages_at(storage_addresses.into_iter().zip(raw_values).collect()); - self.index_entity(model, raw_keys); + self.index_model(model, raw_keys); Ok(()) } /// Set the value of storage slots in bulk - pub(super) fn set_storages_at(&self, storage_entries: Vec<(FieldElement, FieldElement)>) { + pub(super) fn set_storages_at(&self, storage_models: Vec<(FieldElement, FieldElement)>) { let mut senders: HashSet = Default::default(); - for (key, _) in &storage_entries { + for (key, _) in &storage_models { if let Some(lists) = self.listeners.lock().get(key) { for id in lists { senders.insert(*id); @@ -127,7 +127,7 @@ impl ModelStorage { } } - self.storage.write().extend(storage_entries); + self.storage.write().extend(storage_models); for sender_id in senders { self.notify_listener(sender_id); @@ -140,7 +140,7 @@ impl ModelStorage { } } - fn get_entity_storage_addresses( + fn get_model_storage_addresses( &self, model: FieldElement, raw_keys: &[FieldElement], @@ -158,7 +158,7 @@ impl ModelStorage { Ok(compute_all_storage_addresses(model, raw_keys, model_packed_size)) } - fn index_entity(&self, model: FieldElement, raw_keys: Vec) { + fn index_model(&self, model: FieldElement, raw_keys: Vec) { self.model_index.write().entry(model).or_default().insert(raw_keys); } } @@ -204,7 +204,7 @@ mod tests { let keys = vec![felt!("0x12345")]; let values = vec![felt!("1"), felt!("2"), felt!("3"), felt!("4"), felt!("5")]; let model = cairo_short_string_to_felt("Position").unwrap(); - let result = storage.set_entity_storage(model, keys, values); + let result = storage.set_model_storage(model, keys, values); assert!(storage.storage.read().is_empty()); matches!( @@ -219,7 +219,7 @@ mod tests { let keys = vec![felt!("0x12345")]; let values = vec![felt!("1"), felt!("2")]; let model = cairo_short_string_to_felt("Position").unwrap(); - let result = storage.set_entity_storage(model, keys, values); + let result = storage.set_model_storage(model, keys, values); assert!(storage.storage.read().is_empty()); matches!( @@ -229,7 +229,7 @@ mod tests { } #[test] - fn set_and_get_entity_value() { + fn set_and_get_model_value() { let storage = create_dummy_storage(); let keys = vec![felt!("0x12345")]; @@ -246,11 +246,11 @@ mod tests { let model_name_in_felt = cairo_short_string_to_felt("Position").unwrap(); storage - .set_entity_storage(model_name_in_felt, keys.clone(), expected_values.clone()) + .set_model_storage(model_name_in_felt, keys.clone(), expected_values.clone()) .expect("set storage values"); let actual_values = storage - .get_entity_storage(model_name_in_felt, &keys) + .get_model_storage(model_name_in_felt, &keys) .expect("model exist") .expect("values are set"); @@ -259,7 +259,7 @@ mod tests { assert!( storage.model_index.read().get(&model_name_in_felt).is_some_and(|e| e.contains(&keys)), - "entity keys must be indexed" + "model keys must be indexed" ); assert!(actual_values == expected_values); assert!(storage.storage.read().len() == model.packed_size as usize); diff --git a/crates/torii/client/src/client/subscription.rs b/crates/torii/client/src/client/subscription.rs index 898d008130..fccb86ce50 100644 --- a/crates/torii/client/src/client/subscription.rs +++ b/crates/torii/client/src/client/subscription.rs @@ -11,53 +11,53 @@ use parking_lot::{Mutex, RwLock}; use starknet::core::types::{StateDiff, StateUpdate}; use starknet::core::utils::cairo_short_string_to_felt; use starknet_crypto::FieldElement; -use torii_grpc::client::EntityUpdateStreaming; +use torii_grpc::client::ModelDiffsStreaming; use torii_grpc::types::KeysClause; -use super::error::{Error, ParseError}; -use super::ModelStorage; +use crate::client::error::{Error, ParseError}; +use crate::client::storage::ModelStorage; use crate::utils::compute_all_storage_addresses; pub enum SubscriptionEvent { - UpdateSubsciptionStream(EntityUpdateStreaming), + UpdateSubsciptionStream(ModelDiffsStreaming), } -pub struct SubscribedEntities { +pub struct SubscribedModels { metadata: Arc>, - pub(super) entities_keys: RwLock>, - /// All the relevant storage addresses derived from the subscribed entities - pub(super) subscribed_storage_addresses: RwLock>, + pub(crate) models_keys: RwLock>, + /// All the relevant storage addresses derived from the subscribed models + pub(crate) subscribed_storage_addresses: RwLock>, } -impl SubscribedEntities { - pub(super) fn is_synced(&self, keys: &KeysClause) -> bool { - self.entities_keys.read().contains(keys) +impl SubscribedModels { + pub(crate) fn is_synced(&self, keys: &KeysClause) -> bool { + self.models_keys.read().contains(keys) } - pub(super) fn new(metadata: Arc>) -> Self { + pub(crate) fn new(metadata: Arc>) -> Self { Self { metadata, - entities_keys: Default::default(), + models_keys: Default::default(), subscribed_storage_addresses: Default::default(), } } - pub(super) fn add_entities(&self, entities_keys: Vec) -> Result<(), Error> { - for keys in entities_keys { - Self::add_entity(self, keys)?; + pub(crate) fn add_models(&self, models_keys: Vec) -> Result<(), Error> { + for keys in models_keys { + Self::add_model(self, keys)?; } Ok(()) } - pub(super) fn remove_entities(&self, entities_keys: Vec) -> Result<(), Error> { + pub(crate) fn remove_models(&self, entities_keys: Vec) -> Result<(), Error> { for keys in entities_keys { - Self::remove_entity(self, keys)?; + Self::remove_model(self, keys)?; } Ok(()) } - pub(super) fn add_entity(&self, keys: KeysClause) -> Result<(), Error> { - if !self.entities_keys.write().insert(keys.clone()) { + pub(crate) fn add_model(&self, keys: KeysClause) -> Result<(), Error> { + if !self.models_keys.write().insert(keys.clone()) { return Ok(()); } @@ -83,8 +83,8 @@ impl SubscribedEntities { Ok(()) } - pub(super) fn remove_entity(&self, keys: KeysClause) -> Result<(), Error> { - if !self.entities_keys.write().remove(&keys) { + pub(crate) fn remove_model(&self, keys: KeysClause) -> Result<(), Error> { + if !self.models_keys.write().remove(&keys) { return Ok(()); } @@ -119,7 +119,7 @@ impl SubscriptionClientHandle { Self(Mutex::new(sender)) } - pub(crate) fn update_subscription_stream(&self, stream: EntityUpdateStreaming) { + pub(crate) fn update_subscription_stream(&self, stream: ModelDiffsStreaming) { let _ = self.0.lock().try_send(SubscriptionEvent::UpdateSubsciptionStream(stream)); } } @@ -127,37 +127,37 @@ impl SubscriptionClientHandle { #[must_use = "SubscriptionClient does nothing unless polled"] pub struct SubscriptionService { req_rcv: Receiver, - /// The stream returned by the subscription server to receive the response - sub_res_stream: RefCell>, + /// Model Diff stream by subscription server to receive response + model_diffs_stream: RefCell>, /// Callback to be called on error err_callback: Option>, - // for processing the entity diff and updating the storage + // for processing the model diff and updating the storage storage: Arc, world_metadata: Arc>, - subscribed_entities: Arc, + subscribed_models: Arc, } impl SubscriptionService { - pub(super) fn new( + pub(crate) fn new( storage: Arc, world_metadata: Arc>, - subscribed_entities: Arc, - sub_res_stream: EntityUpdateStreaming, + subscribed_models: Arc, + model_diffs_stream: ModelDiffsStreaming, ) -> (Self, SubscriptionClientHandle) { let (req_sender, req_rcv) = mpsc::channel(128); let handle = SubscriptionClientHandle::new(req_sender); - let sub_res_stream = RefCell::new(Some(sub_res_stream)); + let model_diffs_stream = RefCell::new(Some(model_diffs_stream)); let client = Self { req_rcv, storage, world_metadata, - sub_res_stream, + model_diffs_stream, err_callback: None, - subscribed_entities, + subscribed_models, }; (client, handle) @@ -167,7 +167,7 @@ impl SubscriptionService { fn handle_event(&self, event: SubscriptionEvent) -> Result<(), Error> { match event { SubscriptionEvent::UpdateSubsciptionStream(stream) => { - self.sub_res_stream.replace(Some(stream)); + self.model_diffs_stream.replace(Some(stream)); } } Ok(()) @@ -177,7 +177,7 @@ impl SubscriptionService { fn handle_response(&mut self, response: Result) { match response { Ok(update) => { - self.process_entity_diff(update.state_diff); + self.process_model_diff(update.state_diff); } Err(err) => { @@ -188,7 +188,7 @@ impl SubscriptionService { } } - fn process_entity_diff(&mut self, diff: StateDiff) { + fn process_model_diff(&mut self, diff: StateDiff) { let storage_entries = diff.storage_diffs.into_iter().find_map(|d| { let expected = self.world_metadata.read().world_address; let current = d.address; @@ -200,10 +200,10 @@ impl SubscriptionService { }; let entries: Vec<(FieldElement, FieldElement)> = { - let subscribed_entities = self.subscribed_entities.subscribed_storage_addresses.read(); + let subscribed_models = self.subscribed_models.subscribed_storage_addresses.read(); entries .into_iter() - .filter(|entry| subscribed_entities.contains(&entry.key)) + .filter(|entry| subscribed_models.contains(&entry.key)) .map(|entry| (entry.key, entry.value)) .collect() }; @@ -226,7 +226,7 @@ impl Future for SubscriptionService { let _ = pin.handle_event(req); } - if let Some(stream) = pin.sub_res_stream.get_mut() { + if let Some(stream) = pin.model_diffs_stream.get_mut() { match stream.poll_next_unpin(cx) { Poll::Ready(Some(res)) => pin.handle_response(res), Poll::Ready(None) => return Poll::Ready(()), @@ -268,7 +268,7 @@ mod tests { } #[test] - fn add_and_remove_subscribed_entity() { + fn add_and_remove_subscribed_model() { let model_name = String::from("Position"); let keys = vec![felt!("0x12345")]; let packed_size: u32 = 1; @@ -284,24 +284,24 @@ mod tests { let keys = KeysClause { model: model_name, keys }; - let subscribed_entities = super::SubscribedEntities::new(Arc::new(RwLock::new(metadata))); - subscribed_entities.add_entities(vec![keys.clone()]).expect("able to add entity"); + let subscribed_models = super::SubscribedModels::new(Arc::new(RwLock::new(metadata))); + subscribed_models.add_models(vec![keys.clone()]).expect("able to add model"); let actual_storage_addresses_count = - subscribed_entities.subscribed_storage_addresses.read().len(); + subscribed_models.subscribed_storage_addresses.read().len(); let actual_storage_addresses = - subscribed_entities.subscribed_storage_addresses.read().clone(); + subscribed_models.subscribed_storage_addresses.read().clone(); - assert!(subscribed_entities.entities_keys.read().contains(&keys)); + assert!(subscribed_models.models_keys.read().contains(&keys)); assert_eq!(actual_storage_addresses_count, expected_storage_addresses.len()); assert!(expected_storage_addresses.all(|addr| actual_storage_addresses.contains(&addr))); - subscribed_entities.remove_entities(vec![keys.clone()]).expect("able to remove entities"); + subscribed_models.remove_models(vec![keys.clone()]).expect("able to remove entities"); let actual_storage_addresses_count_after = - subscribed_entities.subscribed_storage_addresses.read().len(); + subscribed_models.subscribed_storage_addresses.read().len(); assert_eq!(actual_storage_addresses_count_after, 0); - assert!(!subscribed_entities.entities_keys.read().contains(&keys)); + assert!(!subscribed_models.models_keys.read().contains(&keys)); } } diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index e413817e22..138130432a 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -36,8 +36,8 @@ message Model { repeated Member members = 2; } message Entity { - // The entity key - bytes key = 1; + // The entity id + bytes id = 1; // Models of the entity repeated Model models = 2; } @@ -56,14 +56,14 @@ message StorageDiff { repeated StorageEntry storage_entries = 2; } -message EntityDiff { +message ModelDiff { // Storage diffs repeated StorageDiff storage_diffs = 1; } -message EntityUpdate { +message ModelUpdate { string block_hash = 1; - EntityDiff entity_diff = 2; + ModelDiff model_diff = 2; } message Query { diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 2f55a2cc72..3b42be93af 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -8,11 +8,13 @@ service World { // Retrieves metadata about the World including all the registered components and systems. rpc WorldMetadata (MetadataRequest) returns (MetadataResponse); - - // Subscribes to entities updates. - rpc SubscribeEntities (SubscribeEntitiesRequest) returns (stream SubscribeEntitiesResponse); + // Subscribes to models updates. + rpc SubscribeModels (SubscribeModelsRequest) returns (stream SubscribeModelsResponse); - // Retrieve entity + // Subscribe to entity updates. + rpc SubscribeEntities (SubscribeEntitiesRequest) returns (stream SubscribeEntityResponse); + + // Retrieve entities rpc RetrieveEntities (RetrieveEntitiesRequest) returns (RetrieveEntitiesResponse); } @@ -27,14 +29,22 @@ message MetadataResponse { types.WorldMetadata metadata = 1; } +message SubscribeModelsRequest { + // The list of model keys to subscribe to. + repeated types.KeysClause models_keys = 1; +} + +message SubscribeModelsResponse { + // List of models that have been updated. + types.ModelUpdate model_update = 1; +} + message SubscribeEntitiesRequest { - // The list of entity keys to subscribe to. - repeated types.KeysClause entities_keys = 1; + repeated bytes ids = 1; } -message SubscribeEntitiesResponse { - // List of entities that have been updated. - types.EntityUpdate entity_update = 1; +message SubscribeEntityResponse { + types.Entity entity = 1; } message RetrieveEntitiesRequest { diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index 0e2ef98e1b..c8870bbd1c 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -3,14 +3,15 @@ use std::num::ParseIntError; use futures_util::stream::MapOk; use futures_util::{Stream, StreamExt, TryStreamExt}; -use proto::world::{world_client, SubscribeEntitiesRequest}; use starknet::core::types::{FromByteSliceError, FromStrError, StateUpdate}; use starknet_crypto::FieldElement; use crate::proto::world::{ - MetadataRequest, RetrieveEntitiesRequest, RetrieveEntitiesResponse, SubscribeEntitiesResponse, + world_client, MetadataRequest, RetrieveEntitiesRequest, RetrieveEntitiesResponse, + SubscribeEntitiesRequest, SubscribeEntityResponse, SubscribeModelsRequest, + SubscribeModelsResponse, }; -use crate::proto::{self}; +use crate::types::schema::Entity; use crate::types::{KeysClause, Query}; #[derive(Debug, thiserror::Error)] @@ -82,36 +83,72 @@ impl WorldClient { self.inner.retrieve_entities(request).await.map_err(Error::Grpc).map(|res| res.into_inner()) } - /// Subscribe to the state diff for a set of entities of a World. + /// Subscribe to entities updates of a World. pub async fn subscribe_entities( &mut self, - entities_keys: Vec, + ids: Vec, ) -> Result { + let ids = ids.iter().map(|id| id.to_bytes_be().to_vec()).collect(); let stream = self .inner - .subscribe_entities(SubscribeEntitiesRequest { - entities_keys: entities_keys.into_iter().map(|e| e.into()).collect(), - }) + .subscribe_entities(SubscribeEntitiesRequest { ids }) .await .map_err(Error::Grpc) .map(|res| res.into_inner())?; Ok(EntityUpdateStreaming(stream.map_ok(Box::new(|res| { - let update = res.entity_update.expect("qed; state update must exist"); + let entity = res.entity.expect("entity must exist"); + entity.try_into().expect("must able to serialize") + })))) + } + + /// Subscribe to the model diff for a set of models of a World. + pub async fn subscribe_model_diffs( + &mut self, + models_keys: Vec, + ) -> Result { + let stream = self + .inner + .subscribe_models(SubscribeModelsRequest { + models_keys: models_keys.into_iter().map(|e| e.into()).collect(), + }) + .await + .map_err(Error::Grpc) + .map(|res| res.into_inner())?; + + Ok(ModelDiffsStreaming(stream.map_ok(Box::new(|res| { + let update = res.model_update.expect("qed; state update must exist"); TryInto::::try_into(update).expect("must able to serialize") })))) } } -type MappedStream = MapOk< - tonic::Streaming, - Box StateUpdate + Send>, +type ModelDiffMappedStream = MapOk< + tonic::Streaming, + Box StateUpdate + Send>, +>; + +pub struct ModelDiffsStreaming(ModelDiffMappedStream); + +impl Stream for ModelDiffsStreaming { + type Item = ::Item; + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.0.poll_next_unpin(cx) + } +} + +type EntityMappedStream = MapOk< + tonic::Streaming, + Box Entity + Send>, >; -pub struct EntityUpdateStreaming(MappedStream); +pub struct EntityUpdateStreaming(EntityMappedStream); impl Stream for EntityUpdateStreaming { - type Item = ::Item; + type Item = ::Item; fn poll_next( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 1e32ba5308..43005f25c9 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -1,6 +1,5 @@ -pub mod error; pub mod logger; -pub mod subscription; +pub mod subscriptions; use std::future::Future; use std::net::SocketAddr; @@ -10,9 +9,10 @@ use std::sync::Arc; use dojo_types::schema::Ty; use futures::Stream; +use hex::encode; use proto::world::{ MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, - SubscribeEntitiesRequest, SubscribeEntitiesResponse, + SubscribeModelsRequest, SubscribeModelsResponse, }; use sqlx::sqlite::SqliteRow; use sqlx::{Pool, Row, Sqlite}; @@ -29,17 +29,20 @@ use torii_core::cache::ModelCache; use torii_core::error::{Error, ParseError, QueryError}; use torii_core::model::{build_sql_query, map_row_to_ty}; -use self::subscription::SubscribeRequest; +use self::subscriptions::entity::EntityManager; +use self::subscriptions::model_diff::{ModelDiffRequest, StateDiffManager}; use crate::proto::types::clause::ClauseType; use crate::proto::world::world_server::WorldServer; +use crate::proto::world::{SubscribeEntitiesRequest, SubscribeEntityResponse}; use crate::proto::{self}; #[derive(Clone)] pub struct DojoWorld { - world_address: FieldElement, pool: Pool, - subscriber_manager: Arc, + world_address: FieldElement, model_cache: Arc, + entity_manager: Arc, + state_diff_manager: Arc, } impl DojoWorld { @@ -49,18 +52,24 @@ impl DojoWorld { world_address: FieldElement, provider: Arc>, ) -> Self { - let subscriber_manager = Arc::new(subscription::SubscriberManager::default()); + let model_cache = Arc::new(ModelCache::new(pool.clone())); + let entity_manager = Arc::new(EntityManager::default()); + let state_diff_manager = Arc::new(StateDiffManager::default()); - tokio::task::spawn(subscription::Service::new_with_block_rcv( + tokio::task::spawn(subscriptions::model_diff::Service::new_with_block_rcv( block_rx, world_address, provider, - Arc::clone(&subscriber_manager), + Arc::clone(&state_diff_manager), )); - let model_cache = Arc::new(ModelCache::new(pool.clone())); + tokio::task::spawn(subscriptions::entity::Service::new( + pool.clone(), + Arc::clone(&entity_manager), + Arc::clone(&model_cache), + )); - Self { pool, model_cache, world_address, subscriber_manager } + Self { pool, world_address, model_cache, entity_manager, state_diff_manager } } } @@ -141,8 +150,8 @@ impl DojoWorld { }) .collect::, Error>>()?; - let key = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; - entities.push(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) + let id = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; + entities.push(proto::types::Entity { id: id.to_bytes_be().to_vec(), models }) } Ok(entities) @@ -247,29 +256,35 @@ impl DojoWorld { }) } - async fn subscribe_entities( + async fn subscribe_models( &self, - entities_keys: Vec, - ) -> Result>, Error> - { - let mut subs = Vec::with_capacity(entities_keys.len()); - for keys in entities_keys { + models_keys: Vec, + ) -> Result>, Error> { + let mut subs = Vec::with_capacity(models_keys.len()); + for keys in models_keys { let model = cairo_short_string_to_felt(&keys.model) .map_err(ParseError::CairoShortStringToFelt)?; let proto::types::ModelMetadata { packed_size, .. } = self.model_metadata(&keys.model).await?; - subs.push(SubscribeRequest { + subs.push(ModelDiffRequest { keys, - model: subscription::ModelMetadata { + model: subscriptions::model_diff::ModelMetadata { name: model, packed_size: packed_size as usize, }, }); } - self.subscriber_manager.add_subscriber(subs).await + self.state_diff_manager.add_subscriber(subs).await + } + + async fn subscribe_entities( + &self, + ids: Vec, + ) -> Result>, Error> { + self.entity_manager.add_subscriber(ids).await } async fn retrieve_entities( @@ -308,7 +323,7 @@ impl DojoWorld { } fn map_row_to_entity(row: &SqliteRow, schemas: &[Ty]) -> Result { - let key = + let id = FieldElement::from_str(&row.get::("id")).map_err(ParseError::FromStr)?; let models = schemas .iter() @@ -320,16 +335,21 @@ impl DojoWorld { }) .collect::, Error>>()?; - Ok(proto::types::Entity { key: key.to_bytes_be().to_vec(), models }) + Ok(proto::types::Entity { id: id.to_bytes_be().to_vec(), models }) } } type ServiceResult = Result, Status>; +type SubscribeModelsResponseStream = + Pin> + Send>>; type SubscribeEntitiesResponseStream = - Pin> + Send>>; + Pin> + Send>>; #[tonic::async_trait] impl proto::world::world_server::World for DojoWorld { + type SubscribeModelsStream = SubscribeModelsResponseStream; + type SubscribeEntitiesStream = SubscribeEntitiesResponseStream; + async fn world_metadata( &self, _request: Request, @@ -342,17 +362,26 @@ impl proto::world::world_server::World for DojoWorld { Ok(Response::new(MetadataResponse { metadata: Some(metadata) })) } - type SubscribeEntitiesStream = SubscribeEntitiesResponseStream; + async fn subscribe_models( + &self, + request: Request, + ) -> ServiceResult { + let SubscribeModelsRequest { models_keys } = request.into_inner(); + let rx = self + .subscribe_models(models_keys) + .await + .map_err(|e| Status::internal(e.to_string()))?; + Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeModelsStream)) + } async fn subscribe_entities( &self, request: Request, ) -> ServiceResult { - let SubscribeEntitiesRequest { entities_keys } = request.into_inner(); - let rx = self - .subscribe_entities(entities_keys) - .await - .map_err(|e| Status::internal(e.to_string()))?; + let SubscribeEntitiesRequest { ids } = request.into_inner(); + let ids = ids.iter().map(encode).collect(); + let rx = self.subscribe_entities(ids).await.map_err(|e| Status::internal(e.to_string()))?; + Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) } diff --git a/crates/torii/grpc/src/server/subscriptions/entity.rs b/crates/torii/grpc/src/server/subscriptions/entity.rs new file mode 100644 index 0000000000..d1e290ca9e --- /dev/null +++ b/crates/torii/grpc/src/server/subscriptions/entity.rs @@ -0,0 +1,159 @@ +use std::collections::{HashMap, HashSet}; +use std::future::Future; +use std::pin::Pin; +use std::str::FromStr; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::Stream; +use futures_util::StreamExt; +use rand::Rng; +use sqlx::{Pool, Sqlite}; +use starknet_crypto::FieldElement; +use tokio::sync::mpsc::{channel, Receiver, Sender}; +use tokio::sync::RwLock; +use torii_core::cache::ModelCache; +use torii_core::error::{Error, ParseError}; +use torii_core::model::{build_sql_query, map_row_to_ty}; +use torii_core::simple_broker::SimpleBroker; +use torii_core::types::Entity; +use tracing::{error, trace}; + +use crate::proto; + +pub struct EntitiesSubscriber { + /// Entity ids that the subscriber is interested in + ids: HashSet, + /// The channel to send the response back to the subscriber. + sender: Sender>, +} + +#[derive(Default)] +pub struct EntityManager { + subscribers: RwLock>, +} + +impl EntityManager { + pub async fn add_subscriber( + &self, + ids: Vec, + ) -> Result>, Error> { + let id = rand::thread_rng().gen::(); + let (sender, receiver) = channel(1); + + self.subscribers + .write() + .await + .insert(id, EntitiesSubscriber { ids: ids.iter().cloned().collect(), sender }); + + Ok(receiver) + } + + pub(super) async fn remove_subscriber(&self, id: usize) { + self.subscribers.write().await.remove(&id); + } +} + +#[must_use = "Service does nothing unless polled"] +pub struct Service { + pool: Pool, + subs_manager: Arc, + model_cache: Arc, + simple_broker: Pin + Send>>, +} + +impl Service { + pub fn new( + pool: Pool, + subs_manager: Arc, + model_cache: Arc, + ) -> Self { + Self { + pool, + subs_manager, + model_cache, + simple_broker: Box::pin(SimpleBroker::::subscribe()), + } + } + + async fn publish_updates( + subs: Arc, + cache: Arc, + pool: Pool, + id: &str, + ) -> Result<(), Error> { + let mut closed_stream = Vec::new(); + + for (idx, sub) in subs.subscribers.read().await.iter() { + // publish all updates if ids is empty or only ids that are subscribed to + if sub.ids.is_empty() || sub.ids.contains(id) { + let models_query = r#" + SELECT group_concat(entity_model.model_id) as model_names + FROM entities + JOIN entity_model ON entities.id = entity_model.entity_id + WHERE entities.id = ? + GROUP BY entities.id + "#; + let (model_names,): (String,) = + sqlx::query_as(models_query).bind(id).fetch_one(&pool).await?; + let model_names: Vec<&str> = model_names.split(',').collect(); + let schemas = cache.schemas(model_names).await?; + let entity_query = format!("{} WHERE entities.id = ?", build_sql_query(&schemas)?); + let row = sqlx::query(&entity_query).bind(id).fetch_one(&pool).await?; + + let models = schemas + .iter() + .map(|s| { + let mut struct_ty = + s.as_struct().expect("schema should be struct").to_owned(); + map_row_to_ty(&s.name(), &mut struct_ty, &row)?; + + Ok(struct_ty.try_into().unwrap()) + }) + .collect::, Error>>()?; + + let resp = proto::world::SubscribeEntityResponse { + entity: Some(proto::types::Entity { + id: FieldElement::from_str(id) + .map_err(ParseError::FromStr)? + .to_bytes_be() + .to_vec(), + models, + }), + }; + + if sub.sender.send(Ok(resp)).await.is_err() { + closed_stream.push(*idx); + } + } + } + + for id in closed_stream { + trace!(target = "subscription", "closing entity stream idx: {id}"); + subs.remove_subscriber(id).await + } + + Ok(()) + } +} + +impl Future for Service { + type Output = (); + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { + let pin = self.get_mut(); + + while let Poll::Ready(Some(entity)) = pin.simple_broker.poll_next_unpin(cx) { + let subs = Arc::clone(&pin.subs_manager); + let cache = Arc::clone(&pin.model_cache); + let pool = pin.pool.clone(); + tokio::spawn(async move { + if let Err(e) = Service::publish_updates(subs, cache, pool, &entity.id).await { + error!(target = "subscription", "error when publishing entity update: {e}"); + } + }); + } + + Poll::Pending + } +} diff --git a/crates/torii/grpc/src/server/error.rs b/crates/torii/grpc/src/server/subscriptions/error.rs similarity index 100% rename from crates/torii/grpc/src/server/error.rs rename to crates/torii/grpc/src/server/subscriptions/error.rs diff --git a/crates/torii/grpc/src/server/subscriptions/mod.rs b/crates/torii/grpc/src/server/subscriptions/mod.rs new file mode 100644 index 0000000000..07b593d366 --- /dev/null +++ b/crates/torii/grpc/src/server/subscriptions/mod.rs @@ -0,0 +1,3 @@ +pub mod entity; +pub mod error; +pub mod model_diff; diff --git a/crates/torii/grpc/src/server/subscription.rs b/crates/torii/grpc/src/server/subscriptions/model_diff.rs similarity index 84% rename from crates/torii/grpc/src/server/subscription.rs rename to crates/torii/grpc/src/server/subscriptions/model_diff.rs index bc20140df7..ac73d15f6e 100644 --- a/crates/torii/grpc/src/server/subscription.rs +++ b/crates/torii/grpc/src/server/subscriptions/model_diff.rs @@ -27,44 +27,30 @@ pub struct ModelMetadata { pub packed_size: usize, } -pub struct SubscribeRequest { +pub struct ModelDiffRequest { pub model: ModelMetadata, pub keys: proto::types::KeysClause, } -impl SubscribeRequest { - // pub fn slots(&self) -> Result, QueryError> { - // match self.query.clause { - // Clause::Keys(KeysClause { keys }) => { - // let base = poseidon_hash_many(&[ - // short_string!("dojo_storage"), - // req.model.name, - // poseidon_hash_many(&keys), - // ]); - // } - // _ => Err(QueryError::UnsupportedQuery), - // } - // } -} +impl ModelDiffRequest {} -pub struct Subscriber { +pub struct ModelDiffSubscriber { /// The storage addresses that the subscriber is interested in. storage_addresses: HashSet, /// The channel to send the response back to the subscriber. - sender: Sender>, + sender: Sender>, } #[derive(Default)] -pub struct SubscriberManager { - subscribers: RwLock>, +pub struct StateDiffManager { + subscribers: RwLock>, } -impl SubscriberManager { - pub(super) async fn add_subscriber( +impl StateDiffManager { + pub async fn add_subscriber( &self, - reqs: Vec, - ) -> Result>, Error> - { + reqs: Vec, + ) -> Result>, Error> { let id = rand::thread_rng().gen::(); let (sender, receiver) = channel(1); @@ -94,7 +80,10 @@ impl SubscriberManager { .flatten() .collect::>(); - self.subscribers.write().await.insert(id, Subscriber { storage_addresses, sender }); + self.subscribers + .write() + .await + .insert(id, ModelDiffSubscriber { storage_addresses, sender }); Ok(receiver) } @@ -114,7 +103,7 @@ pub struct Service { block_num_rcv: Receiver, state_update_queue: VecDeque, state_update_req_fut: Option>, - subs_manager: Arc, + subs_manager: Arc, publish_fut: Option>, } @@ -122,11 +111,11 @@ impl

Service

where P: Provider + Send, { - pub(super) fn new_with_block_rcv( + pub fn new_with_block_rcv( block_num_rcv: Receiver, world_address: FieldElement, provider: P, - subs_manager: Arc, + subs_manager: Arc, ) -> Self { Self { subs_manager, @@ -148,7 +137,7 @@ where } async fn publish_updates( - subs: Arc, + subs: Arc, contract_address: FieldElement, state_update: StateUpdate, ) -> PublishStateUpdateResult { @@ -173,9 +162,9 @@ where }) .collect::>(); - let entity_update = proto::types::EntityUpdate { + let model_update = proto::types::ModelUpdate { block_hash: format!("{:#x}", state_update.block_hash), - entity_diff: Some(proto::types::EntityDiff { + model_diff: Some(proto::types::ModelDiff { storage_diffs: vec![proto::types::StorageDiff { address: format!("{contract_address:#x}"), storage_entries: relevant_storage_entries, @@ -183,8 +172,7 @@ where }), }; - let resp = - proto::world::SubscribeEntitiesResponse { entity_update: Some(entity_update) }; + let resp = proto::world::SubscribeModelsResponse { model_update: Some(model_update) }; if sub.sender.send(Ok(resp)).await.is_err() { closed_stream.push(*idx); diff --git a/crates/torii/grpc/src/types/mod.rs b/crates/torii/grpc/src/types/mod.rs index 25531536df..da97af938b 100644 --- a/crates/torii/grpc/src/types/mod.rs +++ b/crates/torii/grpc/src/types/mod.rs @@ -218,9 +218,9 @@ impl TryFrom for ContractStorageDiffItem { } } -impl TryFrom for StateDiff { +impl TryFrom for StateDiff { type Error = FromStrError; - fn try_from(value: proto::types::EntityDiff) -> Result { + fn try_from(value: proto::types::ModelDiff) -> Result { Ok(Self { nonces: vec![], declared_classes: vec![], @@ -236,14 +236,14 @@ impl TryFrom for StateDiff { } } -impl TryFrom for StateUpdate { +impl TryFrom for StateUpdate { type Error = FromStrError; - fn try_from(value: proto::types::EntityUpdate) -> Result { + fn try_from(value: proto::types::ModelUpdate) -> Result { Ok(Self { new_root: FieldElement::ZERO, old_root: FieldElement::ZERO, block_hash: FieldElement::from_str(&value.block_hash)?, - state_diff: value.entity_diff.expect("must have").try_into()?, + state_diff: value.model_diff.expect("must have").try_into()?, }) } } diff --git a/crates/torii/grpc/src/types/schema.rs b/crates/torii/grpc/src/types/schema.rs index 72047b6f06..1931989a5c 100644 --- a/crates/torii/grpc/src/types/schema.rs +++ b/crates/torii/grpc/src/types/schema.rs @@ -22,7 +22,7 @@ impl TryFrom for Entity { type Error = ClientError; fn try_from(entity: proto::types::Entity) -> Result { Ok(Self { - key: FieldElement::from_byte_slice_be(&entity.key).map_err(ClientError::SliceError)?, + key: FieldElement::from_byte_slice_be(&entity.id).map_err(ClientError::SliceError)?, models: entity .models .into_iter() From 6f451fcc087c4c9f344f8e818f02f48959668de0 Mon Sep 17 00:00:00 2001 From: loothero <100039621+loothero@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:03:08 -0500 Subject: [PATCH 120/192] install cairo1 in devcontainer (#1267) --- .devcontainer/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c453c291e3..36d37341ec 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -27,7 +27,8 @@ "mutantdino.resourcemonitor", "rust-lang.rust-analyzer", "tamasfe.even-better-toml", - "serayuzgur.crates" + "serayuzgur.crates", + "starkware.cairo1" ] } }, From c78fad8ff6593d2091a8c50e49db87824b3ae9bc Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 13 Dec 2023 17:11:04 +0800 Subject: [PATCH 121/192] fix(katana-rpc-types): compute contract address correctly (#1269) --- crates/katana/rpc/rpc-types/src/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/katana/rpc/rpc-types/src/transaction.rs b/crates/katana/rpc/rpc-types/src/transaction.rs index a28ff1f0ea..ee2934e8ed 100644 --- a/crates/katana/rpc/rpc-types/src/transaction.rs +++ b/crates/katana/rpc/rpc-types/src/transaction.rs @@ -114,7 +114,7 @@ impl BroadcastedDeployAccountTx { self.0.contract_address_salt, self.0.class_hash, &self.0.constructor_calldata, - chain_id, + FieldElement::ZERO, ); DeployAccountTx { From b01bf09bab47fdce46de25b82ee68b322f2f7da1 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 14 Dec 2023 00:03:48 +0800 Subject: [PATCH 122/192] feat(katana): update RPC to v0.5.1 (#1259) * bump `starknet-rs` to `0.8.0` * wip * wip * no error * update * update * wip * update * update * fix hurl test * fix * update hurl test * Update examples/rpc/starknet/starknet_getBlockWithTxs.hurl Co-authored-by: glihm * Update examples/rpc/starknet/starknet_getStorageAt.hurl Co-authored-by: glihm --------- Co-authored-by: glihm --- Cargo.lock | 52 +++------ Cargo.toml | 4 +- crates/dojo-world/src/contracts/model.rs | 11 +- crates/dojo-world/src/manifest.rs | 23 ++-- crates/dojo-world/src/migration/mod.rs | 21 +--- crates/dojo-world/src/utils.rs | 13 +-- crates/katana/core/src/backend/mod.rs | 15 ++- crates/katana/core/src/backend/storage.rs | 25 ++-- .../katana/core/src/service/block_producer.rs | 23 ++-- .../core/src/service/messaging/ethereum.rs | 25 ++-- .../core/src/service/messaging/starknet.rs | 7 ++ crates/katana/executor/src/blockifier/mod.rs | 42 +++++++ .../katana/executor/src/blockifier/outcome.rs | 50 ++++++-- .../katana/executor/src/blockifier/state.rs | 2 +- crates/katana/primitives/Cargo.toml | 3 + crates/katana/primitives/src/block.rs | 35 +++++- crates/katana/primitives/src/env.rs | 8 +- crates/katana/primitives/src/lib.rs | 1 + crates/katana/primitives/src/receipt.rs | 29 +++-- crates/katana/primitives/src/transaction.rs | 4 +- .../primitives/src/utils/transaction.rs | 21 ++++ crates/katana/primitives/src/version.rs | 87 ++++++++++++++ crates/katana/rpc/rpc-types/src/block.rs | 30 ++++- crates/katana/rpc/rpc-types/src/message.rs | 34 ++++-- crates/katana/rpc/rpc-types/src/receipt.rs | 11 ++ crates/katana/rpc/src/api/starknet.rs | 15 ++- crates/katana/rpc/src/starknet.rs | 108 ++++++++++++++---- .../provider/src/providers/fork/backend.rs | 12 +- crates/sozo/src/ops/migration/mod.rs | 11 +- examples/rpc/starknet/starknet_chainId.hurl | 2 +- .../starknet_getBlockWithTxHashes.hurl | 10 +- .../starknet/starknet_getBlockWithTxs.hurl | 5 +- .../rpc/starknet/starknet_getClassAt.hurl | 2 +- .../rpc/starknet/starknet_getClassHashAt.hurl | 2 +- .../rpc/starknet/starknet_getStorageAt.hurl | 47 +++++++- .../starknet_getTransactionStatus.hurl | 15 +++ ...actions.hurl => starknet_specVersion.hurl} | 6 +- 37 files changed, 589 insertions(+), 222 deletions(-) create mode 100644 crates/katana/primitives/src/version.rs create mode 100644 examples/rpc/starknet/starknet_getTransactionStatus.hurl rename examples/rpc/starknet/{starknet_pendingTransactions.hurl => starknet_specVersion.hurl} (62%) diff --git a/Cargo.lock b/Cargo.lock index 49d428564e..07de8cc4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5338,9 +5338,11 @@ dependencies = [ "cairo-lang-starknet", "cairo-vm", "derive_more", + "ethers", "flate2", "serde", "serde_json", + "sha3", "starknet", "starknet_api", "thiserror", @@ -8401,13 +8403,13 @@ dependencies = [ [[package]] name = "starknet" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351ffff1bcf6a1dc569a1b330dfd85779e16506e7d4a87baa8be3744cb5415a6" +checksum = "5eb139c5e6f6c6da627080e33cc00b3fc1c9733403034ca1ee9c42a95c337c7f" dependencies = [ "starknet-accounts", "starknet-contract", - "starknet-core 0.7.2", + "starknet-core", "starknet-crypto 0.6.1", "starknet-ff", "starknet-macros", @@ -8417,13 +8419,13 @@ dependencies = [ [[package]] name = "starknet-accounts" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7062b020f65d9da7f9dd9f1d97bfb644e881cda8ddb999799a799e6f2e408dd" +checksum = "3743932c80ad2a5868c2dd4ef729de4e12060c88e73e4bb678a5f8e51b105e53" dependencies = [ "async-trait", "auto_impl", - "starknet-core 0.7.2", + "starknet-core", "starknet-providers", "starknet-signers", "thiserror", @@ -8431,37 +8433,19 @@ dependencies = [ [[package]] name = "starknet-contract" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d858efc93d85de95065a5732cb3e94d0c746674964c0859aab442ffbb9de76b4" +checksum = "4e55aac528c5376e1626d5a8d4daaf280bfd08f909dadc729e5b009203d6ec21" dependencies = [ "serde", "serde_json", "serde_with", "starknet-accounts", - "starknet-core 0.7.2", + "starknet-core", "starknet-providers", "thiserror", ] -[[package]] -name = "starknet-core" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1683ca7c63f0642310eddedb7d35056d8306084dff323d440711065c63ed87" -dependencies = [ - "base64 0.21.5", - "flate2", - "hex", - "serde", - "serde_json", - "serde_json_pythonic", - "serde_with", - "sha3", - "starknet-crypto 0.6.1", - "starknet-ff", -] - [[package]] name = "starknet-core" version = "0.8.0" @@ -8570,15 +8554,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "840be1a7eb5735863eee47d3a3f26df45b9be2c519e8da294e74b4d0524d77d1" dependencies = [ - "starknet-core 0.8.0", + "starknet-core", "syn 2.0.39", ] [[package]] name = "starknet-providers" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52072c2d258bf692affeccd602613d5f6c61a6ffc84da8f191ab4a1b0a5e24d1" +checksum = "5b08084f36ff7f11743ec71f33f0b11d439cbe0524058def299eb47de1ef1c28" dependencies = [ "async-trait", "auto_impl", @@ -8589,23 +8573,23 @@ dependencies = [ "serde", "serde_json", "serde_with", - "starknet-core 0.7.2", + "starknet-core", "thiserror", "url", ] [[package]] name = "starknet-signers" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347b1bfc09846aafe16d2b3a5bc2d8a2f845e2958602442182d265fbd6011c2e" +checksum = "91919d8f318f0b5bcc4ff5849fbd3fb46adaaa72e0bf204742bab7c822425ff4" dependencies = [ "async-trait", "auto_impl", "crypto-bigint", "eth-keystore", "rand", - "starknet-core 0.7.2", + "starknet-core", "starknet-crypto 0.6.1", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index a8d28b933c..bd0d88fc0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,9 @@ members = [ "crates/katana/rpc", "crates/katana/rpc/rpc-types", "crates/katana/rpc/rpc-types-builder", + "crates/katana/runner", "crates/katana/storage/db", "crates/katana/storage/provider", - "crates/katana/runner", "crates/sozo", "crates/torii/client", "crates/torii/server", @@ -96,7 +96,7 @@ serde_json = "1.0" serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } sqlx = { version = "0.7.2", features = [ "chrono", "macros", "regexp", "runtime-async-std", "runtime-tokio", "sqlite", "uuid" ] } -starknet = "0.7.0" +starknet = "0.8.0" starknet-crypto = "0.6.1" starknet_api = "0.6.0-rc3" strum = "0.25" diff --git a/crates/dojo-world/src/contracts/model.rs b/crates/dojo-world/src/contracts/model.rs index 87e6e01150..433f3b8cd4 100644 --- a/crates/dojo-world/src/contracts/model.rs +++ b/crates/dojo-world/src/contracts/model.rs @@ -10,9 +10,7 @@ use starknet::core::utils::{ ParseCairoShortStringError, }; use starknet::macros::short_string; -use starknet::providers::{ - MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{Provider, ProviderError}; use starknet_crypto::poseidon_hash_many; use crate::contracts::world::{ContractReaderError, WorldContractReader}; @@ -89,10 +87,9 @@ where .await .map(|res| res[0]) .map_err(|err| match err { - ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }) => ModelError::ModelNotFound, + ProviderError::StarknetError(StarknetError::ContractNotFound) => { + ModelError::ModelNotFound + } err => err.into(), })?; diff --git a/crates/dojo-world/src/manifest.rs b/crates/dojo-world/src/manifest.rs index 249bef2d2c..4184d56258 100644 --- a/crates/dojo-world/src/manifest.rs +++ b/crates/dojo-world/src/manifest.rs @@ -15,9 +15,7 @@ use starknet::core::utils::{ ParseCairoShortStringError, }; use starknet::macros::selector; -use starknet::providers::{ - MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{Provider, ProviderError}; use thiserror::Error; use crate::contracts::model::ModelError; @@ -168,10 +166,9 @@ impl Manifest { let world_class_hash = provider.get_class_hash_at(BLOCK_ID, world_address).await.map_err(|err| match err { - ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }) => ManifestError::RemoteWorldNotFound, + ProviderError::StarknetError(StarknetError::ContractNotFound) => { + ManifestError::RemoteWorldNotFound + } err => err.into(), })?; @@ -184,10 +181,9 @@ impl Manifest { .get_class_hash_at(BLOCK_ID, executor_address) .await .map_err(|err| match err { - ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - }) => ManifestError::ExecutorNotFound, + ProviderError::StarknetError(StarknetError::ContractNotFound) => { + ManifestError::ExecutorNotFound + } err => err.into(), })?; @@ -290,10 +286,7 @@ where { Ok(res) => parse_cairo_short_string(&res[0])?.into(), - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractError), - .. - })) => SmolStr::from(""), + Err(ProviderError::StarknetError(StarknetError::ContractError(_))) => SmolStr::from(""), Err(err) => return Err(err.into()), }; diff --git a/crates/dojo-world/src/migration/mod.rs b/crates/dojo-world/src/migration/mod.rs index 0343284de8..5b3d256fec 100644 --- a/crates/dojo-world/src/migration/mod.rs +++ b/crates/dojo-world/src/migration/mod.rs @@ -16,9 +16,7 @@ use starknet::core::utils::{ get_contract_address, get_selector_from_name, CairoShortStringToFeltError, }; use starknet::macros::{felt, selector}; -use starknet::providers::{ - MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{Provider, ProviderError}; use starknet::signers::Signer; use thiserror::Error; @@ -106,11 +104,7 @@ pub trait Declarable { .get_class(BlockId::Tag(BlockTag::Pending), flattened_class.class_hash()) .await { - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), - .. - })) => {} - + Err(ProviderError::StarknetError(StarknetError::ClassHashNotFound)) => {} Ok(_) => return Err(MigrationError::ClassAlreadyDeclared), Err(e) => return Err(MigrationError::Provider(e)), } @@ -181,10 +175,7 @@ pub trait Deployable: Declarable + Sync { to: world_address, }, - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - })) => Call { + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => Call { calldata: vec![self.salt(), class_hash], selector: selector!("deploy_contract"), to: world_address, @@ -251,11 +242,7 @@ pub trait Deployable: Declarable + Sync { .get_class_hash_at(BlockId::Tag(BlockTag::Pending), contract_address) .await { - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - })) => {} - + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => {} Ok(_) => return Err(MigrationError::ContractAlreadyDeployed(contract_address)), Err(e) => return Err(MigrationError::Provider(e)), } diff --git a/crates/dojo-world/src/utils.rs b/crates/dojo-world/src/utils.rs index 641527e850..1c0bc88037 100644 --- a/crates/dojo-world/src/utils.rs +++ b/crates/dojo-world/src/utils.rs @@ -8,9 +8,7 @@ use starknet::core::types::{ ExecutionResult, FieldElement, MaybePendingTransactionReceipt, PendingTransactionReceipt, StarknetError, TransactionFinalityStatus, TransactionReceipt, }; -use starknet::providers::{ - MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{Provider, ProviderError}; use tokio::time::{Instant, Interval}; type GetReceiptResult = Result; @@ -185,11 +183,9 @@ where } }, - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: - MaybeUnknownErrorCode::Known(StarknetError::TransactionHashNotFound), - .. - })) => {} + Err(ProviderError::StarknetError( + StarknetError::TransactionHashNotFound, + )) => {} Err(e) => { return Poll::Ready(Err(TransactionWaitingError::Provider(e))); @@ -230,7 +226,6 @@ fn execution_status_from_receipt(receipt: &TransactionReceipt) -> &ExecutionResu fn execution_status_from_pending_receipt(receipt: &PendingTransactionReceipt) -> &ExecutionResult { match receipt { PendingTransactionReceipt::Invoke(receipt) => &receipt.execution_result, - PendingTransactionReceipt::Deploy(receipt) => &receipt.execution_result, PendingTransactionReceipt::Declare(receipt) => &receipt.execution_result, PendingTransactionReceipt::L1Handler(receipt) => &receipt.execution_result, PendingTransactionReceipt::DeployAccount(receipt) => &receipt.execution_result, diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 32343541e0..99d6009687 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -2,12 +2,13 @@ use std::sync::Arc; use blockifier::block_context::BlockContext; use katana_primitives::block::{ - Block, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, + Block, FinalityStatus, GasPrices, Header, PartialHeader, SealedBlockWithStatus, }; use katana_primitives::contract::ContractAddress; use katana_primitives::receipt::Receipt; use katana_primitives::state::StateUpdatesWithDeclaredClasses; use katana_primitives::transaction::TxWithHash; +use katana_primitives::version::CURRENT_STARKNET_VERSION; use katana_primitives::FieldElement; use katana_provider::providers::fork::ForkedProvider; use katana_provider::providers::in_memory::InMemoryProvider; @@ -149,15 +150,19 @@ impl Backend { let partial_header = PartialHeader { parent_hash: prev_hash, - gas_price: block_context.gas_prices.eth_l1_gas_price, - number: block_context.block_number.0, + version: CURRENT_STARKNET_VERSION, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), + gas_prices: GasPrices { + eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(), + strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(), + }, }; let tx_count = txs.len(); - let block_number = partial_header.number; - let header = Header::new(partial_header, FieldElement::ZERO); + let block_number = block_context.block_number.0; + + let header = Header::new(partial_header, block_number, FieldElement::ZERO); let block = Block { header, body: txs }.seal(); let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }; diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 49b59c8b3a..7970dd9fc2 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -1,9 +1,10 @@ use anyhow::Result; use blockifier::block_context::BlockContext; use katana_primitives::block::{ - Block, BlockHash, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, + Block, BlockHash, FinalityStatus, GasPrices, Header, PartialHeader, SealedBlockWithStatus, }; use katana_primitives::state::StateUpdatesWithDeclaredClasses; +use katana_primitives::version::CURRENT_STARKNET_VERSION; use katana_primitives::FieldElement; use katana_provider::traits::block::{BlockProvider, BlockWriter}; use katana_provider::traits::contract::{ContractClassWriter, ContractInfoProvider}; @@ -66,17 +67,23 @@ impl Blockchain { pub fn new_with_genesis(provider: impl Database, block_context: &BlockContext) -> Result { let header = PartialHeader { - // TODO: need to be adjusted, eth is used for compatibility for now. parent_hash: 0u8.into(), - gas_price: block_context.gas_prices.eth_l1_gas_price, - number: block_context.block_number.0, + version: CURRENT_STARKNET_VERSION, timestamp: block_context.block_timestamp.0, sequencer_address: *SEQUENCER_ADDRESS, + gas_prices: GasPrices { + eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(), + strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(), + }, }; let block = SealedBlockWithStatus { status: FinalityStatus::AcceptedOnL1, - block: Block { header: Header::new(header, 0u8.into()), body: vec![] }.seal(), + block: Block { + header: Header::new(header, block_context.block_number.0, 0u8.into()), + body: vec![], + } + .seal(), }; Self::new_with_block_and_state(provider, block, get_genesis_states_for_testing()) @@ -95,10 +102,14 @@ impl Blockchain { let header = Header { state_root, parent_hash, - gas_price: block_context.gas_prices.eth_l1_gas_price, + version: CURRENT_STARKNET_VERSION, number: block_context.block_number.0, timestamp: block_context.block_timestamp.0, sequencer_address: *SEQUENCER_ADDRESS, + gas_prices: GasPrices { + eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(), + strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(), + }, }; let block = SealedBlockWithStatus { @@ -214,7 +225,7 @@ mod tests { assert_eq!(latest_number, 23); assert_eq!(latest_hash, felt!("1111")); - assert_eq!(header.gas_price, 9090); + assert_eq!(header.gas_prices.eth_gas_price, 9090); assert_eq!(header.timestamp, 6868); assert_eq!(header.number, latest_number); assert_eq!(header.state_root, felt!("1334")); diff --git a/crates/katana/core/src/service/block_producer.rs b/crates/katana/core/src/service/block_producer.rs index af123eba45..c87f8a0ff6 100644 --- a/crates/katana/core/src/service/block_producer.rs +++ b/crates/katana/core/src/service/block_producer.rs @@ -151,10 +151,7 @@ impl IntervalBlockProducer { interval }; - let state = Arc::new(PendingState { - state: Arc::new(CachedStateWrapper::new(db)), - executed_txs: Default::default(), - }); + let state = Arc::new(PendingState::new(db)); Self { state, @@ -170,10 +167,7 @@ impl IntervalBlockProducer { /// for every fixed interval, although it will still execute all queued transactions and /// keep hold of the pending state. pub fn new_no_mining(backend: Arc, db: StateRefDb) -> Self { - let state = Arc::new(PendingState { - state: Arc::new(CachedStateWrapper::new(db)), - executed_txs: Default::default(), - }); + let state = Arc::new(PendingState::new(db)); Self { state, @@ -206,16 +200,15 @@ impl IntervalBlockProducer { ) -> MinedBlockOutcome { trace!(target: "miner", "creating new block"); - let tx_receipt_pairs = { - let mut txs_lock = pending_state.executed_txs.write(); - txs_lock.drain(..).map(|(tx, rct)| (tx, rct.receipt)).collect::>() - }; + let (txs, _) = pending_state.take_txs_all(); + let tx_receipt_pairs = + txs.into_iter().map(|(tx, rct)| (tx, rct.receipt)).collect::>(); let (outcome, new_state) = backend.mine_pending_block(tx_receipt_pairs, state_updates); trace!(target: "miner", "created new block: {}", outcome.block_number); backend.update_block_context(); - pending_state.state.reset_with_new_state(new_state.into()); + pending_state.reset_state_with(new_state.into()); outcome } @@ -235,7 +228,7 @@ impl IntervalBlockProducer { .zip(txs) .filter_map(|(res, tx)| { let Ok(info) = res else { return None }; - let receipt = TxReceiptWithExecInfo::from_tx_exec_result(&tx, info); + let receipt = TxReceiptWithExecInfo::new(&tx, info); Some((tx, receipt)) }) .collect::>() @@ -345,7 +338,7 @@ impl InstantBlockProducer { .zip(txs) .filter_map(|(res, tx)| { if let Ok(info) = res { - let receipt = TxReceiptWithExecInfo::from_tx_exec_result(&tx, info); + let receipt = TxReceiptWithExecInfo::new(&tx, info); Some((tx, receipt.receipt)) } else { None diff --git a/crates/katana/core/src/service/messaging/ethereum.rs b/crates/katana/core/src/service/messaging/ethereum.rs index e8646b6336..e282a41464 100644 --- a/crates/katana/core/src/service/messaging/ethereum.rs +++ b/crates/katana/core/src/service/messaging/ethereum.rs @@ -9,8 +9,8 @@ use ethers::providers::{Http, Provider}; use ethers::types::{Address, BlockNumber, Log}; use k256::ecdsa::SigningKey; use katana_primitives::transaction::L1HandlerTx; +use katana_primitives::utils::transaction::compute_l1_message_hash; use katana_primitives::FieldElement; -use sha3::{Digest, Keccak256}; use starknet::core::types::MsgToL1; use tracing::{debug, error, trace, warn}; @@ -218,10 +218,13 @@ fn l1_handler_tx_from_log(log: Log, chain_id: FieldElement) -> MessengerResult Vec { messages .iter() .map(|msg| { - let mut buf: Vec = vec![]; - buf.extend(msg.from_address.to_bytes_be()); - buf.extend(msg.to_address.to_bytes_be()); - buf.extend(FieldElement::from(msg.payload.len()).to_bytes_be()); - msg.payload.iter().for_each(|p| buf.extend(p.to_bytes_be())); - - let mut hasher = Keccak256::new(); - hasher.update(buf); - let hash = hasher.finalize(); - let hash_bytes = hash.as_slice(); - U256::from_big_endian(hash_bytes) + let hash = compute_l1_message_hash(msg.from_address, msg.to_address, &msg.payload); + U256::from_big_endian(hash.as_bytes()) }) .collect() } @@ -301,15 +295,20 @@ mod tests { // SN_GOERLI. let chain_id = starknet::macros::felt!("0x534e5f474f45524c49"); + let to_address = FieldElement::from_hex_be(to_address).unwrap(); + let from_address = FieldElement::from_hex_be(from_address).unwrap(); + + let message_hash = compute_l1_message_hash(from_address, to_address, &calldata); let expected = L1HandlerTx { calldata, chain_id, + message_hash, paid_fee_on_l1: fee, version: FieldElement::ZERO, nonce: FieldElement::from(nonce), + contract_address: to_address.into(), entry_point_selector: FieldElement::from_hex_be(selector).unwrap(), - contract_address: FieldElement::from_hex_be(to_address).unwrap().into(), }; let tx_hash = expected.calculate_hash(); diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index e20d52ecab..3fcdc70283 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; use katana_primitives::transaction::L1HandlerTx; +use katana_primitives::utils::transaction::compute_l1_message_hash; use starknet::accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter, FieldElement, MsgToL1}; use starknet::core::utils::starknet_keccak; @@ -325,10 +326,13 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: FieldElement) -> Res let mut calldata = vec![from_address]; calldata.extend(&event.data[3..]); + let message_hash = compute_l1_message_hash(from_address, to_address, &calldata); + Ok(L1HandlerTx { nonce, calldata, chain_id, + message_hash, // This is the min value paid on L1 for the message to be sent to L2. paid_fee_on_l1: 30000_u128, entry_point_selector, @@ -439,10 +443,13 @@ mod tests { transaction_hash, }; + let message_hash = compute_l1_message_hash(from_address, to_address, &calldata); + let expected = L1HandlerTx { nonce, calldata, chain_id, + message_hash, paid_fee_on_l1: 30000_u128, version: FieldElement::ZERO, entry_point_selector: selector, diff --git a/crates/katana/executor/src/blockifier/mod.rs b/crates/katana/executor/src/blockifier/mod.rs index 124a74197c..16089d132c 100644 --- a/crates/katana/executor/src/blockifier/mod.rs +++ b/crates/katana/executor/src/blockifier/mod.rs @@ -175,8 +175,50 @@ fn execute_tx( res } +pub type AcceptedTxPair = (TxWithHash, TxReceiptWithExecInfo); +pub type RejectedTxPair = (TxWithHash, TransactionExecutionError); + pub struct PendingState { pub state: Arc>, /// The transactions that have been executed. pub executed_txs: RwLock>, + /// The transactions that have been rejected. + pub rejected_txs: RwLock>, +} + +impl PendingState { + pub fn new(state: StateRefDb) -> Self { + Self { + state: Arc::new(CachedStateWrapper::new(state)), + executed_txs: RwLock::new(Vec::new()), + rejected_txs: RwLock::new(Vec::new()), + } + } + + pub fn reset_state_with(&self, state: StateRefDb) { + self.state.reset_with_new_state(state); + } + + pub fn add_executed_txs(&self, transactions: Vec<(TxWithHash, TxExecutionResult)>) { + transactions.into_iter().for_each(|(tx, res)| self.add_executed_tx(tx, res)); + } + + /// Drain the pending transactions, returning the executed and rejected transactions. + pub fn take_txs_all(&self) -> (Vec, Vec) { + let executed_txs = std::mem::take(&mut *self.executed_txs.write()); + let rejected_txs = std::mem::take(&mut *self.rejected_txs.write()); + (executed_txs, rejected_txs) + } + + fn add_executed_tx(&self, tx: TxWithHash, execution_result: TxExecutionResult) { + match execution_result { + Ok(execution_info) => { + let receipt = TxReceiptWithExecInfo::new(&tx, execution_info); + self.executed_txs.write().push((tx, receipt)); + } + Err(err) => { + self.rejected_txs.write().push((tx, err)); + } + } + } } diff --git a/crates/katana/executor/src/blockifier/outcome.rs b/crates/katana/executor/src/blockifier/outcome.rs index 3cd7a9c910..92a0aa1f59 100644 --- a/crates/katana/executor/src/blockifier/outcome.rs +++ b/crates/katana/executor/src/blockifier/outcome.rs @@ -1,8 +1,11 @@ +use std::collections::HashMap; + use blockifier::transaction::objects::TransactionExecutionInfo; use katana_primitives::receipt::{ DeclareTxReceipt, DeployAccountTxReceipt, InvokeTxReceipt, L1HandlerTxReceipt, Receipt, }; use katana_primitives::transaction::Tx; +use starknet::core::types::ExecutionResources; use super::utils::{events_from_exec_info, l2_to_l1_messages_from_exec_info}; @@ -12,15 +15,12 @@ pub struct TxReceiptWithExecInfo { } impl TxReceiptWithExecInfo { - pub fn from_tx_exec_result( - tx: impl AsRef, - execution_info: TransactionExecutionInfo, - ) -> Self { + pub fn new(tx: impl AsRef, execution_info: TransactionExecutionInfo) -> Self { let actual_fee = execution_info.actual_fee.0; let events = events_from_exec_info(&execution_info); let revert_error = execution_info.revert_error.clone(); let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info); - let actual_resources = execution_info.actual_resources.0.clone(); + let actual_resources = parse_actual_resources(&execution_info.actual_resources.0); let receipt = match tx.as_ref() { Tx::Invoke(_) => Receipt::Invoke(InvokeTxReceipt { @@ -28,7 +28,7 @@ impl TxReceiptWithExecInfo { actual_fee, revert_error, messages_sent, - actual_resources, + execution_resources: actual_resources, }), Tx::Declare(_) => Receipt::Declare(DeclareTxReceipt { @@ -36,15 +36,16 @@ impl TxReceiptWithExecInfo { actual_fee, revert_error, messages_sent, - actual_resources, + execution_resources: actual_resources, }), - Tx::L1Handler(_) => Receipt::L1Handler(L1HandlerTxReceipt { + Tx::L1Handler(tx) => Receipt::L1Handler(L1HandlerTxReceipt { events, actual_fee, revert_error, messages_sent, - actual_resources, + message_hash: tx.message_hash, + execution_resources: actual_resources, }), Tx::DeployAccount(tx) => Receipt::DeployAccount(DeployAccountTxReceipt { @@ -52,7 +53,7 @@ impl TxReceiptWithExecInfo { actual_fee, revert_error, messages_sent, - actual_resources, + execution_resources: actual_resources, contract_address: tx.contract_address, }), }; @@ -60,3 +61,32 @@ impl TxReceiptWithExecInfo { Self { receipt, execution_info } } } + +/// Parse the `actual resources` field from the execution info into a more structured type, +/// [`ExecutionResources`]. +fn parse_actual_resources(resources: &HashMap) -> ExecutionResources { + ExecutionResources { + steps: resources.get("n_steps").copied().unwrap_or_default() as u64, + memory_holes: resources.get("memory_holes").map(|x| *x as u64), + ec_op_builtin_applications: resources.get("ec_op_builtin").copied().unwrap_or_default() + as u64, + ecdsa_builtin_applications: resources.get("ecdsa_builtin").copied().unwrap_or_default() + as u64, + keccak_builtin_applications: resources.get("keccak_builtin").copied().unwrap_or_default() + as u64, + bitwise_builtin_applications: resources.get("bitwise_builtin").copied().unwrap_or_default() + as u64, + pedersen_builtin_applications: resources + .get("pedersen_builtin") + .copied() + .unwrap_or_default() as u64, + poseidon_builtin_applications: resources + .get("poseidon_builtin") + .copied() + .unwrap_or_default() as u64, + range_check_builtin_applications: resources + .get("range_check_builtin") + .copied() + .unwrap_or_default() as u64, + } +} diff --git a/crates/katana/executor/src/blockifier/state.rs b/crates/katana/executor/src/blockifier/state.rs index 1037b3a7f6..c2bda79e09 100644 --- a/crates/katana/executor/src/blockifier/state.rs +++ b/crates/katana/executor/src/blockifier/state.rs @@ -105,7 +105,7 @@ impl CachedStateWrapper { } } - pub fn reset_with_new_state(&self, db: S) { + pub(super) fn reset_with_new_state(&self, db: S) { *self.inner() = CachedState::new(db, GlobalContractCache::default()); self.sierra_class_mut().clear(); } diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 3a97362e0e..81c1409cfd 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -20,6 +20,9 @@ cairo-lang-starknet.workspace = true flate2.workspace = true starknet_api.workspace = true +sha3 = { version = "0.10.7", default-features = false } +ethers = "2.0.8" + [features] default = [ "blockifier", "rpc", "serde" ] diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index c860d2065b..2774b20924 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -2,6 +2,7 @@ use starknet::core::crypto::compute_hash_on_elements; use crate::contract::ContractAddress; use crate::transaction::{TxHash, TxWithHash}; +use crate::version::Version; use crate::FieldElement; pub type BlockIdOrTag = starknet::core::types::BlockId; @@ -32,10 +33,26 @@ pub enum FinalityStatus { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PartialHeader { pub parent_hash: FieldElement, - pub number: BlockNumber, - pub gas_price: u128, + pub gas_prices: GasPrices, pub timestamp: u64, pub sequencer_address: ContractAddress, + pub version: Version, +} + +/// The L1 gas prices. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GasPrices { + /// The price of one unit of the given resource, denominated in wei + pub eth_gas_price: u64, + /// The price of one unit of the given resource, denominated in strk + pub strk_gas_price: u64, +} + +impl GasPrices { + pub fn new(eth_gas_price: u64, strk_gas_price: u64) -> Self { + Self { eth_gas_price, strk_gas_price } + } } /// Represents a block header. @@ -44,19 +61,25 @@ pub struct PartialHeader { pub struct Header { pub parent_hash: BlockHash, pub number: BlockNumber, - pub gas_price: u128, + pub gas_prices: GasPrices, pub timestamp: u64, pub state_root: FieldElement, pub sequencer_address: ContractAddress, + pub version: Version, } impl Header { - pub fn new(partial_header: PartialHeader, state_root: FieldElement) -> Self { + pub fn new( + partial_header: PartialHeader, + number: BlockNumber, + state_root: FieldElement, + ) -> Self { Self { + number, state_root, - number: partial_header.number, - gas_price: partial_header.gas_price, + version: partial_header.version, timestamp: partial_header.timestamp, + gas_prices: partial_header.gas_prices, parent_hash: partial_header.parent_hash, sequencer_address: partial_header.sequencer_address, } diff --git a/crates/katana/primitives/src/env.rs b/crates/katana/primitives/src/env.rs index 8108e4a831..5d7633ce36 100644 --- a/crates/katana/primitives/src/env.rs +++ b/crates/katana/primitives/src/env.rs @@ -11,6 +11,10 @@ pub struct BlockEnv { pub timestamp: u64, /// The block gas price in wei. pub gas_price: u128, + /// The contract address of the sequencer. + pub sequencer_address: ContractAddress, + /// The contract address of the fee token. + pub fee_token_address: ContractAddress, } /// Starknet configuration values. @@ -18,10 +22,6 @@ pub struct BlockEnv { pub struct CfgEnv { /// The chain id. pub chain_id: u64, - /// The contract address of the sequencer. - pub sequencer_address: ContractAddress, - /// The contract address of the fee token. - pub fee_token_address: ContractAddress, /// The fee cost of the VM resources. pub vm_resource_fee_cost: HashMap, /// The maximum number of steps allowed for an invoke transaction. diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index a6965c29ff..c24e5d06ce 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -4,6 +4,7 @@ pub mod env; pub mod event; pub mod receipt; pub mod transaction; +pub mod version; pub mod conversion; #[cfg(feature = "serde")] diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index 000f7bf086..5d3471789a 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -1,12 +1,7 @@ -use std::collections::HashMap; - -use starknet::core::types::{Event, MsgToL1}; +use starknet::core::types::{Event, ExecutionResources, Hash256, MsgToL1}; use crate::contract::ContractAddress; -/// A mapping of execution resource to the amount used. -pub type ExecutionResources = HashMap; - /// Receipt for a `Invoke` transaction. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -20,7 +15,7 @@ pub struct InvokeTxReceipt { /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub actual_resources: ExecutionResources, + pub execution_resources: ExecutionResources, } /// Receipt for a `Declare` transaction. @@ -36,7 +31,7 @@ pub struct DeclareTxReceipt { /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub actual_resources: ExecutionResources, + pub execution_resources: ExecutionResources, } /// Receipt for a `L1Handler` transaction. @@ -47,12 +42,14 @@ pub struct L1HandlerTxReceipt { pub actual_fee: u128, /// Events emitted by contracts. pub events: Vec, + /// The hash of the L1 message + pub message_hash: Hash256, /// Messages sent to L1. pub messages_sent: Vec, /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub actual_resources: ExecutionResources, + pub execution_resources: ExecutionResources, } /// Receipt for a `DeployAccount` transaction. @@ -68,7 +65,7 @@ pub struct DeployAccountTxReceipt { /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub actual_resources: ExecutionResources, + pub execution_resources: ExecutionResources, /// Contract address of the deployed account contract. pub contract_address: ContractAddress, } @@ -84,6 +81,18 @@ pub enum Receipt { } impl Receipt { + /// Returns `true` if the transaction is reverted. + /// + /// A transaction is reverted if the `revert_error` field in the receipt is not `None`. + pub fn is_reverted(&self) -> bool { + match self { + Receipt::Invoke(rct) => rct.revert_error.is_some(), + Receipt::Declare(rct) => rct.revert_error.is_some(), + Receipt::L1Handler(rct) => rct.revert_error.is_some(), + Receipt::DeployAccount(rct) => rct.revert_error.is_some(), + } + } + pub fn messages_sent(&self) -> &[MsgToL1] { match self { Receipt::Invoke(rct) => &rct.messages_sent, diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 6a6ccf2b96..ccf93e386d 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,4 +1,5 @@ use derive_more::{AsRef, Deref}; +use starknet::core::types::Hash256; use crate::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, @@ -211,13 +212,14 @@ impl DeclareTx { } } -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1HandlerTx { pub nonce: Nonce, pub chain_id: ChainId, pub paid_fee_on_l1: u128, pub version: FieldElement, + pub message_hash: Hash256, pub calldata: Vec, pub contract_address: ContractAddress, pub entry_point_selector: FieldElement, diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs index 4404244113..c04760e453 100644 --- a/crates/katana/primitives/src/utils/transaction.rs +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -1,4 +1,6 @@ +use sha3::{Digest, Keccak256}; use starknet::core::crypto::compute_hash_on_elements; +use starknet::core::types::Hash256; use crate::FieldElement; @@ -156,6 +158,25 @@ pub fn compute_l1_handler_tx_hash( ]) } +/// Computes the hash of a L1 message. +pub fn compute_l1_message_hash( + from_address: FieldElement, + to_address: FieldElement, + payload: &[FieldElement], +) -> Hash256 { + let mut buf: Vec = vec![]; + buf.extend(from_address.to_bytes_be()); + buf.extend(to_address.to_bytes_be()); + buf.extend(FieldElement::from(payload.len()).to_bytes_be()); + payload.iter().for_each(|p| buf.extend(p.to_bytes_be())); + + let mut hasher = Keccak256::new(); + hasher.update(buf); + let hash = hasher.finalize(); + + Hash256::from_bytes(*AsRef::<[u8; 32]>::as_ref(&hash)) +} + #[cfg(test)] mod tests { use starknet::core::chain_id; diff --git a/crates/katana/primitives/src/version.rs b/crates/katana/primitives/src/version.rs new file mode 100644 index 0000000000..0c8b7fddf9 --- /dev/null +++ b/crates/katana/primitives/src/version.rs @@ -0,0 +1,87 @@ +use anyhow::anyhow; + +/// The currently supported version of the Starknet protocol. +pub static CURRENT_STARKNET_VERSION: Version = Version::new(0, 12, 2); // version 0.12.2 + +/// Starknet protocol version. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct Version { + major: u64, + minor: u64, + patch: u64, +} + +impl Version { + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Self { major, minor, patch } + } + + pub fn parse(version: &str) -> anyhow::Result { + let mut parts = version.split('.'); + + if parts.clone().count() > 3 { + return Err(anyhow!("invalid version format")); + } + + let major = parts.next().map(|s| s.parse::()).transpose()?.unwrap_or_default(); + let minor = parts.next().map(|s| s.parse::()).transpose()?.unwrap_or_default(); + let patch = parts.next().map(|s| s.parse::()).transpose()?.unwrap_or_default(); + + Ok(Self::new(major, minor, patch)) + } +} + +impl std::fmt::Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{major}.{minor}.{patch}", + major = self.major, + minor = self.minor, + patch = self.patch + ) + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn parse_semver_valid() { + let version = "1.9.0"; + let parsed = super::Version::parse(version).unwrap(); + assert_eq!(parsed.major, 1); + assert_eq!(parsed.minor, 9); + assert_eq!(parsed.patch, 0); + assert_eq!(String::from("1.9.0"), parsed.to_string()); + } + + #[test] + fn parse_semver_missing_parts() { + let version = "1.9"; + let parsed = super::Version::parse(version).unwrap(); + assert_eq!(parsed.major, 1); + assert_eq!(parsed.minor, 9); + assert_eq!(parsed.patch, 0); + assert_eq!(String::from("1.9.0"), parsed.to_string()); + } + + #[test] + fn parse_semver_invalid_digit_should_fail() { + let version = "0.fv.1"; + assert!(super::Version::parse(version).is_err()); + } + + #[test] + fn parse_semver_missing_digit_should_fail() { + let version = "1.."; + assert!(super::Version::parse(version).is_err()); + } + + #[test] + fn parse_semver_too_many_parts_should_fail() { + let version = "1.2.3.4"; + assert!(super::Version::parse(version).is_err()); + } +} diff --git a/crates/katana/rpc/rpc-types/src/block.rs b/crates/katana/rpc/rpc-types/src/block.rs index 32f3b12ae3..dae9949c24 100644 --- a/crates/katana/rpc/rpc-types/src/block.rs +++ b/crates/katana/rpc/rpc-types/src/block.rs @@ -1,7 +1,7 @@ use katana_primitives::block::{Block, BlockHash, BlockNumber, FinalityStatus, PartialHeader}; use katana_primitives::transaction::{TxHash, TxWithHash}; use serde::Serialize; -use starknet::core::types::BlockStatus; +use starknet::core::types::{BlockStatus, ResourcePrice}; pub type BlockTxCount = u64; @@ -11,16 +11,23 @@ pub struct BlockWithTxs(starknet::core::types::BlockWithTxs); impl BlockWithTxs { pub fn new(block_hash: BlockHash, block: Block, finality_status: FinalityStatus) -> Self { + let l1_gas_price = ResourcePrice { + price_in_wei: block.header.gas_prices.eth_gas_price, + price_in_strk: Some(block.header.gas_prices.strk_gas_price), + }; + let transactions = block.body.into_iter().map(|tx| crate::transaction::Tx::from(tx).0).collect(); Self(starknet::core::types::BlockWithTxs { block_hash, + l1_gas_price, transactions, new_root: block.header.state_root, timestamp: block.header.timestamp, block_number: block.header.number, parent_hash: block.header.parent_hash, + starknet_version: block.header.version.to_string(), sequencer_address: block.header.sequencer_address.into(), status: match finality_status { FinalityStatus::AcceptedOnL1 => BlockStatus::AcceptedOnL1, @@ -39,10 +46,17 @@ impl PendingBlockWithTxs { let transactions = transactions.into_iter().map(|tx| crate::transaction::Tx::from(tx).0).collect(); + let l1_gas_price = ResourcePrice { + price_in_wei: header.gas_prices.eth_gas_price, + price_in_strk: Some(header.gas_prices.strk_gas_price), + }; + Self(starknet::core::types::PendingBlockWithTxs { transactions, + l1_gas_price, timestamp: header.timestamp, parent_hash: header.parent_hash, + starknet_version: header.version.to_string(), sequencer_address: header.sequencer_address.into(), }) } @@ -65,13 +79,20 @@ impl BlockWithTxHashes { block: katana_primitives::block::BlockWithTxHashes, finality_status: FinalityStatus, ) -> Self { + let l1_gas_price = ResourcePrice { + price_in_wei: block.header.gas_prices.eth_gas_price, + price_in_strk: Some(block.header.gas_prices.strk_gas_price), + }; + Self(starknet::core::types::BlockWithTxHashes { block_hash, + l1_gas_price, transactions: block.body, new_root: block.header.state_root, timestamp: block.header.timestamp, block_number: block.header.number, parent_hash: block.header.parent_hash, + starknet_version: block.header.version.to_string(), sequencer_address: block.header.sequencer_address.into(), status: match finality_status { FinalityStatus::AcceptedOnL1 => BlockStatus::AcceptedOnL1, @@ -87,10 +108,17 @@ pub struct PendingBlockWithTxHashes(starknet::core::types::PendingBlockWithTxHas impl PendingBlockWithTxHashes { pub fn new(header: PartialHeader, transactions: Vec) -> Self { + let l1_gas_price = ResourcePrice { + price_in_wei: header.gas_prices.eth_gas_price, + price_in_strk: Some(header.gas_prices.strk_gas_price), + }; + Self(starknet::core::types::PendingBlockWithTxHashes { transactions, + l1_gas_price, timestamp: header.timestamp, parent_hash: header.parent_hash, + starknet_version: header.version.to_string(), sequencer_address: header.sequencer_address.into(), }) } diff --git a/crates/katana/rpc/rpc-types/src/message.rs b/crates/katana/rpc/rpc-types/src/message.rs index cb51551957..cff3689a2d 100644 --- a/crates/katana/rpc/rpc-types/src/message.rs +++ b/crates/katana/rpc/rpc-types/src/message.rs @@ -1,18 +1,30 @@ -use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx}; +use katana_primitives::transaction::L1HandlerTx; +use katana_primitives::utils::transaction::compute_l1_message_hash; +use katana_primitives::FieldElement; use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] pub struct MsgFromL1(starknet::core::types::MsgFromL1); -impl From for ExecutableTxWithHash { - fn from(value: MsgFromL1) -> Self { - let tx = L1HandlerTx { - calldata: value.0.payload, - contract_address: value.0.to_address.into(), - entry_point_selector: value.0.entry_point_selector, - ..Default::default() - }; - let hash = tx.calculate_hash(); - ExecutableTxWithHash { hash, transaction: tx.into() } +impl MsgFromL1 { + pub fn into_tx_with_chain_id(self, chain_id: FieldElement) -> L1HandlerTx { + let message_hash = compute_l1_message_hash( + // This conversion will never fail bcs `from_address` is 20 bytes and the it will only + // fail if the slice is > 32 bytes + FieldElement::from_byte_slice_be(self.0.from_address.as_bytes()).unwrap(), + self.0.to_address, + &self.0.payload, + ); + + L1HandlerTx { + chain_id, + message_hash, + calldata: self.0.payload, + nonce: Default::default(), + version: FieldElement::ZERO, + paid_fee_on_l1: Default::default(), + contract_address: self.0.to_address.into(), + entry_point_selector: self.0.entry_point_selector, + } } } diff --git a/crates/katana/rpc/rpc-types/src/receipt.rs b/crates/katana/rpc/rpc-types/src/receipt.rs index b08b28bba5..490c68d3e8 100644 --- a/crates/katana/rpc/rpc-types/src/receipt.rs +++ b/crates/katana/rpc/rpc-types/src/receipt.rs @@ -36,6 +36,7 @@ impl TxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -51,6 +52,7 @@ impl TxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -64,8 +66,10 @@ impl TxReceipt { finality_status, transaction_hash, events: rct.events, + message_hash: rct.message_hash, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -82,6 +86,7 @@ impl TxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, contract_address: rct.contract_address.into(), execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } @@ -109,6 +114,7 @@ impl PendingTxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -123,6 +129,7 @@ impl PendingTxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -135,8 +142,10 @@ impl PendingTxReceipt { PendingTransactionReceipt::L1Handler(PendingL1HandlerTransactionReceipt { transaction_hash, events: rct.events, + message_hash: rct.message_hash, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -151,6 +160,8 @@ impl PendingTxReceipt { events: rct.events, messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), + contract_address: rct.contract_address.into(), + execution_resources: rct.execution_resources, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index c276d98924..cefd0f1bcb 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -16,6 +16,7 @@ use katana_rpc_types::transaction::{ DeclareTxResult, DeployAccountTxResult, InvokeTxResult, Tx, }; use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; +use starknet::core::types::TransactionStatus; #[derive(thiserror::Error, Clone, Copy, Debug)] pub enum StarknetApiError { @@ -89,6 +90,11 @@ impl From for Error { pub trait StarknetApi { // Read API + #[method(name = "specVersion")] + async fn spec_version(&self) -> Result { + Ok("0.5.1".into()) + } + #[method(name = "chainId")] async fn chain_id(&self) -> Result; @@ -146,6 +152,12 @@ pub trait StarknetApi { transaction_hash: TxHash, ) -> Result; + #[method(name = "getTransactionStatus")] + async fn transaction_status( + &self, + transaction_hash: TxHash, + ) -> Result; + #[method(name = "getClassHashAt")] async fn class_hash_at( &self, @@ -163,9 +175,6 @@ pub trait StarknetApi { #[method(name = "getEvents")] async fn events(&self, filter: EventFilterWithPage) -> Result; - #[method(name = "pendingTransactions")] - async fn pending_transactions(&self) -> Result, Error>; - #[method(name = "estimateFee")] async fn estimate_fee( &self, diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index a10e6c56dd..6e8ea78d0b 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -7,11 +7,16 @@ use katana_core::sequencer::KatanaSequencer; use katana_core::sequencer_error::SequencerError; use katana_core::utils::contract::legacy_inner_to_rpc_class; use katana_executor::blockifier::utils::EntryPointCall; -use katana_primitives::block::{BlockHashOrNumber, BlockIdOrTag, PartialHeader}; -use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash}; +use katana_primitives::block::{ + BlockHashOrNumber, BlockIdOrTag, FinalityStatus, GasPrices, PartialHeader, +}; +use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash, TxHash}; +use katana_primitives::version::CURRENT_STARKNET_VERSION; use katana_primitives::FieldElement; use katana_provider::traits::block::{BlockHashProvider, BlockIdReader, BlockNumberProvider}; -use katana_provider::traits::transaction::TransactionProvider; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, +}; use katana_rpc_types::block::{ BlockHashAndNumber, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, PendingBlockWithTxHashes, PendingBlockWithTxs, @@ -26,7 +31,7 @@ use katana_rpc_types::transaction::{ }; use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; use katana_rpc_types_builder::ReceiptBuilder; -use starknet::core::types::BlockTag; +use starknet::core::types::{BlockTag, TransactionExecutionStatus, TransactionStatus}; use crate::api::starknet::{StarknetApiError, StarknetApiServer}; @@ -120,10 +125,15 @@ impl StarknetApiServer for StarknetApi { let block_context = self.sequencer.backend.env.read().block.clone(); let latest_hash = BlockHashProvider::latest_hash(provider)?; + let gas_prices = GasPrices { + eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(), + strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(), + }; + let header = PartialHeader { + gas_prices, parent_hash: latest_hash, - gas_price: block_context.gas_prices.eth_l1_gas_price, - number: block_context.block_number.0, + version: CURRENT_STARKNET_VERSION, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), }; @@ -189,10 +199,15 @@ impl StarknetApiServer for StarknetApi { let block_context = self.sequencer.backend.env.read().block.clone(); let latest_hash = BlockHashProvider::latest_hash(provider)?; + let gas_prices = GasPrices { + eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(), + strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(), + }; + let header = PartialHeader { + gas_prices, parent_hash: latest_hash, - gas_price: block_context.gas_prices.eth_l1_gas_price, - number: block_context.block_number.0, + version: CURRENT_STARKNET_VERSION, timestamp: block_context.block_timestamp.0, sequencer_address: block_context.sequencer_address.into(), }; @@ -338,19 +353,6 @@ impl StarknetApiServer for StarknetApi { Ok(events) } - async fn pending_transactions(&self) -> Result, Error> { - let Some(pending_state) = self.sequencer.pending_state() else { return Ok(vec![]) }; - - let txs = pending_state - .executed_txs - .read() - .iter() - .map(|(tx, _)| tx.clone().into()) - .collect::>(); - - Ok(txs) - } - async fn call( &self, request: FunctionCall, @@ -459,9 +461,16 @@ impl StarknetApiServer for StarknetApi { message: MsgFromL1, block_id: BlockIdOrTag, ) -> Result { + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) + .map_err(|_| StarknetApiError::UnexpectedError)?; + + let tx = message.into_tx_with_chain_id(chain_id); + let hash = tx.calculate_hash(); + let tx: ExecutableTxWithHash = ExecutableTxWithHash { hash, transaction: tx.into() }; + let res = self .sequencer - .estimate_fee(vec![message.into()], block_id) + .estimate_fee(vec![tx], block_id) .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, @@ -525,4 +534,59 @@ impl StarknetApiServer for StarknetApi { Ok(tx_hash.into()) } + + async fn transaction_status( + &self, + transaction_hash: TxHash, + ) -> Result { + let provider = self.sequencer.backend.blockchain.provider(); + + let tx_status = TransactionStatusProvider::transaction_status(provider, transaction_hash) + .map_err(|_| StarknetApiError::UnexpectedError)?; + + if let Some(status) = tx_status { + let receipt = ReceiptProvider::receipt_by_hash(provider, transaction_hash) + .map_err(|_| StarknetApiError::UnexpectedError)? + .ok_or(StarknetApiError::UnexpectedError)?; + + let execution_status = if receipt.is_reverted() { + TransactionExecutionStatus::Reverted + } else { + TransactionExecutionStatus::Succeeded + }; + + Ok(match status { + FinalityStatus::AcceptedOnL1 => TransactionStatus::AcceptedOnL1(execution_status), + FinalityStatus::AcceptedOnL2 => TransactionStatus::AcceptedOnL2(execution_status), + }) + } else { + let pending_state = self.sequencer.pending_state(); + let state = pending_state.ok_or(StarknetApiError::TxnHashNotFound)?; + let executed_txs = state.executed_txs.read(); + + // attemps to find in the valid transactions list first (executed_txs) + // if not found, then search in the rejected transactions list (rejected_txs) + if let Some(is_reverted) = executed_txs + .iter() + .find(|(tx, _)| tx.hash == transaction_hash) + .map(|(_, rct)| rct.receipt.is_reverted()) + { + let exec_status = if is_reverted { + TransactionExecutionStatus::Reverted + } else { + TransactionExecutionStatus::Succeeded + }; + + Ok(TransactionStatus::AcceptedOnL2(exec_status)) + } else { + let rejected_txs = state.rejected_txs.read(); + + rejected_txs + .iter() + .find(|(tx, _)| tx.hash == transaction_hash) + .map(|_| TransactionStatus::Rejected) + .ok_or(Error::from(StarknetApiError::TxnHashNotFound)) + } + } + } } diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 96ac74e4fc..01c9c9f07d 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -22,9 +22,7 @@ use katana_primitives::FieldElement; use parking_lot::Mutex; use starknet::core::types::{BlockId, ContractClass, StarknetError}; use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::{ - JsonRpcClient, MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{JsonRpcClient, Provider, ProviderError}; use tracing::{error, trace}; use crate::providers::in_memory::cache::CacheStateDb; @@ -483,13 +481,7 @@ fn handle_contract_or_class_not_found_err( Ok(value) => Ok(Some(value)), Err(ForkedBackendError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: - MaybeUnknownErrorCode::Known( - StarknetError::ContractNotFound | StarknetError::ClassHashNotFound, - ), - .. - }, + StarknetError::ContractNotFound | StarknetError::ClassHashNotFound, ))) => Ok(None), Err(e) => Err(e), diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs index 7b1c04adcc..4b8fc8b851 100644 --- a/crates/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/src/ops/migration/mod.rs @@ -25,9 +25,7 @@ use starknet::providers::jsonrpc::HttpTransport; mod migration_test; mod ui; -use starknet::providers::{ - JsonRpcClient, MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, -}; +use starknet::providers::{JsonRpcClient, Provider, ProviderError}; use starknet::signers::{LocalWallet, Signer}; use ui::MigrationUi; @@ -187,10 +185,9 @@ pub(crate) async fn setup_env( match account.provider().get_class_hash_at(BlockId::Tag(BlockTag::Pending), address).await { Ok(_) => Ok(account), - Err(ProviderError::StarknetError(StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), - .. - })) => Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())), + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => { + Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())) + } Err(e) => Err(e.into()), } } diff --git a/examples/rpc/starknet/starknet_chainId.hurl b/examples/rpc/starknet/starknet_chainId.hurl index 805516f414..1b34280da6 100644 --- a/examples/rpc/starknet/starknet_chainId.hurl +++ b/examples/rpc/starknet/starknet_chainId.hurl @@ -10,4 +10,4 @@ Content-Type: application/json HTTP 200 [Asserts] jsonpath "$.error" not exists -jsonpath "$.result" == "0x4b4154414e41" +jsonpath "$.result" matches /^0x[A-Fa-f0-9]+$/ diff --git a/examples/rpc/starknet/starknet_getBlockWithTxHashes.hurl b/examples/rpc/starknet/starknet_getBlockWithTxHashes.hurl index 85e86da4ec..e978694681 100644 --- a/examples/rpc/starknet/starknet_getBlockWithTxHashes.hurl +++ b/examples/rpc/starknet/starknet_getBlockWithTxHashes.hurl @@ -4,7 +4,9 @@ Content-Type: application/json "jsonrpc": "2.0", "method": "starknet_getBlockWithTxHashes", "params": [ - "latest" + { + "block_number": 0 + } ], "id":1 } @@ -12,5 +14,9 @@ Content-Type: application/json HTTP 200 [Asserts] jsonpath "$.error" not exists -# jsonpath "$.result.block_hash" matches /^0x[A-Fa-f0-9]+$/ jsonpath "$.result.transactions" isCollection +jsonpath "$.result.block_number" isInteger +jsonpath "$.result.block_hash" matches /^0x[A-Fa-f0-9]+$/ +jsonpath "$.result.parent_hash" matches /^0x[A-Fa-f0-9]+$/ +jsonpath "$.result.starknet_version" matches /^[0-9]+.[0-9]+.[0-9]+$/ + diff --git a/examples/rpc/starknet/starknet_getBlockWithTxs.hurl b/examples/rpc/starknet/starknet_getBlockWithTxs.hurl index 133994050d..106716e4a1 100644 --- a/examples/rpc/starknet/starknet_getBlockWithTxs.hurl +++ b/examples/rpc/starknet/starknet_getBlockWithTxs.hurl @@ -12,5 +12,8 @@ Content-Type: application/json HTTP 200 [Asserts] jsonpath "$.error" not exists -# jsonpath "$.result.block_hash" matches /^0x[A-Fa-f0-9]+$/ jsonpath "$.result.transactions" isCollection +jsonpath "$.result.block_number" isInteger +jsonpath "$.result.block_hash" matches /^0x[A-Fa-f0-9]+$/ +jsonpath "$.result.parent_hash" matches /^0x[A-Fa-f0-9]+$/ +jsonpath "$.result.starknet_version" matches /^[0-9]+.[0-9]+.[0-9]+$/ diff --git a/examples/rpc/starknet/starknet_getClassAt.hurl b/examples/rpc/starknet/starknet_getClassAt.hurl index 1cf31f3d04..c7ac14c1c0 100644 --- a/examples/rpc/starknet/starknet_getClassAt.hurl +++ b/examples/rpc/starknet/starknet_getClassAt.hurl @@ -5,7 +5,7 @@ Content-Type: application/json "method": "starknet_getClassAt", "params": [ "latest", - "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" ], "id":1 } diff --git a/examples/rpc/starknet/starknet_getClassHashAt.hurl b/examples/rpc/starknet/starknet_getClassHashAt.hurl index 630b8e7884..ecbfc0251f 100644 --- a/examples/rpc/starknet/starknet_getClassHashAt.hurl +++ b/examples/rpc/starknet/starknet_getClassHashAt.hurl @@ -5,7 +5,7 @@ Content-Type: application/json "method": "starknet_getClassHashAt", "params": [ "pending", - "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" ], "id": 1 } diff --git a/examples/rpc/starknet/starknet_getStorageAt.hurl b/examples/rpc/starknet/starknet_getStorageAt.hurl index cfc4af0781..6c21db3e01 100644 --- a/examples/rpc/starknet/starknet_getStorageAt.hurl +++ b/examples/rpc/starknet/starknet_getStorageAt.hurl @@ -1,3 +1,4 @@ +# Get the value for the ERC20 token name storage slot for the fee token. POST http://0.0.0.0:5050 Content-Type: application/json { @@ -5,13 +6,53 @@ Content-Type: application/json "method": "starknet_getStorageAt", "params": [ "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "0x37c96558f481f70f2c8b6c29e2d05668a3ec0b30c56618a59c16edd587bfc44", + "0x0341c1bdfd89f69748aa00b5742b03adbffd79b8e80cab5c50d91cd8c2a79be1", "pending" ], - "id":1 + "id": 1 } HTTP 200 [Asserts] jsonpath "$.error" not exists -jsonpath "$.result" exists +jsonpath "$.result" matches /^0x[A-Fa-f0-9]+$/ + + +# Uninitialized storage slot of a contract should default to zero. +POST http://0.0.0.0:5050 +Content-Type: application/json +{ + "jsonrpc": "2.0", + "method": "starknet_getStorageAt", + "params": [ + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "0x3434343434", + "pending" + ], + "id": 1 +} + +HTTP 200 +[Asserts] +jsonpath "$.error" not exists +jsonpath "$.result" equals "0x0" + + +# Fetching storage slot of non-existant contract should fail. +POST http://0.0.0.0:5050 +Content-Type: application/json +{ + "jsonrpc": "2.0", + "method": "starknet_getStorageAt", + "params": [ + "0xdead", + "0xdead", + "pending" + ], + "id": 1 +} + +HTTP 200 +[Asserts] +jsonpath "$.error.message" equals "Contract not found" +jsonpath "$.result" not exists diff --git a/examples/rpc/starknet/starknet_getTransactionStatus.hurl b/examples/rpc/starknet/starknet_getTransactionStatus.hurl new file mode 100644 index 0000000000..13cc11f322 --- /dev/null +++ b/examples/rpc/starknet/starknet_getTransactionStatus.hurl @@ -0,0 +1,15 @@ +POST http://0.0.0.0:5050 +Content-Type: application/json +{ + "jsonrpc": "2.0", + "method": "starknet_getTransactionStatus", + "params": [ + "0x1" + ], + "id": 1 +} + +HTTP 200 +[Asserts] +jsonpath "$.error.message" equals "Transaction hash not found" +jsonpath "$.result" not exists diff --git a/examples/rpc/starknet/starknet_pendingTransactions.hurl b/examples/rpc/starknet/starknet_specVersion.hurl similarity index 62% rename from examples/rpc/starknet/starknet_pendingTransactions.hurl rename to examples/rpc/starknet/starknet_specVersion.hurl index 0c0a4986bf..e49f15c58b 100644 --- a/examples/rpc/starknet/starknet_pendingTransactions.hurl +++ b/examples/rpc/starknet/starknet_specVersion.hurl @@ -2,12 +2,12 @@ POST http://0.0.0.0:5050 Content-Type: application/json { "jsonrpc": "2.0", - "method": "starknet_pendingTransactions", + "method": "starknet_specVersion", "params": [], - "id":1 + "id": 1 } HTTP 200 [Asserts] jsonpath "$.error" not exists -jsonpath "$.result" isCollection +jsonpath "$.result" equals "0.5.1" From b5c6c11c39351b12f141f932f66985409d487b79 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 13 Dec 2023 11:05:09 -0500 Subject: [PATCH 123/192] Prepare v0.4.0-rc2 --- Cargo.lock | 42 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07de8cc4bd..1fca8fd6cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2544,7 +2544,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2592,7 +2592,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2614,7 +2614,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "starknet", @@ -2622,7 +2622,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "assert_fs", @@ -2653,7 +2653,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "crypto-bigint", "hex", @@ -2668,7 +2668,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "assert_fs", @@ -5248,7 +5248,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "assert_matches", "clap", @@ -5266,7 +5266,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "assert_matches", @@ -5300,7 +5300,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5315,7 +5315,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "blockifier", @@ -5331,7 +5331,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "blockifier", @@ -5350,7 +5350,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "auto_impl", @@ -5367,7 +5367,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "assert_matches", @@ -5399,7 +5399,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "derive_more", @@ -5412,7 +5412,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "katana-executor", @@ -8102,7 +8102,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "assert_fs", @@ -9286,7 +9286,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "async-trait", "camino", @@ -9312,7 +9312,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "async-trait", @@ -9348,7 +9348,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "async-graphql", @@ -9387,7 +9387,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "bytes", "crypto-bigint", @@ -9425,7 +9425,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index bd0d88fc0a..9cfdff6145 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.0-rc1" +version = "0.4.0-rc2" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index ea08cb38bc..13a14afc9c 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index a20f23a2e7..efbf9c70dd 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.0-rc1" +version = "0.4.0-rc2" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 6de23a835d..4338af99c4 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "test_crate" -version = "0.4.0-rc1" +version = "0.4.0-rc2" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 09cafa61e4..a3d836115c 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc1" +version = "0.4.0-rc2" dependencies = [ "dojo_plugin", ] From ec8fe13f8f11e95cd285cd5e6d2b4c5a0c3213c3 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:02:38 +0100 Subject: [PATCH 124/192] use full_name for artifacts (#1234) * use full name for artifacts * sozo: include external contracts in manifest * Clean up --------- Co-authored-by: Tarrence van As --- Cargo.lock | 191 +++++++++--------- crates/dojo-lang/src/compiler.rs | 106 ++++++---- .../src/manifest_test_data/cairo_v240 | 8 +- .../dojo-lang/src/manifest_test_data/manifest | 12 +- crates/dojo-world/src/manifest.rs | 6 +- crates/dojo-world/src/migration/strategy.rs | 9 +- crates/dojo-world/src/migration/world_test.rs | 14 +- crates/katana/core/src/sequencer.rs | 7 +- crates/sozo/src/commands/execute.rs | 3 +- crates/torii/graphql/src/tests/mod.rs | 3 +- 10 files changed, 194 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fca8fd6cf..f550fc6ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,13 +399,13 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.1.1", "async-executor", - "async-io 2.2.1", + "async-io 2.2.2", "async-lock 3.2.0", "blocking", "futures-lite 2.1.0", @@ -459,7 +459,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.25.0", - "syn 2.0.39", + "syn 2.0.41", "thiserror", ] @@ -521,9 +521,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" dependencies = [ "async-lock 3.2.0", "cfg-if", @@ -566,7 +566,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -614,7 +614,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -631,7 +631,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -855,7 +855,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1348,7 +1348,7 @@ checksum = "c8cc59c40344194d2cc825071080d887826dcf0df37de71e58fc8aa4c344bb84" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1840,7 +1840,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1915,11 +1915,10 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] @@ -2113,9 +2112,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2123,9 +2122,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -2134,22 +2133,21 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "b9bcf5bdbfdd6030fb4a1c497b5d5fc5921aa2f60d359a17e249c0e6df3de153" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2157,9 +2155,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -2195,12 +2193,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2267,7 +2265,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2289,7 +2287,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2822,9 +2820,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.31" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +checksum = "a3286168faae03a0e583f6fde17c02c8b8bba2dcc2061d0f7817066e5b0af706" dependencies = [ "serde", ] @@ -2986,7 +2984,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.39", + "syn 2.0.41", "toml 0.8.8", "walkdir", ] @@ -3004,7 +3002,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3030,7 +3028,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.39", + "syn 2.0.41", "tempfile", "thiserror", "tiny-keccak", @@ -3197,9 +3195,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.10" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbb8258be8305fb0237d7b295f47bb24ff1b136a535f473baf40e70468515aa" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" dependencies = [ "indenter", "once_cell", @@ -3223,6 +3221,15 @@ dependencies = [ "serde", ] +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -3483,7 +3490,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3554,7 +3561,7 @@ checksum = "d4cf186fea4af17825116f72932fe52cce9a13bae39ff63b4dc0cfdb3fb4bde1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3899,11 +3906,11 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c1e554a87759e672c7d2e37211e761aa390c61ffcd3753a57c51173143f3cb" +checksum = "1f8cf8c2266f63e582b7eb206799b63aa5fa68ee510ad349f637dfe2d0653de0" dependencies = [ - "faster-hex", + "faster-hex 0.9.0", "thiserror", ] @@ -3932,9 +3939,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ce8d03ec25de952be7d2a9adce2a4c2cb8f7fc2d4c25be91301be9707f380b" +checksum = "f3f308f5cd2992e96a274b0d1931e9a0e44fdcba87695ead3f6df30d8a697e9c" dependencies = [ "bitflags 2.4.1", "bstr", @@ -3974,7 +3981,7 @@ checksum = "02a5bcaf6704d9354a3071cede7e77d366a5980c7352e102e2c2f9b645b1d3ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -4071,7 +4078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50052c0f76c5af5acda41177fb55b60c1e484cc246ae919d8d21129cd1000a4e" dependencies = [ "bstr", - "faster-hex", + "faster-hex 0.8.1", "gix-trace", "thiserror", ] @@ -4576,9 +4583,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -5530,9 +5537,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -6078,7 +6085,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6171,7 +6178,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6443,7 +6450,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6507,7 +6514,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6551,7 +6558,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6713,7 +6720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -6871,7 +6878,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "regex", - "syn 2.0.39", + "syn 2.0.41", "tempfile", "which 4.4.2", ] @@ -6899,7 +6906,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -7150,7 +7157,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#8a670d57ec1c8f3cdf617e09f939d7d2624b6ded" +source = "git+https://github.com/paradigmxyz/reth.git#013b4c93db27e6c1926d3295319f9a4319d4f691" dependencies = [ "bitflags 2.4.1", "byteorder", @@ -7165,7 +7172,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#8a670d57ec1c8f3cdf617e09f939d7d2624b6ded" +source = "git+https://github.com/paradigmxyz/reth.git#013b4c93db27e6c1926d3295319f9a4319d4f691" dependencies = [ "bindgen", "cc", @@ -7391,9 +7398,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "salsa" @@ -7722,9 +7729,9 @@ dependencies = [ [[package]] name = "serde-untagged" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba3ac59c62f51b75a6bfad8840b2ede4a81ff5cc23c200221ef479ae75a4aa3" +checksum = "c38885c2d9d8f038478583b7acf8f6029d020ba4b20e9dcaeb8799d67a04aae7" dependencies = [ "erased-serde", "serde", @@ -7748,7 +7755,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -7792,7 +7799,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -7841,7 +7848,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -7876,7 +7883,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8512,7 +8519,7 @@ checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ "starknet-curve 0.4.0", "starknet-ff", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8555,7 +8562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "840be1a7eb5735863eee47d3a3f26df45b9be2c519e8da294e74b4d0524d77d1" dependencies = [ "starknet-core", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8596,9 +8603,9 @@ dependencies = [ [[package]] name = "starknet_api" -version = "0.6.0-rc3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874475a79285b03525dcb6773c5b6436d0bb937de9791c43a02a682a0fcbefd4" +checksum = "0f9d0c42ef835df4cb8fc0468825aa5ad2825da927a89d5346ec0ef74573f158" dependencies = [ "cairo-lang-starknet", "derive_more", @@ -8688,7 +8695,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8730,9 +8737,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -8852,7 +8859,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8872,7 +8879,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -8997,7 +9004,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -9224,7 +9231,7 @@ dependencies = [ "proc-macro2", "prost-build 0.12.3", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -9551,7 +9558,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -9580,7 +9587,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -9699,7 +9706,7 @@ checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -9993,7 +10000,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -10027,7 +10034,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10357,9 +10364,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.26" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] @@ -10404,11 +10411,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc6ab6ec1907d1a901cdbcd2bd4cb9e7d64ce5c9739cbb97d3c391acd8c7fae" +checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995" dependencies = [ "libc", + "linux-raw-sys 0.4.12", + "rustix 0.38.28", ] [[package]] @@ -10455,7 +10464,7 @@ checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -10475,7 +10484,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] diff --git a/crates/dojo-lang/src/compiler.rs b/crates/dojo-lang/src/compiler.rs index 234219c955..56972ce92a 100644 --- a/crates/dojo-lang/src/compiler.rs +++ b/crates/dojo-lang/src/compiler.rs @@ -83,7 +83,7 @@ impl Compiler for DojoCompiler { let contracts = find_project_contracts( db.upcast_mut(), main_crate_ids.clone(), - props.build_external_contracts, + props.build_external_contracts.clone(), )?; let contract_paths = contracts @@ -104,18 +104,17 @@ impl Compiler for DojoCompiler { HashMap::new(); for (decl, class) in zip(contracts, classes) { - let target_name = &unit.target().name; - let contract_name = decl.submodule_id.name(db.upcast_mut()); - let file_name = format!("{target_name}-{contract_name}.json"); + let contract_full_path = decl.module_id().full_path(db.upcast_mut()); + let file_name = format!("{contract_full_path}.json"); let mut file = target_dir.open_rw(file_name.clone(), "output file", ws.config())?; serde_json::to_writer_pretty(file.deref_mut(), &class) - .with_context(|| format!("failed to serialize contract: {contract_name}"))?; + .with_context(|| format!("failed to serialize contract: {contract_full_path}"))?; let class_hash = compute_class_hash_of_contract_class(&class).with_context(|| { - format!("problem computing class hash for contract `{contract_name}`") + format!("problem computing class hash for contract `{contract_full_path}`") })?; - compiled_classes.insert(contract_name, (class_hash, class.abi)); + compiled_classes.insert(contract_full_path.into(), (class_hash, class.abi)); } let mut manifest = target_dir @@ -123,7 +122,13 @@ impl Compiler for DojoCompiler { .map(|file| dojo_world::manifest::Manifest::try_from(file.deref()).unwrap_or_default()) .unwrap_or_default(); - update_manifest(&mut manifest, db, &main_crate_ids, compiled_classes)?; + update_manifest( + &mut manifest, + db, + &main_crate_ids, + compiled_classes, + props.build_external_contracts, + )?; manifest.write_to_path( target_dir.open_rw("manifest.json", "output file", ws.config())?.path(), @@ -181,9 +186,9 @@ fn find_project_contracts( pub fn collect_core_crate_ids(db: &RootDatabase) -> Vec { [ - ContractSelector("dojo::base::base".to_string()), - ContractSelector("dojo::executor::executor".to_string()), - ContractSelector("dojo::world::world".to_string()), + ContractSelector(BASE_CONTRACT_NAME.to_string()), + ContractSelector(EXECUTOR_CONTRACT_NAME.to_string()), + ContractSelector(WORLD_CONTRACT_NAME.to_string()), ] .iter() .map(|selector| selector.package().into()) @@ -209,6 +214,7 @@ fn update_manifest( db: &RootDatabase, crate_ids: &[CrateId], compiled_artifacts: HashMap)>, + external_contracts: Option>, ) -> anyhow::Result<()> { fn get_compiled_artifact_from_map<'a>( artifacts: &'a HashMap)>, @@ -219,6 +225,8 @@ fn update_manifest( )) } + let mut crate_ids = crate_ids.to_vec(); + let world = { let (hash, abi) = get_compiled_artifact_from_map(&compiled_artifacts, WORLD_CONTRACT_NAME)?; Contract { @@ -249,8 +257,13 @@ fn update_manifest( let mut contracts = BTreeMap::new(); let mut computed = BTreeMap::new(); + if let Some(external_contracts) = external_contracts { + let external_crate_ids = collect_external_crate_ids(db, external_contracts); + crate_ids.extend(external_crate_ids); + } + for crate_id in crate_ids { - for module_id in db.crate_modules(*crate_id).as_ref() { + for module_id in db.crate_modules(crate_id).as_ref() { let file_infos = db.module_generated_file_infos(*module_id).unwrap_or_default(); for aux_data in file_infos .iter() @@ -289,7 +302,7 @@ fn update_manifest( }); for model in &models { - contracts.remove(model.0.to_case(Case::Snake).as_str()); + contracts.remove(model.0.as_str()); } do_update_manifest(manifest, world, executor, base, models, contracts)?; @@ -300,33 +313,38 @@ fn update_manifest( /// Finds the inline modules annotated as models in the given crate_ids and /// returns the corresponding Models. fn get_dojo_model_artifacts( - db: &dyn SemanticGroup, + db: &RootDatabase, aux_data: &DojoAuxData, module_id: ModuleId, compiled_classes: &HashMap)>, ) -> anyhow::Result> { let mut models = HashMap::with_capacity(aux_data.models.len()); + let module_name = module_id.full_path(db); + let module_name = module_name.as_str(); + for model in &aux_data.models { if let Ok(Some(ModuleItemId::Struct(_))) = db.module_item_by_name(module_id, model.name.clone().into()) { let model_contract_name = model.name.to_case(Case::Snake); - - let (class_hash, abi) = compiled_classes - .get(model_contract_name.as_str()) - .cloned() - .ok_or(anyhow!("Model {} not found in target.", model.name))?; - - models.insert( - model.name.clone(), - dojo_world::manifest::Model { - abi, - class_hash, - name: model.name.clone(), - members: model.members.clone(), - }, - ); + let model_full_name = format!("{module_name}::{}", &model_contract_name); + + let compiled_class = compiled_classes.get(model_full_name.as_str()).cloned(); + + if let Some((class_hash, abi)) = compiled_class { + models.insert( + model_full_name.clone(), + dojo_world::manifest::Model { + abi, + class_hash, + name: model_full_name.clone(), + members: model.members.clone(), + }, + ); + } else { + println!("Model {} not found in target.", model_full_name.clone()); + } } } @@ -339,14 +357,16 @@ fn get_dojo_computed_values( aux_data: &ComputedValuesAuxData, computed_values: &mut BTreeMap>, ) { - if let ModuleId::Submodule(submod_id) = module_id { - let contract = submod_id.name(db); - if !computed_values.contains_key(&contract) { - computed_values.insert(contract.clone(), vec![]); + if let ModuleId::Submodule(_) = module_id { + let module_name = module_id.full_path(db); + let module_name = SmolStr::from(module_name); + + if !computed_values.contains_key(&module_name) { + computed_values.insert(module_name.clone(), vec![]); } - let computed_vals = computed_values.get_mut(&contract).unwrap(); + let computed_vals = computed_values.get_mut(&module_name).unwrap(); computed_vals.push(ComputedValueEntrypoint { - contract, + contract: module_name, entrypoint: aux_data.entrypoint.clone(), model: aux_data.model.clone(), }) @@ -363,11 +383,15 @@ fn get_dojo_contract_artifacts( .contracts .iter() .filter(|name| !matches!(name.as_ref(), "world" | "executor" | "base")) + .filter(|_name| { + let module_name = module_id.full_path(db); + compiled_classes.get(module_name.as_str()).cloned().is_some() + }) .map(|name| { let module_name = module_id.full_path(db); - let module_last_name = module_name.split("::").last().unwrap(); + let module_name = module_name.as_str(); - let reads = match SYSTEM_READS.lock().unwrap().get(module_last_name) { + let reads = match SYSTEM_READS.lock().unwrap().get(module_name) { Some(models) => { models.clone().into_iter().collect::>().into_iter().collect() } @@ -375,20 +399,20 @@ fn get_dojo_contract_artifacts( }; let write_entries = SYSTEM_WRITES.lock().unwrap(); - let writes = match write_entries.get(module_last_name) { + let writes = match write_entries.get(module_name) { Some(write_ops) => find_module_rw(db, module_id, write_ops), None => vec![], }; let (class_hash, abi) = compiled_classes - .get(name) + .get(module_name) .cloned() .ok_or(anyhow!("Contract {name} not found in target."))?; Ok(( - name.clone(), + SmolStr::from(module_name), Contract { - name: name.clone(), + name: module_name.into(), class_hash, abi, writes, diff --git a/crates/dojo-lang/src/manifest_test_data/cairo_v240 b/crates/dojo-lang/src/manifest_test_data/cairo_v240 index 8841c3876a..ab46139485 100644 --- a/crates/dojo-lang/src/manifest_test_data/cairo_v240 +++ b/crates/dojo-lang/src/manifest_test_data/cairo_v240 @@ -6,7 +6,7 @@ test_compiler_cairo_v240 //! > expected_manifest_file { "world": { - "name": "world", + "name": "dojo::world::world", "address": null, "class_hash": "0x5ac623f0c96059936bd2d0904bdd31799e430fe08a0caff7a5f497260b16497", "abi": [ @@ -789,7 +789,7 @@ test_compiler_cairo_v240 "computed": [] }, "executor": { - "name": "executor", + "name": "dojo::executor::executor", "address": null, "class_hash": "0x585507fa2818fe78e66da6ea4c5915376739f4abf509d41153f60a16cb1f68d", "abi": [ @@ -850,7 +850,7 @@ test_compiler_cairo_v240 "computed": [] }, "base": { - "name": "base", + "name": "dojo::base::base", "class_hash": "0x6c458453d35753703ad25632deec20a29faf8531942ec109e6eb0650316a2bc", "abi": [ { @@ -953,7 +953,7 @@ test_compiler_cairo_v240 }, "contracts": [ { - "name": "cairo_v240", + "name": "cairo_v240::cairo_v240", "address": null, "class_hash": "0x714353d2a54cfd74820f89c19d8911a379214d5d4ccf369d74b5f5844dc565f", "abi": [ diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest index 0a737a9b4f..787374d71f 100644 --- a/crates/dojo-lang/src/manifest_test_data/manifest +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -6,7 +6,7 @@ test_manifest_file //! > expected_manifest_file { "world": { - "name": "world", + "name": "dojo::world::world", "address": null, "class_hash": "0x5ac623f0c96059936bd2d0904bdd31799e430fe08a0caff7a5f497260b16497", "abi": [ @@ -789,7 +789,7 @@ test_manifest_file "computed": [] }, "executor": { - "name": "executor", + "name": "dojo::executor::executor", "address": null, "class_hash": "0x585507fa2818fe78e66da6ea4c5915376739f4abf509d41153f60a16cb1f68d", "abi": [ @@ -850,7 +850,7 @@ test_manifest_file "computed": [] }, "base": { - "name": "base", + "name": "dojo::base::base", "class_hash": "0x6c458453d35753703ad25632deec20a29faf8531942ec109e6eb0650316a2bc", "abi": [ { @@ -953,7 +953,7 @@ test_manifest_file }, "contracts": [ { - "name": "actions", + "name": "dojo_examples::actions::actions", "address": null, "class_hash": "0x69c6bec7de74fc2404fe6b68ad8ece7be81ad6d861b38a8ba8fa583bfc3666b", "abi": [ @@ -1203,7 +1203,7 @@ test_manifest_file "computed": [] }, { - "name": "moves", + "name": "dojo_examples::models::moves", "address": null, "class_hash": "0x64495ca6dc1dc328972697b30468cea364bcb7452bbb6e4aaad3e4b3f190147", "abi": [ @@ -1372,7 +1372,7 @@ test_manifest_file "computed": [] }, { - "name": "position", + "name": "dojo_examples::models::position", "address": null, "class_hash": "0x2b233bba9a232a5e891c85eca9f67beedca7a12f9768729ff017bcb62d25c9d", "abi": [ diff --git a/crates/dojo-world/src/manifest.rs b/crates/dojo-world/src/manifest.rs index 4184d56258..5854b7110f 100644 --- a/crates/dojo-world/src/manifest.rs +++ b/crates/dojo-world/src/manifest.rs @@ -26,9 +26,9 @@ use crate::contracts::WorldContractReader; #[path = "manifest_test.rs"] mod test; -pub const WORLD_CONTRACT_NAME: &str = "world"; -pub const EXECUTOR_CONTRACT_NAME: &str = "executor"; -pub const BASE_CONTRACT_NAME: &str = "base"; +pub const WORLD_CONTRACT_NAME: &str = "dojo::world::world"; +pub const EXECUTOR_CONTRACT_NAME: &str = "dojo::executor::executor"; +pub const BASE_CONTRACT_NAME: &str = "dojo::base::base"; #[derive(Error, Debug)] pub enum ManifestError { diff --git a/crates/dojo-world/src/migration/strategy.rs b/crates/dojo-world/src/migration/strategy.rs index 3c007baf6c..cde6af17ee 100644 --- a/crates/dojo-world/src/migration/strategy.rs +++ b/crates/dojo-world/src/migration/strategy.rs @@ -3,7 +3,6 @@ use std::fs; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context, Result}; -use convert_case::{Case, Casing}; use starknet::core::types::FieldElement; use starknet::core::utils::{cairo_short_string_to_felt, get_contract_address}; use starknet_crypto::{poseidon_hash_many, poseidon_hash_single}; @@ -99,7 +98,7 @@ where continue; } - let name = file_name_str.split('-').last().unwrap().trim_end_matches(".json").to_string(); + let name = file_name_str.trim_end_matches(".json").to_string(); artifact_paths.insert(name, entry.path()); } @@ -165,8 +164,7 @@ fn evaluate_class_to_migrate( match class.remote { Some(remote) if remote == class.local && !world_contract_will_migrate => Ok(None), _ => { - let path = - find_artifact_path(class.name.to_case(Case::Snake).as_str(), artifact_paths)?; + let path = find_artifact_path(class.name.as_str(), artifact_paths)?; Ok(Some(ClassMigration { diff: class.clone(), artifact_path: path.clone() })) } } @@ -183,8 +181,7 @@ fn evaluate_contracts_to_migrate( match c.remote { Some(remote) if remote == c.local && !world_contract_will_migrate => continue, _ => { - let path = - find_artifact_path(c.name.to_case(Case::Snake).as_str(), artifact_paths)?; + let path = find_artifact_path(c.name.as_str(), artifact_paths)?; comps_to_migrate.push(ContractMigration { diff: c.clone(), artifact_path: path.clone(), diff --git a/crates/dojo-world/src/migration/world_test.rs b/crates/dojo-world/src/migration/world_test.rs index daf9be2367..28dc999e8e 100644 --- a/crates/dojo-world/src/migration/world_test.rs +++ b/crates/dojo-world/src/migration/world_test.rs @@ -21,7 +21,7 @@ fn no_diff_when_local_and_remote_are_equal() { let models = vec![Model { members: vec![], - name: "Model".into(), + name: "dojo_mock::models::model".into(), class_hash: 11_u32.into(), ..Default::default() }]; @@ -56,13 +56,13 @@ fn diff_when_local_and_remote_are_different() { let models = vec![ Model { members: vec![], - name: "Model".into(), + name: "dojo_mock::models::model".into(), class_hash: felt!("0x11"), ..Default::default() }, Model { members: vec![], - name: "Model2".into(), + name: "dojo_mock::models::model_2".into(), class_hash: felt!("0x22"), ..Default::default() }, @@ -70,13 +70,13 @@ fn diff_when_local_and_remote_are_different() { let contracts = vec![ Contract { - name: "my_contract".into(), + name: "dojo_mock::contracts::my_contract".into(), class_hash: felt!("0x1111"), address: Some(felt!("0x2222")), ..Contract::default() }, Contract { - name: "my_contract_2".into(), + name: "dojo_mock::contracts::my_contract_2".into(), class_hash: felt!("0x3333"), address: Some(felt!("4444")), ..Contract::default() @@ -100,6 +100,6 @@ fn diff_when_local_and_remote_are_different() { let diff = WorldDiff::compute(local, Some(remote)); assert_eq!(diff.count_diffs(), 4); - assert!(diff.models.iter().any(|m| m.name == "Model2")); - assert!(diff.contracts.iter().any(|c| c.name == "my_contract")); + assert!(diff.models.iter().any(|m| m.name == "dojo_mock::models::model_2")); + assert!(diff.contracts.iter().any(|c| c.name == "dojo_mock::contracts::my_contract")); } diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index b5b648cc0c..8655dee3cd 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -442,11 +442,8 @@ impl KatanaSequencer { // contract_address: ContractAddress, // storage_key: StorageKey, // value: StorageValue, - // ) -> Result<(), SequencerError> { - // if let Some(ref pending) = self.pending_state() { - // StateWriter::set_storage(&pending.state, contract_address, storage_key, value)?; - // } - // Ok(()) + // ) -> Result<(), SequencerError> { if let Some(ref pending) = self.pending_state() { + // StateWriter::set_storage(&pending.state, contract_address, storage_key, value)?; } Ok(()) // } } diff --git a/crates/sozo/src/commands/execute.rs b/crates/sozo/src/commands/execute.rs index 666c0b8357..a2110a0231 100644 --- a/crates/sozo/src/commands/execute.rs +++ b/crates/sozo/src/commands/execute.rs @@ -11,7 +11,8 @@ use crate::ops::execute; #[derive(Debug, Args)] #[command(about = "Execute a system with the given calldata.")] pub struct ExecuteArgs { - #[arg(help = "The address of the contract to be executed.")] + #[arg(help = "The address of the contract to be executed. Or fully qualified contract name \ + (ex: dojo_example::actions::actions")] pub contract: String, #[arg(help = "The name of the entrypoint to be executed.")] diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index b8df16fb89..d6abd80668 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -254,7 +254,8 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); // Execute `create` and insert 10 records into storage - let records_contract = "0x414e9776a1626963ba21c4f45ddd7b4d2ac907d20c230e9c6a2e6119c2e5d"; + + let records_contract = "0x7b44a597f4027588f226293105c77c99c436ab4016bbcb51f6711ab1ccfeeb0"; let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], From 772e4b6d3de18ad94be11b88f8c25c4000a9a489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Galv=C3=A1n=20=28Dub=29?= Date: Wed, 13 Dec 2023 18:05:21 -0300 Subject: [PATCH 125/192] [core] Inline macro - `delete!` (#1271) * adding delete_macro (wip) * fix test delete!() * fix delete.rs --- crates/dojo-core/src/world_test.cairo | 8 +- crates/dojo-lang/src/inline_macros/delete.rs | 164 +++++++++++++++++++ crates/dojo-lang/src/inline_macros/mod.rs | 1 + crates/dojo-lang/src/plugin.rs | 2 + 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 crates/dojo-lang/src/inline_macros/delete.rs diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index 22cb3c372d..07bb4e1e9b 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -33,6 +33,7 @@ struct Fizz { trait Ibar { fn set_foo(self: @TContractState, a: felt252, b: u128); fn delete_foo(self: @TContractState); + fn delete_foo_macro(self: @TContractState, foo: Foo); fn set_char(self: @TContractState, a: felt252, b: u32); } @@ -67,6 +68,10 @@ mod bar { .delete_entity('Foo', array![get_caller_address().into()].span(), layout.span()); } + fn delete_foo_macro(self: @ContractState, foo: Foo) { + delete!(self.world.read(), Foo { caller: foo.caller, a: foo.a, b: foo.b }); + } + fn set_char(self: @ContractState, a: felt252, b: u32) { set!( self.world.read(), @@ -151,7 +156,8 @@ fn test_delete() { assert(stored.b == 1337, 'data not stored'); // delete model - bar_contract.delete_foo(); + bar_contract.delete_foo_macro(stored); + let deleted: Foo = get!(world, get_caller_address(), Foo); assert(deleted.a == 0, 'data not deleted'); assert(deleted.b == 0, 'data not deleted'); diff --git a/crates/dojo-lang/src/inline_macros/delete.rs b/crates/dojo-lang/src/inline_macros/delete.rs new file mode 100644 index 0000000000..728a7ee10f --- /dev/null +++ b/crates/dojo-lang/src/inline_macros/delete.rs @@ -0,0 +1,164 @@ +use std::collections::HashMap; + +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_defs::plugin::{ + InlineMacroExprPlugin, InlinePluginResult, NamedPlugin, PluginDiagnostic, PluginGeneratedFile, +}; +use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; +use cairo_lang_syntax::node::ast::{ExprPath, ExprStructCtorCall, FunctionWithBody, ItemModule}; +use cairo_lang_syntax::node::kind::SyntaxKind; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; + +use super::unsupported_arg_diagnostic; +use super::utils::{parent_of_kind, SystemRWOpRecord, SYSTEM_WRITES}; + +#[derive(Debug, Default)] +pub struct DeleteMacro; + +impl NamedPlugin for DeleteMacro { + const NAME: &'static str = "delete"; +} + +impl InlineMacroExprPlugin for DeleteMacro { + fn generate_code( + &self, + db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, + syntax: &ast::ExprInlineMacro, + ) -> InlinePluginResult { + let ast::WrappedArgList::ParenthesizedArgList(arg_list) = syntax.arguments(db) else { + return unsupported_bracket_diagnostic(db, syntax); + }; + let mut builder = PatchBuilder::new(db); + builder.add_str("{"); + + let args = arg_list.arguments(db).elements(db); + + if args.len() != 2 { + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), + message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), + }], + }; + } + + let world = &args[0]; + + let ast::ArgClause::Unnamed(models) = args[1].arg_clause(db) else { + return unsupported_arg_diagnostic(db, syntax); + }; + + let mut bundle = vec![]; + + match models.value(db) { + ast::Expr::Parenthesized(parens) => { + let syntax_node = parens.expr(db).as_syntax_node(); + bundle.push((syntax_node.get_text(db), syntax_node)); + } + ast::Expr::Tuple(list) => { + list.expressions(db).elements(db).into_iter().for_each(|expr| { + let syntax_node = expr.as_syntax_node(); + bundle.push((syntax_node.get_text(db), syntax_node)); + }) + } + ast::Expr::StructCtorCall(ctor) => { + let syntax_node = ctor.as_syntax_node(); + bundle.push((syntax_node.get_text(db), syntax_node)); + } + _ => { + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), + }], + }; + } + } + + if bundle.is_empty() { + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + message: "Invalid arguments: No models provided.".to_string(), + stable_ptr: arg_list.arguments(db).stable_ptr().untyped(), + }], + }; + } + + let module_syntax_node = + parent_of_kind(db, &syntax.as_syntax_node(), SyntaxKind::ItemModule); + let module_name = if let Some(module_syntax_node) = &module_syntax_node { + let mod_ast = ItemModule::from_syntax_node(db, module_syntax_node.clone()); + mod_ast.name(db).as_syntax_node().get_text_without_trivia(db) + } else { + "".into() + }; + + let fn_syntax_node = + parent_of_kind(db, &syntax.as_syntax_node(), SyntaxKind::FunctionWithBody); + let fn_name = if let Some(fn_syntax_node) = &fn_syntax_node { + let fn_ast = FunctionWithBody::from_syntax_node(db, fn_syntax_node.clone()); + fn_ast.declaration(db).name(db).as_syntax_node().get_text_without_trivia(db) + } else { + "".into() + }; + + for (entity, syntax_node) in bundle { + // db.lookup_intern_file(key0); + if !module_name.is_empty() && !fn_name.is_empty() { + let mut system_writes = SYSTEM_WRITES.lock().unwrap(); + // fn_syntax_node + if system_writes.get(&module_name).is_none() { + system_writes.insert(module_name.clone(), HashMap::new()); + } + let fns = system_writes.get_mut(&module_name).unwrap(); + if fns.get(&fn_name).is_none() { + fns.insert(fn_name.clone(), vec![]); + } + + match syntax_node.kind(db) { + SyntaxKind::ExprPath => { + fns.get_mut(&fn_name).unwrap().push(SystemRWOpRecord::Path( + ExprPath::from_syntax_node(db, syntax_node), + )); + } + // SyntaxKind::StatementExpr => { + // todo!() + // } + SyntaxKind::ExprStructCtorCall => { + fns.get_mut(&fn_name).unwrap().push(SystemRWOpRecord::StructCtor( + ExprStructCtorCall::from_syntax_node(db, syntax_node.clone()), + )); + } + _ => eprintln!( + "Unsupport component value type {} for semantic writer analysis", + syntax_node.kind(db) + ), + } + } + + builder.add_str(&format!( + " + let __delete_macro_value__ = {}; + {}.delete_entity(dojo::model::Model::name(@__delete_macro_value__), + dojo::model::Model::keys(@__delete_macro_value__), + dojo::model::Model::layout(@__delete_macro_value__));", + entity, + world.as_syntax_node().get_text(db), + )); + } + builder.add_str("}"); + + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "delete_inline_macro".into(), + content: builder.code, + diagnostics_mappings: builder.diagnostics_mappings, + aux_data: None, + }), + diagnostics: vec![], + } + } +} diff --git a/crates/dojo-lang/src/inline_macros/mod.rs b/crates/dojo-lang/src/inline_macros/mod.rs index 2cc237256d..08953fc9c5 100644 --- a/crates/dojo-lang/src/inline_macros/mod.rs +++ b/crates/dojo-lang/src/inline_macros/mod.rs @@ -3,6 +3,7 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use smol_str::SmolStr; +pub mod delete; pub mod emit; pub mod get; pub mod set; diff --git a/crates/dojo-lang/src/plugin.rs b/crates/dojo-lang/src/plugin.rs index 725a0e1b93..e97f5763f5 100644 --- a/crates/dojo-lang/src/plugin.rs +++ b/crates/dojo-lang/src/plugin.rs @@ -21,6 +21,7 @@ use smol_str::SmolStr; use url::Url; use crate::contract::DojoContract; +use crate::inline_macros::delete::DeleteMacro; use crate::inline_macros::emit::EmitMacro; use crate::inline_macros::get::GetMacro; use crate::inline_macros::set::SetMacro; @@ -216,6 +217,7 @@ pub fn dojo_plugin_suite() -> PluginSuite { suite .add_plugin::() + .add_inline_macro_plugin::() .add_inline_macro_plugin::() .add_inline_macro_plugin::() .add_inline_macro_plugin::(); From 6bbeecc8a73d0a3b32d694427b38b41a2d598207 Mon Sep 17 00:00:00 2001 From: Yun Date: Wed, 13 Dec 2023 13:09:24 -0800 Subject: [PATCH 126/192] Fix grpc sub entity ids (#1273) --- crates/torii/grpc/src/server/mod.rs | 11 ++++++++--- crates/torii/grpc/src/server/subscriptions/entity.rs | 12 +++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 43005f25c9..ba36a65869 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use dojo_types::schema::Ty; use futures::Stream; -use hex::encode; use proto::world::{ MetadataRequest, MetadataResponse, RetrieveEntitiesRequest, RetrieveEntitiesResponse, SubscribeModelsRequest, SubscribeModelsResponse, @@ -282,7 +281,7 @@ impl DojoWorld { async fn subscribe_entities( &self, - ids: Vec, + ids: Vec, ) -> Result>, Error> { self.entity_manager.add_subscriber(ids).await } @@ -379,7 +378,13 @@ impl proto::world::world_server::World for DojoWorld { request: Request, ) -> ServiceResult { let SubscribeEntitiesRequest { ids } = request.into_inner(); - let ids = ids.iter().map(encode).collect(); + let ids = ids + .iter() + .map(|id| { + FieldElement::from_byte_slice_be(id) + .map_err(|e| Status::invalid_argument(e.to_string())) + }) + .collect::, _>>()?; let rx = self.subscribe_entities(ids).await.map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) diff --git a/crates/torii/grpc/src/server/subscriptions/entity.rs b/crates/torii/grpc/src/server/subscriptions/entity.rs index d1e290ca9e..a39529e681 100644 --- a/crates/torii/grpc/src/server/subscriptions/entity.rs +++ b/crates/torii/grpc/src/server/subscriptions/entity.rs @@ -23,7 +23,7 @@ use crate::proto; pub struct EntitiesSubscriber { /// Entity ids that the subscriber is interested in - ids: HashSet, + ids: HashSet, /// The channel to send the response back to the subscriber. sender: Sender>, } @@ -36,7 +36,7 @@ pub struct EntityManager { impl EntityManager { pub async fn add_subscriber( &self, - ids: Vec, + ids: Vec, ) -> Result>, Error> { let id = rand::thread_rng().gen::(); let (sender, receiver) = channel(1); @@ -85,8 +85,9 @@ impl Service { let mut closed_stream = Vec::new(); for (idx, sub) in subs.subscribers.read().await.iter() { + let felt_id = FieldElement::from_str(id).map_err(ParseError::FromStr)?; // publish all updates if ids is empty or only ids that are subscribed to - if sub.ids.is_empty() || sub.ids.contains(id) { + if sub.ids.is_empty() || sub.ids.contains(&felt_id) { let models_query = r#" SELECT group_concat(entity_model.model_id) as model_names FROM entities @@ -114,10 +115,7 @@ impl Service { let resp = proto::world::SubscribeEntityResponse { entity: Some(proto::types::Entity { - id: FieldElement::from_str(id) - .map_err(ParseError::FromStr)? - .to_bytes_be() - .to_vec(), + id: felt_id.to_bytes_be().to_vec(), models, }), }; From 17c1b42910cb751fe3986ed44ce4e3a6a16b1e51 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 13 Dec 2023 16:15:50 -0500 Subject: [PATCH 127/192] Prepare v0.4.0 --- Cargo.lock | 42 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f550fc6ede..dc20a3d5a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,7 +2542,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2590,7 +2590,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2612,7 +2612,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "starknet", @@ -2620,7 +2620,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "assert_fs", @@ -2651,7 +2651,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "crypto-bigint", "hex", @@ -2666,7 +2666,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "assert_fs", @@ -5255,7 +5255,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "assert_matches", "clap", @@ -5273,7 +5273,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "assert_matches", @@ -5307,7 +5307,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5322,7 +5322,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "blockifier", @@ -5338,7 +5338,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "blockifier", @@ -5357,7 +5357,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "auto_impl", @@ -5374,7 +5374,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "assert_matches", @@ -5406,7 +5406,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "derive_more", @@ -5419,7 +5419,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "katana-executor", @@ -8109,7 +8109,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "assert_fs", @@ -9293,7 +9293,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "async-trait", "camino", @@ -9319,7 +9319,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "async-trait", @@ -9355,7 +9355,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "async-graphql", @@ -9394,7 +9394,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "bytes", "crypto-bigint", @@ -9432,7 +9432,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 9cfdff6145..80e9f7b01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.0-rc2" +version = "0.4.0" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 13a14afc9c..500d0d7c5f 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index efbf9c70dd..443c47837d 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.0-rc2" +version = "0.4.0" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 4338af99c4..73f2bccb3b 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "test_crate" -version = "0.4.0-rc2" +version = "0.4.0" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index a3d836115c..83d15e7bbb 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc2" +version = "0.4.0" dependencies = [ "dojo_plugin", ] From a62b24d14b567a9229b6882de0ec36dce6b091c3 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 14 Dec 2023 13:59:11 +0800 Subject: [PATCH 128/192] fix(katana-provider): take correct amount of tx (#1274) --- crates/katana/storage/provider/src/providers/fork/mod.rs | 2 +- crates/katana/storage/provider/src/providers/in_memory/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index c43e5ec635..09d8c77985 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -204,7 +204,7 @@ impl TransactionProvider for ForkedProvider { .iter() .enumerate() .skip(offset) - .take(offset + count) + .take(count) .map(|(n, tx)| { let hash = self.storage.read().transaction_hashes.get(&(n as u64)).cloned().unwrap(); diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index b727b44638..0355e5bbfc 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -201,7 +201,7 @@ impl TransactionProvider for InMemoryProvider { .iter() .enumerate() .skip(offset) - .take(offset + count) + .take(count) .map(|(n, tx)| { let hash = self.storage.read().transaction_hashes.get(&(n as u64)).cloned().unwrap(); From 96f773b147b3abdc4870e03be76778337cf8fc7b Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 14 Dec 2023 22:59:21 +0800 Subject: [PATCH 129/192] test(katana-provider): test block insertions for providers (#1275) --- Cargo.lock | 44 +++++++++ crates/katana/primitives/src/block.rs | 14 ++- crates/katana/primitives/src/transaction.rs | 10 +- crates/katana/storage/provider/Cargo.toml | 9 +- .../provider/src/providers/in_memory/mod.rs | 17 ---- .../provider/src/providers/in_memory/state.rs | 6 +- .../katana/storage/provider/tests/common.rs | 97 +++++++++++++++++++ .../katana/storage/provider/tests/fixtures.rs | 24 +++++ 8 files changed, 190 insertions(+), 31 deletions(-) create mode 100644 crates/katana/storage/provider/tests/common.rs create mode 100644 crates/katana/storage/provider/tests/fixtures.rs diff --git a/Cargo.lock b/Cargo.lock index dc20a3d5a6..11a6e52ab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5365,6 +5365,9 @@ dependencies = [ "katana-db", "katana-primitives", "parking_lot 0.12.1", + "rand", + "rstest", + "rstest_reuse", "starknet", "thiserror", "tokio", @@ -7269,6 +7272,47 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.41", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" +dependencies = [ + "quote", + "rand", + "rustc_version", + "syn 2.0.41", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 2774b20924..b451dbc3f1 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -40,7 +40,7 @@ pub struct PartialHeader { } /// The L1 gas prices. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GasPrices { /// The price of one unit of the given resource, denominated in wei @@ -56,7 +56,7 @@ impl GasPrices { } /// Represents a block header. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Header { pub parent_hash: BlockHash, @@ -107,7 +107,7 @@ impl Header { } /// Represents a Starknet full block. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Block { pub header: Header, @@ -150,7 +150,15 @@ pub struct SealedBlock { pub body: Vec, } +impl SealedBlock { + /// Unseal the block. + pub fn unseal(self) -> Block { + Block { header: self.header.header, body: self.body } + } +} + /// A sealed block along with its status. +#[derive(Debug, Clone)] pub struct SealedBlockWithStatus { pub block: SealedBlock, /// The block status. diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index ccf93e386d..7bd98eac65 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -15,7 +15,7 @@ pub type TxHash = FieldElement; /// The sequential number for all the transactions.. pub type TxNumber = u64; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Tx { Invoke(InvokeTx), @@ -142,7 +142,7 @@ impl InvokeTx { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DeclareTx { V1(DeclareTxV1), @@ -172,7 +172,7 @@ pub struct DeclareTxV1 { } /// Represents a declare transaction type. -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeclareTxV2 { pub nonce: Nonce, @@ -239,7 +239,7 @@ impl L1HandlerTx { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeployAccountTx { pub nonce: Nonce, @@ -269,7 +269,7 @@ impl DeployAccountTx { } } -#[derive(Debug, Clone, AsRef, Deref)] +#[derive(Debug, Clone, AsRef, Deref, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxWithHash { /// The hash of the transaction. diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index 99b94712de..abd85129d3 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -22,10 +22,13 @@ starknet.workspace = true tokio.workspace = true [features] -default = [ "fork", "in-memory" ] -fork = [ "in-memory" ] -in-memory = [ ] +default = ["fork", "in-memory"] +fork = ["in-memory"] +in-memory = [] [dev-dependencies] +rand = "0.8.5" +rstest = "0.18.2" +rstest_reuse = "0.6.0" starknet.workspace = true url.workspace = true diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 0355e5bbfc..1f074f8831 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -495,20 +495,3 @@ impl StateWriter for InMemoryProvider { Ok(()) } } - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use parking_lot::RwLock; - - use super::cache::{CacheDb, CacheStateDb}; - use super::InMemoryProvider; - - pub(super) fn create_mock_provider() -> InMemoryProvider { - let storage = RwLock::new(CacheDb::new(())); - let state = Arc::new(CacheStateDb::new(())); - let historical_states = Default::default(); - InMemoryProvider { storage, state, historical_states } - } -} diff --git a/crates/katana/storage/provider/src/providers/in_memory/state.rs b/crates/katana/storage/provider/src/providers/in_memory/state.rs index 6d90feae5b..1355cea528 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/state.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/state.rs @@ -220,7 +220,7 @@ mod tests { use starknet::macros::felt; use super::*; - use crate::providers::in_memory::tests::create_mock_provider; + use crate::providers::in_memory::InMemoryProvider; use crate::traits::state::StateFactoryProvider; const STORAGE_KEY: StorageKey = felt!("0x1"); @@ -273,7 +273,7 @@ mod tests { fn latest_state_provider() { let state = create_mock_state(); - let mut provider = create_mock_provider(); + let mut provider = InMemoryProvider::new(); provider.state = Arc::new(state); let latest_state_provider = StateFactoryProvider::latest(&provider).unwrap(); @@ -340,7 +340,7 @@ mod tests { state.contract_state.write().entry(ADDR_1).and_modify(|e| e.nonce = ADDR_1_NONCE_AT_3); state.contract_state.write().entry(ADDR_2).and_modify(|e| e.nonce = ADDR_2_NONCE_AT_3); - let mut provider = create_mock_provider(); + let mut provider = InMemoryProvider::new(); provider.state = Arc::new(state); provider.historical_states.write().insert(1, Box::new(snapshot_1)); provider.historical_states.write().insert(2, Box::new(snapshot_2)); diff --git a/crates/katana/storage/provider/tests/common.rs b/crates/katana/storage/provider/tests/common.rs new file mode 100644 index 0000000000..9fa4086298 --- /dev/null +++ b/crates/katana/storage/provider/tests/common.rs @@ -0,0 +1,97 @@ +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, FinalityStatus, Header, SealedBlockWithStatus, +}; +use katana_primitives::transaction::{Tx, TxHash, TxWithHash}; +use katana_primitives::FieldElement; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::block::{BlockProvider, BlockWriter}; +use katana_provider::traits::transaction::TransactionProvider; +use katana_provider::BlockchainProvider; +use rstest_reuse::{self, *}; + +mod fixtures; + +use fixtures::{fork_provider, in_memory_provider}; + +fn generate_dummy_txs(count: u64) -> Vec { + let mut txs = Vec::with_capacity(count as usize); + for _ in 0..count { + txs.push(TxWithHash { + hash: TxHash::from(rand::random::()), + transaction: Tx::Invoke(Default::default()), + }); + } + txs +} + +fn generate_dummy_blocks(count: u64) -> Vec { + let mut blocks = Vec::with_capacity(count as usize); + let mut parent_hash: BlockHash = 0u8.into(); + + for i in 0..count { + let body = generate_dummy_txs(rand::random::() % 10); + let header = Header { parent_hash, number: i, ..Default::default() }; + let block = + Block { header, body }.seal_with_hash(FieldElement::from(rand::random::())); + parent_hash = block.header.hash; + + blocks.push(SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }); + } + + blocks +} + +#[template] +#[rstest::rstest] +#[case::insert_1_block(1)] +#[case::insert_2_block(2)] +#[case::insert_5_block(5)] +#[case::insert_10_block(10)] +fn insert_block_cases(#[case] block_count: u64) {} + +#[apply(insert_block_cases)] +fn insert_block_with_in_memory_provider( + #[from(in_memory_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> anyhow::Result<()> { + insert_block_impl(provider, block_count) +} + +#[apply(insert_block_cases)] +fn insert_block_with_fork_provider( + #[from(fork_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> anyhow::Result<()> { + insert_block_impl(provider, block_count) +} + +fn insert_block_impl(provider: BlockchainProvider, count: u64) -> anyhow::Result<()> +where + Db: BlockProvider + BlockWriter, +{ + let blocks = generate_dummy_blocks(count); + + for block in &blocks { + provider.insert_block_with_states_and_receipts( + block.clone(), + Default::default(), + Default::default(), + )?; + } + + for block in blocks { + let block_id = BlockHashOrNumber::Hash(block.block.header.hash); + let expected_block = block.block.unseal(); + + let actual_block = provider.block(block_id)?; + let actual_block_txs = provider.transactions_by_block(block_id)?; + let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; + + assert_eq!(actual_block_tx_count, Some(expected_block.body.len() as u64)); + assert_eq!(actual_block_txs, Some(expected_block.body.clone())); + assert_eq!(actual_block, Some(expected_block)); + } + + Ok(()) +} diff --git a/crates/katana/storage/provider/tests/fixtures.rs b/crates/katana/storage/provider/tests/fixtures.rs new file mode 100644 index 0000000000..d6c0ff6f85 --- /dev/null +++ b/crates/katana/storage/provider/tests/fixtures.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; + +use katana_primitives::block::BlockHashOrNumber; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::BlockchainProvider; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use url::Url; + +#[rstest::fixture] +pub fn in_memory_provider() -> BlockchainProvider { + BlockchainProvider::new(InMemoryProvider::new()) +} + +#[rstest::fixture] +pub fn fork_provider( + #[default("http://localhost:5050")] rpc: &str, + #[default(0)] block_num: u64, +) -> BlockchainProvider { + let rpc_provider = JsonRpcClient::new(HttpTransport::new(Url::parse(rpc).unwrap())); + let provider = ForkedProvider::new(Arc::new(rpc_provider), BlockHashOrNumber::Num(block_num)); + BlockchainProvider::new(provider) +} From e0a16635d1b833e0f5b89a9bdf358d784751c1b4 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 14 Dec 2023 12:27:33 -0500 Subject: [PATCH 130/192] Prepare v0.4.1 --- Cargo.lock | 42 +++++++++---------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- crates/dojo-lang/Scarb.toml | 2 +- .../simple_crate/Scarb.toml | 2 +- crates/katana/rpc/Cargo.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11a6e52ab9..70174674b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,7 +2542,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-lang" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2590,7 +2590,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2612,7 +2612,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "starknet", @@ -2620,7 +2620,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -2651,7 +2651,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.0" +version = "0.4.1" dependencies = [ "crypto-bigint", "hex", @@ -2666,7 +2666,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -5255,7 +5255,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.0" +version = "0.4.1" dependencies = [ "assert_matches", "clap", @@ -5273,7 +5273,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_matches", @@ -5307,7 +5307,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5322,7 +5322,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "blockifier", @@ -5338,7 +5338,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "blockifier", @@ -5357,7 +5357,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "auto_impl", @@ -5377,7 +5377,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_matches", @@ -5409,7 +5409,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "derive_more", @@ -5422,7 +5422,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "katana-executor", @@ -8153,7 +8153,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -9337,7 +9337,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.0" +version = "0.4.1" dependencies = [ "async-trait", "camino", @@ -9363,7 +9363,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "async-trait", @@ -9399,7 +9399,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "async-graphql", @@ -9438,7 +9438,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.0" +version = "0.4.1" dependencies = [ "bytes", "crypto-bigint", @@ -9476,7 +9476,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 80e9f7b01d..f3050b7bb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.0" +version = "0.4.1" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 500d0d7c5f..ee17c212ea 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0" +version = "0.4.1" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 443c47837d..a8809ef181 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.0" +version = "0.4.1" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/dojo-lang/Scarb.toml b/crates/dojo-lang/Scarb.toml index 6824d0ffd4..a533c510d2 100644 --- a/crates/dojo-lang/Scarb.toml +++ b/crates/dojo-lang/Scarb.toml @@ -1,5 +1,5 @@ [package] name = "dojo_plugin" -version = "0.4.0" +version = "0.4.1" [cairo-plugin] diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml index 73f2bccb3b..64a96a42c6 100644 --- a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "test_crate" -version = "0.4.0" +version = "0.4.1" [cairo] sierra-replace-ids = true diff --git a/crates/katana/rpc/Cargo.toml b/crates/katana/rpc/Cargo.toml index 114a181902..33d21f9570 100644 --- a/crates/katana/rpc/Cargo.toml +++ b/crates/katana/rpc/Cargo.toml @@ -29,7 +29,7 @@ starknet_api.workspace = true thiserror.workspace = true tokio.workspace = true tower = { version = "0.4.13", features = [ "full" ] } -tower-http = { version = "0.4.0", features = [ "full" ] } +tower-http = { version = "0.4.1", features = [ "full" ] } tracing.workspace = true [dev-dependencies] diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 83d15e7bbb..9474cc125c 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0" +version = "0.4.1" dependencies = [ "dojo_plugin", ] From f7e273738e27ae891099d1d72c9fe11e939996d6 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 14 Dec 2023 15:19:38 -0500 Subject: [PATCH 131/192] Add prometheus exporter to Katana (#1278) --- Cargo.lock | 255 ++++++++++++++++++++++- crates/katana/Cargo.toml | 17 +- crates/katana/src/args.rs | 21 ++ crates/katana/src/main.rs | 21 ++ crates/katana/src/prometheus_exporter.rs | 180 ++++++++++++++++ crates/katana/src/utils.rs | 46 ++++ 6 files changed, 532 insertions(+), 8 deletions(-) create mode 100644 crates/katana/src/prometheus_exporter.rs create mode 100644 crates/katana/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 70174674b3..8cbfbbd22d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,7 +178,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "hashbrown 0.13.2", + "hashbrown 0.13.1", "itertools 0.10.5", "num-traits 0.2.17", "zeroize", @@ -237,7 +237,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "hashbrown 0.13.2", + "hashbrown 0.13.1", ] [[package]] @@ -1085,7 +1085,7 @@ dependencies = [ "cached_proc_macro", "cached_proc_macro_types", "futures", - "hashbrown 0.13.2", + "hashbrown 0.13.1", "instant", "once_cell", "thiserror", @@ -2781,6 +2781,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enr" version = "0.9.1" @@ -4210,7 +4216,7 @@ dependencies = [ "bitflags 2.4.1", "gix-path", "libc", - "windows", + "windows 0.48.0", ] [[package]] @@ -4481,9 +4487,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" dependencies = [ "ahash 0.8.6", ] @@ -5109,6 +5115,37 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jemalloc-ctl" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cffc705424a344c054e135d12ee591402f4539245e8bbd64e6c9eaa9458b63c" +dependencies = [ + "jemalloc-sys", + "libc", + "paste", +] + +[[package]] +name = "jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc" +dependencies = [ + "jemalloc-sys", + "libc", +] + [[package]] name = "jobserver" version = "0.1.27" @@ -5257,14 +5294,23 @@ dependencies = [ name = "katana" version = "0.4.1" dependencies = [ + "anyhow", "assert_matches", "clap", "clap_complete", "console", + "hyper", + "jemalloc-ctl", + "jemallocator", "katana-core", "katana-rpc", + "metrics", + "metrics-exporter-prometheus", + "metrics-process", + "metrics-util", "serde_json", "starknet_api", + "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -5570,6 +5616,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libproc" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229004ebba9d1d5caf41623f1523b6d52abb47d9f6ab87f7e6fc992e3b854aef" +dependencies = [ + "bindgen", + "errno", + "libc", +] + [[package]] name = "libredox" version = "0.0.1" @@ -5645,6 +5702,15 @@ dependencies = [ "url", ] +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -5703,6 +5769,80 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" +dependencies = [ + "ahash 0.8.6", + "metrics-macros", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" +dependencies = [ + "base64 0.21.5", + "hyper", + "indexmap 1.9.3", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "metrics-process" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674a02f6ad51326c2106d9aa5a07d1f759695b655c06df0bba5d5fb338ac0a4" +dependencies = [ + "libproc", + "mach2", + "metrics", + "once_cell", + "procfs", + "rlimit", + "windows 0.51.1", +] + +[[package]] +name = "metrics-util" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de2ed6e491ed114b40b732e4d1659a9d53992ebd87490c44a6ffe23739d973e" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.13.1", + "indexmap 1.9.3", + "metrics", + "num_cpus", + "ordered-float 3.9.2", + "quanta", + "radix_trie", + "sketches-ddsketch", +] + [[package]] name = "mimalloc" version = "0.1.39" @@ -5887,6 +6027,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.27.1" @@ -6217,6 +6366,15 @@ dependencies = [ "num-traits 0.2.17", ] +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits 0.2.17", +] + [[package]] name = "os_pipe" version = "1.1.4" @@ -6792,6 +6950,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.4.1", + "hex", + "lazy_static", + "procfs-core", + "rustix 0.38.28", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.4.1", + "hex", +] + [[package]] name = "prodash" version = "26.2.2" @@ -6930,6 +7111,22 @@ dependencies = [ "prost 0.12.3", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -6951,6 +7148,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -6990,6 +7197,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -7230,6 +7446,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rlimit" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.5.2" @@ -7787,7 +8012,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] @@ -8055,6 +8280,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sketches-ddsketch" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" + [[package]] name = "slab" version = "0.4.9" @@ -10199,6 +10430,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + [[package]] name = "windows-core" version = "0.51.1" diff --git a/crates/katana/Cargo.toml b/crates/katana/Cargo.toml index 93b1f4a95d..d4b252cb86 100644 --- a/crates/katana/Cargo.toml +++ b/crates/katana/Cargo.toml @@ -7,22 +7,37 @@ repository.workspace = true version.workspace = true [dependencies] +anyhow.workspace = true clap.workspace = true clap_complete.workspace = true console.workspace = true +hyper.workspace = true katana-core = { path = "core" } katana-rpc = { path = "rpc" } serde_json.workspace = true starknet_api.workspace = true +thiserror.workspace = true tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true url.workspace = true +# Metrics +metrics = "0.21.1" +metrics-exporter-prometheus = "0.12.1" +metrics-process = "1.0.9" +metrics-util = "0.15.0" + +[target.'cfg(not(windows))'.dependencies] +jemallocator = { version = "0.5.0", optional = true } +jemalloc-ctl = { version = "0.5.0", optional = true } + [dev-dependencies] assert_matches = "1.5.0" [features] -default = [ "messaging" ] +default = [ "jemalloc", "messaging" ] +jemalloc = ["dep:jemallocator", "dep:jemalloc-ctl"] +jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] messaging = [ "katana-core/messaging" ] starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index ed2b8238ee..0d8ea96a76 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -1,3 +1,16 @@ +//! Katana binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. + +use std::net::SocketAddr; use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; @@ -13,6 +26,8 @@ use tracing::Subscriber; use tracing_subscriber::{fmt, EnvFilter}; use url::Url; +use crate::utils::parse_socket_address; + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] @@ -50,6 +65,12 @@ pub struct KatanaArgs { #[arg(help = "Output logs in JSON format.")] pub json_log: bool, + /// Enable Prometheus metrics. + /// + /// The metrics will be served at the given interface and port. + #[arg(long, value_name = "SOCKET", value_parser = parse_socket_address, help_heading = "Metrics")] + pub metrics: Option, + #[arg(long)] #[arg(requires = "rpc_url")] #[arg(value_name = "BLOCK_NUMBER")] diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index 45da67f74b..93d4d8f8ce 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -13,9 +13,18 @@ use tokio::signal::ctrl_c; use tracing::info; mod args; +mod prometheus_exporter; +mod utils; use args::Commands::Completions; use args::KatanaArgs; +#[cfg(all(feature = "jemalloc", unix))] +use jemallocator as _; + +// We use jemalloc for performance reasons +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; #[tokio::main] async fn main() -> Result<(), Box> { @@ -65,6 +74,18 @@ async fn main() -> Result<(), Box> { } } + if let Some(listen_addr) = config.metrics { + let prometheus_handle = prometheus_exporter::install_recorder()?; + + info!(target: "katana::cli", addr = %listen_addr, "Starting metrics endpoint"); + prometheus_exporter::serve( + listen_addr, + prometheus_handle, + metrics_process::Collector::default(), + ) + .await?; + } + // Wait until Ctrl + C is pressed, then shutdown ctrl_c().await?; handle.stop()?; diff --git a/crates/katana/src/prometheus_exporter.rs b/crates/katana/src/prometheus_exporter.rs new file mode 100644 index 0000000000..d79b5b5bc6 --- /dev/null +++ b/crates/katana/src/prometheus_exporter.rs @@ -0,0 +1,180 @@ +//! Prometheus exporter +//! Adapted from Paradigm's [`reth`](https://github.com/paradigmxyz/reth/blob/c1d7d2bde398bcf410c7e2df13fd7151fc2a58b9/bin/reth/src/prometheus_exporter.rs) +use std::convert::Infallible; +use std::net::SocketAddr; +use std::sync::Arc; + +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Request, Response, Server}; +use metrics::{describe_gauge, gauge}; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; +use metrics_util::layers::{PrefixLayer, Stack}; + +pub(crate) trait Hook: Fn() + Send + Sync {} +impl Hook for T {} + +/// Installs Prometheus as the metrics recorder. +pub(crate) fn install_recorder() -> anyhow::Result { + let recorder = PrometheusBuilder::new().build_recorder(); + let handle = recorder.handle(); + + // Build metrics stack + Stack::new(recorder) + .push(PrefixLayer::new("katana")) + .install() + .map_err(|e| anyhow::anyhow!("Couldn't set metrics recorder: {}", e))?; + + Ok(handle) +} + +/// Serves Prometheus metrics over HTTP with hooks. +/// +/// The hooks are called every time the metrics are requested at the given endpoint, and can be used +/// to record values for pull-style metrics, i.e. metrics that are not automatically updated. +pub(crate) async fn serve_with_hooks( + listen_addr: SocketAddr, + handle: PrometheusHandle, + hooks: impl IntoIterator, +) -> anyhow::Result<()> { + let hooks: Vec<_> = hooks.into_iter().collect(); + + // Start endpoint + start_endpoint(listen_addr, handle, Arc::new(move || hooks.iter().for_each(|hook| hook()))) + .await + .map_err(|e| anyhow::anyhow!("Could not start Prometheus endpoint: {}", e))?; + + Ok(()) +} + +/// Starts an endpoint at the given address to serve Prometheus metrics. +async fn start_endpoint( + listen_addr: SocketAddr, + handle: PrometheusHandle, + hook: Arc, +) -> anyhow::Result<()> { + let make_svc = make_service_fn(move |_| { + let handle = handle.clone(); + let hook = Arc::clone(&hook); + async move { + Ok::<_, Infallible>(service_fn(move |_: Request| { + (hook)(); + let metrics = handle.render(); + async move { Ok::<_, Infallible>(Response::new(Body::from(metrics))) } + })) + } + }); + let server = Server::try_bind(&listen_addr) + .map_err(|e| anyhow::anyhow!("Could not bind to address: {}", e))? + .serve(make_svc); + + tokio::spawn(async move { server.await.expect("Metrics endpoint crashed") }); + + Ok(()) +} + +/// Serves Prometheus metrics over HTTP with database and process metrics. +pub(crate) async fn serve( + listen_addr: SocketAddr, + handle: PrometheusHandle, + process: metrics_process::Collector, +) -> anyhow::Result<()> { + // Clone `process` to move it into the hook and use the original `process` for describe below. + let cloned_process = process.clone(); + let hooks: Vec>> = + vec![Box::new(move || cloned_process.collect()), Box::new(collect_memory_stats)]; + serve_with_hooks(listen_addr, handle, hooks).await?; + + process.describe(); + describe_memory_stats(); + + Ok(()) +} + +#[cfg(all(feature = "jemalloc", unix))] +fn collect_memory_stats() { + use jemalloc_ctl::{epoch, stats}; + + if epoch::advance() + .map_err(|error| tracing::error!(?error, "Failed to advance jemalloc epoch")) + .is_err() + { + return; + } + + if let Ok(value) = stats::active::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.active")) + { + gauge!("jemalloc.active", value as f64); + } + + if let Ok(value) = stats::allocated::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.allocated")) + { + gauge!("jemalloc.allocated", value as f64); + } + + if let Ok(value) = stats::mapped::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.mapped")) + { + gauge!("jemalloc.mapped", value as f64); + } + + if let Ok(value) = stats::metadata::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.metadata")) + { + gauge!("jemalloc.metadata", value as f64); + } + + if let Ok(value) = stats::resident::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.resident")) + { + gauge!("jemalloc.resident", value as f64); + } + + if let Ok(value) = stats::retained::read() + .map_err(|error| tracing::error!(?error, "Failed to read jemalloc.stats.retained")) + { + gauge!("jemalloc.retained", value as f64); + } +} + +#[cfg(all(feature = "jemalloc", unix))] +fn describe_memory_stats() { + describe_gauge!( + "jemalloc.active", + metrics::Unit::Bytes, + "Total number of bytes in active pages allocated by the application" + ); + describe_gauge!( + "jemalloc.allocated", + metrics::Unit::Bytes, + "Total number of bytes allocated by the application" + ); + describe_gauge!( + "jemalloc.mapped", + metrics::Unit::Bytes, + "Total number of bytes in active extents mapped by the allocator" + ); + describe_gauge!( + "jemalloc.metadata", + metrics::Unit::Bytes, + "Total number of bytes dedicated to jemalloc metadata" + ); + describe_gauge!( + "jemalloc.resident", + metrics::Unit::Bytes, + "Total number of bytes in physically resident data pages mapped by the allocator" + ); + describe_gauge!( + "jemalloc.retained", + metrics::Unit::Bytes, + "Total number of bytes in virtual memory mappings that were retained rather than being \ + returned to the operating system via e.g. munmap(2)" + ); +} + +#[cfg(not(all(feature = "jemalloc", unix)))] +fn collect_memory_stats() {} + +#[cfg(not(all(feature = "jemalloc", unix)))] +fn describe_memory_stats() {} diff --git a/crates/katana/src/utils.rs b/crates/katana/src/utils.rs new file mode 100644 index 0000000000..442979297a --- /dev/null +++ b/crates/katana/src/utils.rs @@ -0,0 +1,46 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}; + +/// Error thrown while parsing a socket address. +#[derive(thiserror::Error, Debug)] +pub enum SocketAddressParsingError { + /// Failed to convert the string into a socket addr + #[error("could not parse socket address: {0}")] + Io(#[from] std::io::Error), + /// Input must not be empty + #[error("cannot parse socket address from empty string")] + Empty, + /// Failed to parse the address + #[error("could not parse socket address from {0}")] + Parse(String), + /// Failed to parse port + #[error("could not parse port: {0}")] + Port(#[from] std::num::ParseIntError), +} + +/// Parse a [SocketAddr] from a `str`. +/// +/// The following formats are checked: +/// +/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the +/// hostname is set to `localhost`. +/// - If the value contains `:` it is assumed to be the format `:` +/// - Otherwise it is assumed to be a hostname +/// +/// An error is returned if the value is empty. +pub fn parse_socket_address(value: &str) -> anyhow::Result { + if value.is_empty() { + return Err(SocketAddressParsingError::Empty); + } + + if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) { + let port: u16 = port.parse()?; + return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)); + } + if let Ok(port) = value.parse::() { + return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)); + } + value + .to_socket_addrs()? + .next() + .ok_or_else(|| SocketAddressParsingError::Parse(value.to_string())) +} From c9671e62fd838649298d956ba7dde05b5eaf9685 Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 14 Dec 2023 15:50:27 -0800 Subject: [PATCH 132/192] Fix grpc entity id field name (#1282) --- crates/torii/grpc/src/types/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/torii/grpc/src/types/schema.rs b/crates/torii/grpc/src/types/schema.rs index 1931989a5c..63d6adce8d 100644 --- a/crates/torii/grpc/src/types/schema.rs +++ b/crates/torii/grpc/src/types/schema.rs @@ -8,7 +8,7 @@ use crate::proto::{self}; #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Entity { - pub key: FieldElement, + pub id: FieldElement, pub models: Vec, } @@ -22,7 +22,7 @@ impl TryFrom for Entity { type Error = ClientError; fn try_from(entity: proto::types::Entity) -> Result { Ok(Self { - key: FieldElement::from_byte_slice_be(&entity.id).map_err(ClientError::SliceError)?, + id: FieldElement::from_byte_slice_be(&entity.id).map_err(ClientError::SliceError)?, models: entity .models .into_iter() From ae66da078433dddb4cafb23628a7776345120992 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 14 Dec 2023 20:10:39 -0500 Subject: [PATCH 133/192] Add prometheus exporter to Torii (#1281) --- Cargo.lock | 38 ++++++++++------ Cargo.toml | 2 + crates/katana/Cargo.toml | 18 ++------ crates/katana/src/args.rs | 3 +- crates/katana/src/main.rs | 10 +---- crates/metrics/Cargo.toml | 28 ++++++++++++ crates/metrics/src/lib.rs | 10 +++++ .../src/prometheus_exporter.rs | 4 +- crates/{katana => metrics}/src/utils.rs | 0 crates/torii/server/Cargo.toml | 5 ++- crates/torii/server/src/cli.rs | 43 ++++++++++++++++++- 11 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 crates/metrics/Cargo.toml create mode 100644 crates/metrics/src/lib.rs rename crates/{katana => metrics}/src/prometheus_exporter.rs (98%) rename crates/{katana => metrics}/src/utils.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 8cbfbbd22d..fdabfa7136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5294,23 +5294,16 @@ dependencies = [ name = "katana" version = "0.4.1" dependencies = [ - "anyhow", "assert_matches", "clap", "clap_complete", "console", - "hyper", - "jemalloc-ctl", - "jemallocator", "katana-core", "katana-rpc", - "metrics", - "metrics-exporter-prometheus", + "metrics 0.1.0", "metrics-process", - "metrics-util", "serde_json", "starknet_api", - "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -5769,6 +5762,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.1.0" +dependencies = [ + "anyhow", + "hyper", + "jemalloc-ctl", + "jemallocator", + "metrics 0.21.1", + "metrics-exporter-prometheus", + "metrics-process", + "metrics-util", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "metrics" version = "0.21.1" @@ -5782,15 +5792,15 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" +checksum = "1d4fa7ce7c4862db464a37b0b31d89bca874562f034bd7993895572783d02950" dependencies = [ "base64 0.21.5", "hyper", "indexmap 1.9.3", "ipnet", - "metrics", + "metrics 0.21.1", "metrics-util", "quanta", "thiserror", @@ -5817,7 +5827,7 @@ checksum = "2674a02f6ad51326c2106d9aa5a07d1f759695b655c06df0bba5d5fb338ac0a4" dependencies = [ "libproc", "mach2", - "metrics", + "metrics 0.21.1", "once_cell", "procfs", "rlimit", @@ -5835,7 +5845,7 @@ dependencies = [ "crossbeam-utils", "hashbrown 0.13.1", "indexmap 1.9.3", - "metrics", + "metrics 0.21.1", "num_cpus", "ordered-float 3.9.2", "quanta", @@ -9726,6 +9736,8 @@ dependencies = [ "hyper-reverse-proxy", "indexmap 1.9.3", "lazy_static", + "metrics 0.1.0", + "metrics-process", "scarb", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f3050b7bb9..a6b3ecc46a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "crates/katana/runner", "crates/katana/storage/db", "crates/katana/storage/provider", + "crates/metrics", "crates/sozo", "crates/torii/client", "crates/torii/server", @@ -82,6 +83,7 @@ hex = "0.4.3" indoc = "1.0.7" itertools = "0.10.3" lazy_static = "1.4.0" +metrics-process = "1.0.9" num-bigint = "0.4" once_cell = "1.0" parking_lot = "0.12.1" diff --git a/crates/katana/Cargo.toml b/crates/katana/Cargo.toml index d4b252cb86..0e2b102a61 100644 --- a/crates/katana/Cargo.toml +++ b/crates/katana/Cargo.toml @@ -7,37 +7,25 @@ repository.workspace = true version.workspace = true [dependencies] -anyhow.workspace = true clap.workspace = true clap_complete.workspace = true console.workspace = true -hyper.workspace = true katana-core = { path = "core" } katana-rpc = { path = "rpc" } +metrics = { path = "../metrics" } +metrics-process.workspace = true serde_json.workspace = true starknet_api.workspace = true -thiserror.workspace = true tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true url.workspace = true -# Metrics -metrics = "0.21.1" -metrics-exporter-prometheus = "0.12.1" -metrics-process = "1.0.9" -metrics-util = "0.15.0" - -[target.'cfg(not(windows))'.dependencies] -jemallocator = { version = "0.5.0", optional = true } -jemalloc-ctl = { version = "0.5.0", optional = true } - [dev-dependencies] assert_matches = "1.5.0" [features] default = [ "jemalloc", "messaging" ] -jemalloc = ["dep:jemallocator", "dep:jemalloc-ctl"] -jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] +jemalloc = [ "metrics/jemalloc" ] messaging = [ "katana-core/messaging" ] starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index 0d8ea96a76..d4e155deab 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -22,12 +22,11 @@ use katana_core::constants::{ use katana_core::sequencer::SequencerConfig; use katana_rpc::api::ApiKind; use katana_rpc::config::ServerConfig; +use metrics::utils::parse_socket_address; use tracing::Subscriber; use tracing_subscriber::{fmt, EnvFilter}; use url::Url; -use crate::utils::parse_socket_address; - #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index 93d4d8f8ce..ea53341321 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -9,22 +9,14 @@ use katana_core::constants::{ }; use katana_core::sequencer::KatanaSequencer; use katana_rpc::{spawn, NodeHandle}; +use metrics::prometheus_exporter; use tokio::signal::ctrl_c; use tracing::info; mod args; -mod prometheus_exporter; -mod utils; use args::Commands::Completions; use args::KatanaArgs; -#[cfg(all(feature = "jemalloc", unix))] -use jemallocator as _; - -// We use jemalloc for performance reasons -#[cfg(all(feature = "jemalloc", unix))] -#[global_allocator] -static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml new file mode 100644 index 0000000000..4e740cd611 --- /dev/null +++ b/crates/metrics/Cargo.toml @@ -0,0 +1,28 @@ +[package] +edition = "2021" +name = "metrics" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +hyper.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true + +# Metrics +metrics = "0.21.1" +metrics-exporter-prometheus = "0.12.1" +metrics-process.workspace = true +metrics-util = "0.15.0" + +[target.'cfg(not(windows))'.dependencies] +jemalloc-ctl = { version = "0.5.0", optional = true } +jemallocator = { version = "0.5.0", optional = true } + +[features] +default = [ "jemalloc" ] +jemalloc = [ "dep:jemalloc-ctl", "dep:jemallocator" ] +jemalloc-prof = [ "jemalloc", "jemallocator?/profiling" ] diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs new file mode 100644 index 0000000000..bdd797fc3d --- /dev/null +++ b/crates/metrics/src/lib.rs @@ -0,0 +1,10 @@ +pub mod prometheus_exporter; +pub mod utils; + +#[cfg(all(feature = "jemalloc", unix))] +use jemallocator as _; + +// We use jemalloc for performance reasons +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; diff --git a/crates/katana/src/prometheus_exporter.rs b/crates/metrics/src/prometheus_exporter.rs similarity index 98% rename from crates/katana/src/prometheus_exporter.rs rename to crates/metrics/src/prometheus_exporter.rs index d79b5b5bc6..92ea2d23c7 100644 --- a/crates/katana/src/prometheus_exporter.rs +++ b/crates/metrics/src/prometheus_exporter.rs @@ -14,7 +14,7 @@ pub(crate) trait Hook: Fn() + Send + Sync {} impl Hook for T {} /// Installs Prometheus as the metrics recorder. -pub(crate) fn install_recorder() -> anyhow::Result { +pub fn install_recorder() -> anyhow::Result { let recorder = PrometheusBuilder::new().build_recorder(); let handle = recorder.handle(); @@ -73,7 +73,7 @@ async fn start_endpoint( } /// Serves Prometheus metrics over HTTP with database and process metrics. -pub(crate) async fn serve( +pub async fn serve( listen_addr: SocketAddr, handle: PrometheusHandle, process: metrics_process::Collector, diff --git a/crates/katana/src/utils.rs b/crates/metrics/src/utils.rs similarity index 100% rename from crates/katana/src/utils.rs rename to crates/metrics/src/utils.rs diff --git a/crates/torii/server/Cargo.toml b/crates/torii/server/Cargo.toml index 147d3d330f..2f88105b32 100644 --- a/crates/torii/server/Cargo.toml +++ b/crates/torii/server/Cargo.toml @@ -23,6 +23,8 @@ hyper-reverse-proxy = { git = "https://github.com/tarrencev/hyper-reverse-proxy" hyper.workspace = true indexmap = "1.9.3" lazy_static.workspace = true +metrics = { path = "../../metrics" } +metrics-process.workspace = true scarb.workspace = true serde.workspace = true serde_json.workspace = true @@ -45,7 +47,8 @@ url.workspace = true camino.workspace = true [features] -default = [ "sqlite" ] +default = [ "jemalloc", "sqlite" ] +jemalloc = [ "metrics/jemalloc" ] sqlite = [ "sqlx/sqlite" ] [[bin]] diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index 23eadaaad7..4858ad2265 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -1,3 +1,15 @@ +//! Torii binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. + mod proxy; use std::net::SocketAddr; @@ -6,6 +18,8 @@ use std::sync::Arc; use clap::Parser; use dojo_world::contracts::world::WorldContractReader; +use metrics::prometheus_exporter; +use metrics::utils::parse_socket_address; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::SqlitePool; use starknet::core::types::FieldElement; @@ -35,31 +49,44 @@ struct Args { /// The world to index #[arg(short, long = "world", env = "DOJO_WORLD_ADDRESS")] world_address: FieldElement, + /// The rpc endpoint to use #[arg(long, default_value = "http://localhost:5050")] rpc: String, + /// Database filepath (ex: indexer.db). If specified file doesn't exist, it will be /// created. Defaults to in-memory database #[arg(short, long, default_value = ":memory:")] database: String, + /// Specify a block to start indexing from, ignored if stored head exists #[arg(short, long, default_value = "0")] start_block: u64, + /// Host address for api endpoints #[arg(long, default_value = "0.0.0.0")] host: String, + /// Port number for api endpoints #[arg(long, default_value = "8080")] port: u16, + /// Specify allowed origins for api endpoints (comma-separated list of allowed origins, or "*" /// for all) #[arg(long, default_value = "*")] #[arg(value_delimiter = ',')] allowed_origins: Vec, + /// The external url of the server, used for configuring the GraphQL Playground in a hosted /// environment #[arg(long)] external_url: Option, + + /// Enable Prometheus metrics. + /// + /// The metrics will be served at the given interface and port. + #[arg(long, value_name = "SOCKET", value_parser = parse_socket_address, help_heading = "Metrics")] + pub metrics: Option, } #[tokio::main] @@ -143,8 +170,20 @@ async fn main() -> anyhow::Result<()> { proxy_server.clone(), ); - info!("🚀 Torii listening at {}", format!("http://{}", addr)); - info!("Graphql playground: {}\n", format!("http://{}/graphql", addr)); + info!(target: "torii::cli", "Starting torii endpoint: {}", format!("http://{}", addr)); + info!(target: "torii::cli", "Serving Graphql playground: {}\n", format!("http://{}/graphql", addr)); + + if let Some(listen_addr) = args.metrics { + let prometheus_handle = prometheus_exporter::install_recorder()?; + + info!(target: "torii::cli", addr = %listen_addr, "Starting metrics endpoint"); + prometheus_exporter::serve( + listen_addr, + prometheus_handle, + metrics_process::Collector::default(), + ) + .await?; + } tokio::select! { _ = engine.start() => {}, From 5ac11802f56e7221da72fe5e50f095ded83df503 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 14 Dec 2023 21:13:57 -0500 Subject: [PATCH 134/192] Add cargo-llvm-cov to dev container --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 34193ffd83..12151326ed 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -20,7 +20,7 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" rustup component add clippy && \ rustup component add rustfmt -RUN cargo binstall cargo-nextest --secure -y +RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y # Install dojoup and scarb for vscode user USER vscode From 7d49bcda86c773437b029a002fb7c8af2ed89c8e Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Thu, 14 Dec 2023 21:31:21 -0500 Subject: [PATCH 135/192] Fix devcontainer update pr creation --- .github/workflows/devcontainer.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 8e68287856..52ad04c0f2 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -76,13 +76,6 @@ jobs: git config user.name "GitHub Action" git config user.email "action@github.com" - - name: Commit and push changes - run: | - git add .devcontainer/devcontainer.json - git commit -m "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" - git checkout -b devcontainer-${{ needs.build-and-push.outputs.tag_name }} - git push --set-upstream origin devcontainer-${{ needs.build-and-push.outputs.tag_name }} - - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: From 4c4f7c1053afd45cf85dddb7f6ee79faf8c26978 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Fri, 15 Dec 2023 22:04:13 +0800 Subject: [PATCH 136/192] refactor(katana): allow types to be deserializable from non self-describing formats (#1277) * bump `starknet-ff` to `0.3.6` --- Cargo.lock | 4 +- crates/katana/core/src/sequencer.rs | 7 +- .../core/src/service/messaging/ethereum.rs | 15 +- .../katana/core/src/service/messaging/mod.rs | 8 +- .../core/src/service/messaging/service.rs | 15 +- .../core/src/service/messaging/starknet.rs | 34 ++- .../katana/executor/src/blockifier/outcome.rs | 35 +-- .../katana/executor/src/blockifier/utils.rs | 13 +- crates/katana/primitives/src/contract.rs | 5 +- crates/katana/primitives/src/receipt.rs | 72 +++++- crates/katana/primitives/src/transaction.rs | 4 +- .../primitives/src/utils/transaction.rs | 9 +- crates/katana/rpc/rpc-types/src/receipt.rs | 212 ++++++++++++------ 13 files changed, 292 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fdabfa7136..e3bfad15b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8827,9 +8827,9 @@ dependencies = [ [[package]] name = "starknet-ff" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7584bc732e4d2a8ccebdd1dda8236f7940a79a339e30ebf338d45c329659e36c" +checksum = "067419451efdea1ee968df8438369960c167e0e905c05b84afd074f50e1d6f3d" dependencies = [ "ark-ff", "bigdecimal", diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 8655dee3cd..7740456bf7 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -13,6 +13,7 @@ use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockIdOrTag, Block use katana_primitives::contract::{ ClassHash, CompiledContractClass, ContractAddress, Nonce, StorageKey, StorageValue, }; +use katana_primitives::receipt::Event; use katana_primitives::transaction::{ExecutableTxWithHash, TxHash, TxWithHash}; use katana_primitives::FieldElement; use katana_provider::traits::block::{ @@ -23,7 +24,7 @@ use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; use katana_provider::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionsProviderExt, }; -use starknet::core::types::{BlockTag, EmittedEvent, Event, EventsPage, FeeEstimate}; +use starknet::core::types::{BlockTag, EmittedEvent, EventsPage, FeeEstimate}; use starknet_api::core::ChainId; use crate::backend::config::StarknetConfig; @@ -380,7 +381,7 @@ impl KatanaSequencer { ); filtered_events.extend(new_filtered_events.iter().map(|e| EmittedEvent { - from_address: e.from_address, + from_address: e.from_address.into(), keys: e.keys.clone(), data: e.data.clone(), block_hash, @@ -459,7 +460,7 @@ fn filter_events_by_params( // Iterate on block events. for event in events { index += 1; - if !address.map_or(true, |addr| addr == event.from_address.into()) { + if !address.map_or(true, |addr| addr == event.from_address) { continue; } diff --git a/crates/katana/core/src/service/messaging/ethereum.rs b/crates/katana/core/src/service/messaging/ethereum.rs index e282a41464..ec4e0aa140 100644 --- a/crates/katana/core/src/service/messaging/ethereum.rs +++ b/crates/katana/core/src/service/messaging/ethereum.rs @@ -8,10 +8,10 @@ use ethers::prelude::*; use ethers::providers::{Http, Provider}; use ethers::types::{Address, BlockNumber, Log}; use k256::ecdsa::SigningKey; +use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::L1HandlerTx; use katana_primitives::utils::transaction::compute_l1_message_hash; use katana_primitives::FieldElement; -use starknet::core::types::MsgToL1; use tracing::{debug, error, trace, warn}; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; @@ -164,7 +164,10 @@ impl Messenger for EthereumMessaging { Ok((to_block, l1_handler_txs)) } - async fn send_messages(&self, messages: &[MsgToL1]) -> MessengerResult> { + async fn send_messages( + &self, + messages: &[MessageToL1], + ) -> MessengerResult> { if messages.is_empty() { return Ok(vec![]); } @@ -233,11 +236,13 @@ fn l1_handler_tx_from_log(log: Log, chain_id: FieldElement) -> MessengerResult Vec { +fn parse_messages(messages: &[MessageToL1]) -> Vec { messages .iter() .map(|msg| { - let hash = compute_l1_message_hash(msg.from_address, msg.to_address, &msg.payload); + let hash = + compute_l1_message_hash(msg.from_address.into(), msg.to_address, &msg.payload); + U256::from_big_endian(hash.as_bytes()) }) .collect() @@ -324,7 +329,7 @@ mod tests { let to_address = selector!("to_address"); let payload = vec![FieldElement::ONE, FieldElement::TWO]; - let messages = vec![MsgToL1 { from_address, to_address, payload }]; + let messages = vec![MessageToL1 { from_address: from_address.into(), to_address, payload }]; let hashes = parse_messages(&messages); assert_eq!(hashes.len(), 1); diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index 86745986aa..79d8c6364d 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -39,12 +39,13 @@ mod starknet; use std::path::Path; -use ::starknet::core::types::{FieldElement, MsgToL1}; +use ::starknet::core::types::FieldElement; use ::starknet::providers::ProviderError as StarknetProviderError; use anyhow::Result; use async_trait::async_trait; use ethereum::EthereumMessaging; use ethers::providers::ProviderError as EthereumProviderError; +use katana_primitives::receipt::MessageToL1; use serde::Deserialize; use tracing::{error, info}; @@ -155,7 +156,10 @@ pub trait Messenger { /// # Arguments /// /// * `messages` - Messages to settle. - async fn send_messages(&self, messages: &[MsgToL1]) -> MessengerResult>; + async fn send_messages( + &self, + messages: &[MessageToL1], + ) -> MessengerResult>; } pub enum MessengerMode { diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 76d6b48f52..ec99ceb777 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -3,9 +3,10 @@ use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use ::starknet::core::types::{FieldElement, MsgToL1}; +use ::starknet::core::types::FieldElement; use futures::{Future, FutureExt, Stream}; use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx, TxHash}; use katana_provider::traits::block::BlockNumberProvider; use katana_provider::traits::transaction::ReceiptProvider; @@ -124,7 +125,7 @@ impl MessagingService { BlockHashOrNumber::Num(block_num), ) .unwrap() - .map(|r| r.iter().flat_map(|r| r.messages_sent().to_vec()).collect::>()) else { + .map(|r| r.iter().flat_map(|r| r.messages_sent().to_vec()).collect::>()) else { return Ok(None); }; @@ -253,7 +254,7 @@ fn interval_from_seconds(secs: u64) -> Interval { interval } -fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { +fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { assert_eq!(messages.len(), hashes.len()); #[cfg(feature = "starknet-messaging")] @@ -274,7 +275,7 @@ fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { info!( target: LOG_TARGET, r"Message executed on settlement layer: -| from_address | {:#x} +| from_address | {} | to_address | {} | selector | {} | payload | [{}] @@ -290,10 +291,10 @@ fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { } // We check for magic value 'MSG' used only when we are doing L3-L2 messaging. - let (to_address, payload_str) = if format!("{:#x}", m.to_address) == "0x4d5347" { + let (to_address, payload_str) = if format!("{}", m.to_address) == "0x4d5347" { (payload_str[0].clone(), &payload_str[1..]) } else { - (format!("{:#x}", m.to_address), &payload_str[..]) + (format!("{}", m.to_address), &payload_str[..]) }; #[rustfmt::skip] @@ -301,7 +302,7 @@ fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { target: LOG_TARGET, r#"Message sent to settlement layer: | hash | {} -| from_address | {:#x} +| from_address | {} | to_address | {} | payload | [{}] diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 3fcdc70283..41b6b08313 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; +use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::L1HandlerTx; use katana_primitives::utils::transaction::compute_l1_message_hash; use starknet::accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}; -use starknet::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter, FieldElement, MsgToL1}; +use starknet::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter, FieldElement}; use starknet::core::utils::starknet_keccak; use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; @@ -212,7 +213,10 @@ impl Messenger for StarknetMessaging { Ok((to_block, l1_handler_txs)) } - async fn send_messages(&self, messages: &[MsgToL1]) -> MessengerResult> { + async fn send_messages( + &self, + messages: &[MessageToL1], + ) -> MessengerResult> { if messages.is_empty() { return Ok(vec![]); } @@ -241,7 +245,7 @@ impl Messenger for StarknetMessaging { /// /// Messages can also be labelled as EXE, which in this case generate a `Call` /// additionally to the hash. -fn parse_messages(messages: &[MsgToL1]) -> MessengerResult<(Vec, Vec)> { +fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec, Vec)> { let mut hashes: Vec = vec![]; let mut calls: Vec = vec![]; @@ -358,8 +362,16 @@ mod tests { let payload_exe = vec![to_address, selector, FieldElement::ONE, FieldElement::TWO]; let messages = vec![ - MsgToL1 { from_address, to_address: MSG_MAGIC, payload: payload_msg }, - MsgToL1 { from_address, to_address: EXE_MAGIC, payload: payload_exe.clone() }, + MessageToL1 { + from_address: from_address.into(), + to_address: MSG_MAGIC, + payload: payload_msg, + }, + MessageToL1 { + from_address: from_address.into(), + to_address: EXE_MAGIC, + payload: payload_exe.clone(), + }, ]; let (hashes, calls) = parse_messages(&messages).unwrap(); @@ -388,7 +400,11 @@ mod tests { let from_address = selector!("from_address"); let payload_msg = vec![]; - let messages = vec![MsgToL1 { from_address, to_address: MSG_MAGIC, payload: payload_msg }]; + let messages = vec![MessageToL1 { + from_address: from_address.into(), + to_address: MSG_MAGIC, + payload: payload_msg, + }]; parse_messages(&messages).unwrap(); } @@ -399,7 +415,11 @@ mod tests { let from_address = selector!("from_address"); let payload_exe = vec![FieldElement::ONE]; - let messages = vec![MsgToL1 { from_address, to_address: EXE_MAGIC, payload: payload_exe }]; + let messages = vec![MessageToL1 { + from_address: from_address.into(), + to_address: EXE_MAGIC, + payload: payload_exe, + }]; parse_messages(&messages).unwrap(); } diff --git a/crates/katana/executor/src/blockifier/outcome.rs b/crates/katana/executor/src/blockifier/outcome.rs index 92a0aa1f59..d3cfe82581 100644 --- a/crates/katana/executor/src/blockifier/outcome.rs +++ b/crates/katana/executor/src/blockifier/outcome.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use blockifier::transaction::objects::TransactionExecutionInfo; use katana_primitives::receipt::{ DeclareTxReceipt, DeployAccountTxReceipt, InvokeTxReceipt, L1HandlerTxReceipt, Receipt, + TxExecutionResources, }; use katana_primitives::transaction::Tx; -use starknet::core::types::ExecutionResources; use super::utils::{events_from_exec_info, l2_to_l1_messages_from_exec_info}; @@ -63,30 +63,17 @@ impl TxReceiptWithExecInfo { } /// Parse the `actual resources` field from the execution info into a more structured type, -/// [`ExecutionResources`]. -fn parse_actual_resources(resources: &HashMap) -> ExecutionResources { - ExecutionResources { +/// [`TxExecutionResources`]. +fn parse_actual_resources(resources: &HashMap) -> TxExecutionResources { + TxExecutionResources { steps: resources.get("n_steps").copied().unwrap_or_default() as u64, memory_holes: resources.get("memory_holes").map(|x| *x as u64), - ec_op_builtin_applications: resources.get("ec_op_builtin").copied().unwrap_or_default() - as u64, - ecdsa_builtin_applications: resources.get("ecdsa_builtin").copied().unwrap_or_default() - as u64, - keccak_builtin_applications: resources.get("keccak_builtin").copied().unwrap_or_default() - as u64, - bitwise_builtin_applications: resources.get("bitwise_builtin").copied().unwrap_or_default() - as u64, - pedersen_builtin_applications: resources - .get("pedersen_builtin") - .copied() - .unwrap_or_default() as u64, - poseidon_builtin_applications: resources - .get("poseidon_builtin") - .copied() - .unwrap_or_default() as u64, - range_check_builtin_applications: resources - .get("range_check_builtin") - .copied() - .unwrap_or_default() as u64, + ec_op_builtin: resources.get("ec_op_builtin").map(|x| *x as u64), + ecdsa_builtin: resources.get("ecdsa_builtin").map(|x| *x as u64), + keccak_builtin: resources.get("keccak_builtin").map(|x| *x as u64), + bitwise_builtin: resources.get("bitwise_builtin").map(|x| *x as u64), + pedersen_builtin: resources.get("pedersen_builtin").map(|x| *x as u64), + poseidon_builtin: resources.get("poseidon_builtin").map(|x| *x as u64), + range_check_builtin: resources.get("range_check_builtin").map(|x| *x as u64), } } diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs index f0607992c9..8f3e4450fe 100644 --- a/crates/katana/executor/src/blockifier/utils.rs +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -18,12 +18,13 @@ use blockifier::transaction::objects::{ }; use convert_case::{Case, Casing}; use katana_primitives::contract::ContractAddress; +use katana_primitives::receipt::{Event, MessageToL1}; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use katana_primitives::transaction::ExecutableTxWithHash; use katana_primitives::FieldElement; use katana_provider::traits::contract::ContractClassProvider; use katana_provider::traits::state::StateProvider; -use starknet::core::types::{Event, FeeEstimate, MsgToL1}; +use starknet::core::types::FeeEstimate; use starknet::core::utils::parse_cairo_short_string; use starknet_api::core::EntryPointSelector; use starknet_api::transaction::Calldata; @@ -284,7 +285,7 @@ pub(super) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) - let mut events: Vec = vec![]; events.extend(call_info.execution.events.iter().map(|e| Event { - from_address: (*call_info.call.storage_address.0.key()).into(), + from_address: call_info.call.storage_address.into(), data: e.event.data.0.iter().map(|d| (*d).into()).collect(), keys: e.event.keys.iter().map(|k| k.0.into()).collect(), })); @@ -313,16 +314,16 @@ pub(super) fn events_from_exec_info(execution_info: &TransactionExecutionInfo) - pub(super) fn l2_to_l1_messages_from_exec_info( execution_info: &TransactionExecutionInfo, -) -> Vec { +) -> Vec { let mut messages = vec![]; - fn get_messages_recursively(info: &CallInfo) -> Vec { + fn get_messages_recursively(info: &CallInfo) -> Vec { let mut messages = vec![]; - messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MsgToL1 { + messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MessageToL1 { to_address: FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), - from_address: (*info.call.caller_address.0.key()).into(), + from_address: info.call.caller_address.into(), payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), })); diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 7531edb89e..64596156af 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -1,5 +1,6 @@ use std::fmt; +use derive_more::Deref; use starknet::core::utils::normalize_address; use crate::FieldElement; @@ -20,9 +21,9 @@ pub type Nonce = FieldElement; pub type SierraClass = starknet::core::types::FlattenedSierraClass; /// Represents a contract address. -#[derive(Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +#[derive(Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Deref)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ContractAddress(pub FieldElement); +pub struct ContractAddress(#[deref] pub FieldElement); impl ContractAddress { pub fn new(address: FieldElement) -> Self { diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index 5d3471789a..a31d255c34 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -1,6 +1,30 @@ -use starknet::core::types::{Event, ExecutionResources, Hash256, MsgToL1}; +use ethers::types::H256; use crate::contract::ContractAddress; +use crate::FieldElement; + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Event { + /// The contract address that emitted the event. + pub from_address: ContractAddress, + /// The event keys. + pub keys: Vec, + /// The event data. + pub data: Vec, +} + +/// Represents a message sent to L1. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MessageToL1 { + /// The L2 contract address that sent the message. + pub from_address: ContractAddress, + /// The L1 contract address that the message is sent to. + pub to_address: FieldElement, + /// The payload of the message. + pub payload: Vec, +} /// Receipt for a `Invoke` transaction. #[derive(Debug, Clone)] @@ -11,11 +35,11 @@ pub struct InvokeTxReceipt { /// Events emitted by contracts. pub events: Vec, /// Messages sent to L1. - pub messages_sent: Vec, + pub messages_sent: Vec, /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub execution_resources: ExecutionResources, + pub execution_resources: TxExecutionResources, } /// Receipt for a `Declare` transaction. @@ -27,11 +51,11 @@ pub struct DeclareTxReceipt { /// Events emitted by contracts. pub events: Vec, /// Messages sent to L1. - pub messages_sent: Vec, + pub messages_sent: Vec, /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub execution_resources: ExecutionResources, + pub execution_resources: TxExecutionResources, } /// Receipt for a `L1Handler` transaction. @@ -43,13 +67,13 @@ pub struct L1HandlerTxReceipt { /// Events emitted by contracts. pub events: Vec, /// The hash of the L1 message - pub message_hash: Hash256, + pub message_hash: H256, /// Messages sent to L1. - pub messages_sent: Vec, + pub messages_sent: Vec, /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub execution_resources: ExecutionResources, + pub execution_resources: TxExecutionResources, } /// Receipt for a `DeployAccount` transaction. @@ -61,11 +85,11 @@ pub struct DeployAccountTxReceipt { /// Events emitted by contracts. pub events: Vec, /// Messages sent to L1. - pub messages_sent: Vec, + pub messages_sent: Vec, /// Revert error message if the transaction execution failed. pub revert_error: Option, /// The execution resources used by the transaction. - pub execution_resources: ExecutionResources, + pub execution_resources: TxExecutionResources, /// Contract address of the deployed account contract. pub contract_address: ContractAddress, } @@ -93,7 +117,7 @@ impl Receipt { } } - pub fn messages_sent(&self) -> &[MsgToL1] { + pub fn messages_sent(&self) -> &[MessageToL1] { match self { Receipt::Invoke(rct) => &rct.messages_sent, Receipt::Declare(rct) => &rct.messages_sent, @@ -111,3 +135,29 @@ impl Receipt { } } } + +/// Transaction execution resources. +/// +/// The resources consumed by a transaction during its execution. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TxExecutionResources { + /// The number of cairo steps used + pub steps: u64, + /// The number of unused memory cells (each cell is roughly equivalent to a step) + pub memory_holes: Option, + /// The number of range_check builtin instances + pub range_check_builtin: Option, + /// The number of pedersen builtin instances + pub pedersen_builtin: Option, + /// The number of poseidon builtin instances + pub poseidon_builtin: Option, + /// The number of ec_op builtin instances + pub ec_op_builtin: Option, + /// The number of ecdsa builtin instances + pub ecdsa_builtin: Option, + /// The number of bitwise builtin instances + pub bitwise_builtin: Option, + /// The number of keccak builtin instances + pub keccak_builtin: Option, +} diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 7bd98eac65..9664105776 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -1,5 +1,5 @@ use derive_more::{AsRef, Deref}; -use starknet::core::types::Hash256; +use ethers::types::H256; use crate::contract::{ ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, Nonce, SierraClass, @@ -219,7 +219,7 @@ pub struct L1HandlerTx { pub chain_id: ChainId, pub paid_fee_on_l1: u128, pub version: FieldElement, - pub message_hash: Hash256, + pub message_hash: H256, pub calldata: Vec, pub contract_address: ContractAddress, pub entry_point_selector: FieldElement, diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs index c04760e453..2c9cf6a0b4 100644 --- a/crates/katana/primitives/src/utils/transaction.rs +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -1,6 +1,6 @@ +use ethers::types::H256; use sha3::{Digest, Keccak256}; use starknet::core::crypto::compute_hash_on_elements; -use starknet::core::types::Hash256; use crate::FieldElement; @@ -159,11 +159,13 @@ pub fn compute_l1_handler_tx_hash( } /// Computes the hash of a L1 message. +/// +/// The hash that is used to consume the message in L1. pub fn compute_l1_message_hash( from_address: FieldElement, to_address: FieldElement, payload: &[FieldElement], -) -> Hash256 { +) -> H256 { let mut buf: Vec = vec![]; buf.extend(from_address.to_bytes_be()); buf.extend(to_address.to_bytes_be()); @@ -172,9 +174,8 @@ pub fn compute_l1_message_hash( let mut hasher = Keccak256::new(); hasher.update(buf); - let hash = hasher.finalize(); - Hash256::from_bytes(*AsRef::<[u8; 32]>::as_ref(&hash)) + H256::from_slice(&hasher.finalize()) } #[cfg(test)] diff --git a/crates/katana/rpc/rpc-types/src/receipt.rs b/crates/katana/rpc/rpc-types/src/receipt.rs index 490c68d3e8..0d7ac49838 100644 --- a/crates/katana/rpc/rpc-types/src/receipt.rs +++ b/crates/katana/rpc/rpc-types/src/receipt.rs @@ -1,9 +1,9 @@ use katana_primitives::block::{BlockHash, BlockNumber, FinalityStatus}; -use katana_primitives::receipt::Receipt; +use katana_primitives::receipt::{MessageToL1, Receipt, TxExecutionResources}; use katana_primitives::transaction::TxHash; use serde::Serialize; use starknet::core::types::{ - DeclareTransactionReceipt, DeployAccountTransactionReceipt, ExecutionResult, + DeclareTransactionReceipt, DeployAccountTransactionReceipt, ExecutionResult, Hash256, InvokeTransactionReceipt, L1HandlerTransactionReceipt, PendingDeclareTransactionReceipt, PendingDeployAccountTransactionReceipt, PendingInvokeTransactionReceipt, PendingL1HandlerTransactionReceipt, PendingTransactionReceipt, TransactionFinalityStatus, @@ -28,66 +28,88 @@ impl TxReceipt { }; let receipt = match receipt { - Receipt::Invoke(rct) => TransactionReceipt::Invoke(InvokeTransactionReceipt { - block_hash, - block_number, - finality_status, - transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, - actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, - execution_result: if let Some(reason) = rct.revert_error { - ExecutionResult::Reverted { reason } - } else { - ExecutionResult::Succeeded - }, - }), - - Receipt::Declare(rct) => TransactionReceipt::Declare(DeclareTransactionReceipt { - block_hash, - block_number, - finality_status, - transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, - actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, - execution_result: if let Some(reason) = rct.revert_error { - ExecutionResult::Reverted { reason } - } else { - ExecutionResult::Succeeded - }, - }), - - Receipt::L1Handler(rct) => TransactionReceipt::L1Handler(L1HandlerTransactionReceipt { - block_hash, - block_number, - finality_status, - transaction_hash, - events: rct.events, - message_hash: rct.message_hash, - messages_sent: rct.messages_sent, - actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, - execution_result: if let Some(reason) = rct.revert_error { - ExecutionResult::Reverted { reason } - } else { - ExecutionResult::Succeeded - }, - }), + Receipt::Invoke(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + + TransactionReceipt::Invoke(InvokeTransactionReceipt { + events, + block_hash, + block_number, + messages_sent, + finality_status, + transaction_hash, + actual_fee: rct.actual_fee.into(), + execution_resources: ExecutionResources::from(rct.execution_resources).0, + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + + Receipt::Declare(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + + TransactionReceipt::Declare(DeclareTransactionReceipt { + events, + block_hash, + block_number, + messages_sent, + finality_status, + transaction_hash, + actual_fee: rct.actual_fee.into(), + execution_resources: ExecutionResources::from(rct.execution_resources).0, + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } + + Receipt::L1Handler(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + + TransactionReceipt::L1Handler(L1HandlerTransactionReceipt { + events, + block_hash, + block_number, + messages_sent, + finality_status, + transaction_hash, + actual_fee: rct.actual_fee.into(), + execution_resources: ExecutionResources::from(rct.execution_resources).0, + message_hash: Hash256::from_bytes(rct.message_hash.to_fixed_bytes()), + execution_result: if let Some(reason) = rct.revert_error { + ExecutionResult::Reverted { reason } + } else { + ExecutionResult::Succeeded + }, + }) + } Receipt::DeployAccount(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + TransactionReceipt::DeployAccount(DeployAccountTransactionReceipt { + events, block_hash, block_number, + messages_sent, finality_status, transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, contract_address: rct.contract_address.into(), + execution_resources: ExecutionResources::from(rct.execution_resources).0, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -109,12 +131,16 @@ impl PendingTxReceipt { pub fn new(transaction_hash: TxHash, receipt: Receipt) -> Self { let receipt = match receipt { Receipt::Invoke(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + PendingTransactionReceipt::Invoke(PendingInvokeTransactionReceipt { transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, + events, + messages_sent, actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, + execution_resources: ExecutionResources::from(rct.execution_resources).0, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -124,12 +150,16 @@ impl PendingTxReceipt { } Receipt::Declare(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + PendingTransactionReceipt::Declare(PendingDeclareTransactionReceipt { + events, transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, + messages_sent, actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, + execution_resources: ExecutionResources::from(rct.execution_resources).0, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -139,13 +169,17 @@ impl PendingTxReceipt { } Receipt::L1Handler(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + PendingTransactionReceipt::L1Handler(PendingL1HandlerTransactionReceipt { transaction_hash, - events: rct.events, - message_hash: rct.message_hash, - messages_sent: rct.messages_sent, + events, + messages_sent, actual_fee: rct.actual_fee.into(), - execution_resources: rct.execution_resources, + execution_resources: ExecutionResources::from(rct.execution_resources).0, + message_hash: Hash256::from_bytes(rct.message_hash.0), execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -155,13 +189,17 @@ impl PendingTxReceipt { } Receipt::DeployAccount(rct) => { + let messages_sent = + rct.messages_sent.into_iter().map(|e| MsgToL1::from(e).0).collect(); + let events = rct.events.into_iter().map(|e| Event::from(e).0).collect(); + PendingTransactionReceipt::DeployAccount(PendingDeployAccountTransactionReceipt { transaction_hash, - events: rct.events, - messages_sent: rct.messages_sent, + events, + messages_sent, actual_fee: rct.actual_fee.into(), contract_address: rct.contract_address.into(), - execution_resources: rct.execution_resources, + execution_resources: ExecutionResources::from(rct.execution_resources).0, execution_result: if let Some(reason) = rct.revert_error { ExecutionResult::Reverted { reason } } else { @@ -187,3 +225,45 @@ impl From for TxReceipt { Self(receipt) } } + +struct MsgToL1(starknet::core::types::MsgToL1); + +impl From for MsgToL1 { + fn from(value: MessageToL1) -> Self { + MsgToL1(starknet::core::types::MsgToL1 { + from_address: value.from_address.into(), + to_address: value.to_address, + payload: value.payload, + }) + } +} + +struct Event(starknet::core::types::Event); + +impl From for Event { + fn from(value: katana_primitives::receipt::Event) -> Self { + Event(starknet::core::types::Event { + from_address: value.from_address.into(), + keys: value.keys, + data: value.data, + }) + } +} + +struct ExecutionResources(starknet::core::types::ExecutionResources); + +impl From for ExecutionResources { + fn from(value: TxExecutionResources) -> Self { + ExecutionResources(starknet::core::types::ExecutionResources { + steps: value.steps, + memory_holes: value.memory_holes, + ec_op_builtin_applications: value.ec_op_builtin.unwrap_or_default(), + ecdsa_builtin_applications: value.ecdsa_builtin.unwrap_or_default(), + keccak_builtin_applications: value.keccak_builtin.unwrap_or_default(), + bitwise_builtin_applications: value.bitwise_builtin.unwrap_or_default(), + pedersen_builtin_applications: value.pedersen_builtin.unwrap_or_default(), + poseidon_builtin_applications: value.poseidon_builtin.unwrap_or_default(), + range_check_builtin_applications: value.range_check_builtin.unwrap_or_default(), + }) + } +} From 88719bb4cba1a480f0153969e855c9524e2597dd Mon Sep 17 00:00:00 2001 From: glihm Date: Fri, 15 Dec 2023 10:35:27 -0600 Subject: [PATCH 137/192] fix(katana): adjust the logic to select the from_address from blockifier (#1279) * fix: adjust the logic to select the from_address from blockifier execution * fix: rely on Hash256 from starknet-rs instead of H256 from ethers This commit also bumps ethers to use latest version. * fix: adjust contract and use new foundry version * fix: fmt/clippy * fix: restore previous README for solidity * fix: restore H256 instead of Hash256 due to serialization issues --- Cargo.lock | 2 - crates/katana/core/Cargo.toml | 5 +-- .../katana/core/contracts/messaging/README.md | 21 +++++++++-- .../contracts/messaging/cairo/.tool-versions | 1 + .../core/contracts/messaging/cairo/Makefile | 37 +++++++++++-------- .../core/contracts/messaging/cairo/Scarb.lock | 6 +++ .../core/contracts/messaging/cairo/Scarb.toml | 2 +- .../core/contracts/messaging/run_e2e.sh | 22 +++++++++++ .../contracts/messaging/solidity/.gitignore | 3 +- .../contracts/messaging/solidity/Makefile | 4 +- .../contracts/messaging/solidity/README.md | 1 + .../contracts/messaging/solidity/foundry.toml | 2 +- .../core/src/service/messaging/service.rs | 2 +- .../katana/executor/src/blockifier/utils.rs | 11 +++++- crates/katana/primitives/Cargo.toml | 3 +- .../primitives/src/utils/transaction.rs | 13 ++----- 16 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 crates/katana/core/contracts/messaging/cairo/.tool-versions create mode 100644 crates/katana/core/contracts/messaging/cairo/Scarb.lock create mode 100644 crates/katana/core/contracts/messaging/run_e2e.sh diff --git a/Cargo.lock b/Cargo.lock index e3bfad15b8..6da3ab8b9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5335,7 +5335,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "sha3", "starknet", "starknet_api", "thiserror", @@ -5388,7 +5387,6 @@ dependencies = [ "flate2", "serde", "serde_json", - "sha3", "starknet", "starknet_api", "thiserror", diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 25f13ca1ff..96f81e3eb0 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -18,7 +18,7 @@ cairo-lang-casm = "2.3.1" cairo-lang-starknet = "2.3.1" cairo-vm.workspace = true convert_case.workspace = true -ethers = { version = "2.0.8", optional = true } +ethers = { version = "2.0.11", optional = true } flate2.workspace = true futures.workspace = true lazy_static = "1.4.0" @@ -27,7 +27,6 @@ rand = { version = "0.8.5", features = [ "small_rng" ] } serde.workspace = true serde_json.workspace = true serde_with.workspace = true -sha3 = { version = "0.10.7", default-features = false, optional = true } starknet.workspace = true starknet_api.workspace = true thiserror.workspace = true @@ -40,5 +39,5 @@ assert_matches.workspace = true hex = "0.4.3" [features] -messaging = [ "ethers", "sha3" ] +messaging = [ "ethers" ] starknet-messaging = [ ] diff --git a/crates/katana/core/contracts/messaging/README.md b/crates/katana/core/contracts/messaging/README.md index 0ec0c0529c..7ae51a1390 100644 --- a/crates/katana/core/contracts/messaging/README.md +++ b/crates/katana/core/contracts/messaging/README.md @@ -8,6 +8,12 @@ Please before starting, install: - [starkli](https://github.com/xJonathanLEI/starkli) to interact with Katana. - [foundry](https://book.getfoundry.sh/getting-started/installation) to interact with Anvil. +If it's the first time you run the example file, please install forge dependencies: +```bash +cd ~/dojo/crates/katana/core/contracts/messaging/solidity +forge install +``` + ## Contracts In this folder you will find smart contracts ready to be declared / deployed @@ -32,8 +38,8 @@ To test this scenario, you can use the associated Makefiles. But the flow is the How to run the scripts: -- Starts Anvil in a terminal. -- Starts Katana in an other terminal on default port 5050 with the messaging configuration that is inside the: +- Start Anvil in a terminal. +- Start Katana in an other terminal on default port 5050 with the messaging configuration that is inside the: `katana --messaging ~/dojo/crates/katana/core/contracts/messaging/anvil.messaging.json` - Open an other terminal and `cd ~/dojo/crates/katana/core/contracts/messaging`. @@ -57,7 +63,16 @@ make -sC solidity/ send_msg selector_str=msg_handler_struct payload="[1,2]" # Send message L2 -> L1 to be manually consumed. make -sC cairo/ send_msg_value_l1 value=2 - +``` +Then you've to wait the message to be sent to L1, Katana will display it: +``` +2023-12-15T15:16:18.435370Z INFO messaging: Message sent to settlement layer: +| hash | 0x62c7475daef517f6858a6f539bb4d2aa7eb1e23a7e8b1bc6a0834256d995e49d +| from_address | 0x4231f608ea4a233136f6cdfcd10eaad2e46362bbc4e5d5aa88d0d574ea120d8 +| to_address | 0xe7f1725e7734ce288f8367e1bb143e90bb3f0512 +| payload | [0x2] +``` +``` # Consume the messag previously sent. You can try to call it once and see the second one reverting. make -sC solidity/ consume_msg payload="[2]" ``` diff --git a/crates/katana/core/contracts/messaging/cairo/.tool-versions b/crates/katana/core/contracts/messaging/cairo/.tool-versions new file mode 100644 index 0000000000..697917e577 --- /dev/null +++ b/crates/katana/core/contracts/messaging/cairo/.tool-versions @@ -0,0 +1 @@ +scarb 2.3.1 diff --git a/crates/katana/core/contracts/messaging/cairo/Makefile b/crates/katana/core/contracts/messaging/cairo/Makefile index 267ecb5120..42ba9b392e 100644 --- a/crates/katana/core/contracts/messaging/cairo/Makefile +++ b/crates/katana/core/contracts/messaging/cairo/Makefile @@ -2,21 +2,24 @@ ACCOUNT_L2=./account_l2.json ACCOUNT_L2_ADDR=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 L2_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600 +# Build files helpers. +build = ./target/dev/katana_messaging_ +sierra = .contract_class.json + ################# # ** L1 <-> L2 ** # L1_CONTRACT_ADDR=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -C_MSG_L1_ADDR=0x0429a64d97c1422a37a09fc7406f35c264be59b744aaff5a79d59393eb1bc7e1 -C_MSG_L1_CLASS_HASH = $(shell starkli class-hash target/dev/katana_messaging_contract_msg_l1.sierra.json) +C_MSG_L1_ADDR=0x04231f608ea4a233136f6cdfcd10eaad2e46362bbc4e5d5aa88d0d574ea120d8 -OPTS_L2 := --account ${ACCOUNT_L2} \ - --rpc http://0.0.0.0:5050 \ - --private-key ${L2_PRIVATE_KEY} +OPTS_L2 := --account katana-0 \ + --rpc http://0.0.0.0:5050 setup_for_l1_messaging: - scarb build - starkli declare target/dev/katana_messaging_contract_msg_l1.sierra.json ${OPTS_L2} - starkli deploy --salt 0x1234 ${C_MSG_L1_CLASS_HASH} ${OPTS_L2} + scarb build; \ + class_hash=$$(starkli declare ${build}contract_msg_l1${sierra} ${OPTS_L2}); \ + sleep 1; \ + starkli deploy --salt 0x1234 "$${class_hash}" ${OPTS_L2} send_msg_value_l1_usage: @echo make send_msg_value_l1 value=2 @@ -38,29 +41,31 @@ ACCOUNT_L3=./account_l3.json ACCOUNT_L3_ADDR=0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855 L3_PRIVATE_KEY=0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b -L2_APPCHAIN_MSG_ADDR=0x046c0ea3fb2ad27053e8af3c8cfab38a51afb9fe90fcab1f75446bd41f7d3796 -L2_APPCHAIN_MSG_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_appchain_messaging.sierra.json) +# L2_APPCHAIN_MSG_ADDR=0x046c0ea3fb2ad27053e8af3c8cfab38a51afb9fe90fcab1f75446bd41f7d3796 +# L2_APPCHAIN_MSG_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_appchain_messaging.contract_class.json) L2_CONTRACT1_ADDR=0x054f66c104745e27ad5194815a6c4755cf2076c4809212101dfe31563f312a34 -L2_CONTRACT1_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_contract_1.sierra.json) +L2_CONTRACT1_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_contract_1.contract_class.json) L3_C_MSG_ADDR=0x071278839029ab1f9fa0ce1ee01e38599736dd4e8fed2417158bec4ef5dc6d0f -L3_C_MSG_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_contract_msg_starknet.sierra.json) +L3_C_MSG_CLASS_HASH=$(shell starkli class-hash target/dev/katana_messaging_contract_msg_starknet.contract_class.json) OPTS_L3 := --account ${ACCOUNT_L3} \ --rpc http://0.0.0.0:6060 \ --private-key ${L3_PRIVATE_KEY} setup_l2_messaging: - scarb build - starkli declare target/dev/katana_messaging_appchain_messaging.sierra.json ${OPTS_L2} - starkli declare target/dev/katana_messaging_contract_1.sierra.json ${OPTS_L2} + @set -x; \ + scarb build; \ + appchain_ch=$$(starkli class-hash ${build}ERC20${sierra}); \ + starkli declare target/dev/katana_messaging_appchain_messaging.contract_class.json ${OPTS_L2} + starkli declare target/dev/katana_messaging_contract_1.contract_class.json ${OPTS_L2} starkli deploy --salt 0x1234 ${L2_APPCHAIN_MSG_CLASS_HASH} ${ACCOUNT_L2_ADDR} ${ACCOUNT_L3_ADDR} ${OPTS_L2} starkli deploy --salt 0x1234 ${L2_CONTRACT1_CLASS_HASH} ${L2_APPCHAIN_MSG_ADDR} ${OPTS_L2} setup_l3_messaging: scarb build - starkli declare target/dev/katana_messaging_contract_msg_starknet.sierra.json ${OPTS_L3} + starkli declare target/dev/katana_messaging_contract_msg_starknet.contract_class.json ${OPTS_L3} starkli deploy --salt 0x1234 ${L3_C_MSG_CLASS_HASH} ${OPTS_L3} send_msg_value_l2_usage: diff --git a/crates/katana/core/contracts/messaging/cairo/Scarb.lock b/crates/katana/core/contracts/messaging/cairo/Scarb.lock new file mode 100644 index 0000000000..04f9cd9f8a --- /dev/null +++ b/crates/katana/core/contracts/messaging/cairo/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "katana_messaging" +version = "0.1.0" diff --git a/crates/katana/core/contracts/messaging/cairo/Scarb.toml b/crates/katana/core/contracts/messaging/cairo/Scarb.toml index d21d9f8451..d339966bb7 100644 --- a/crates/katana/core/contracts/messaging/cairo/Scarb.toml +++ b/crates/katana/core/contracts/messaging/cairo/Scarb.toml @@ -3,7 +3,7 @@ name = "katana_messaging" version = "0.1.0" [dependencies] -starknet = ">=2.2.0" +starknet = "2.3.1" [[target.starknet-contract]] sierra = true diff --git a/crates/katana/core/contracts/messaging/run_e2e.sh b/crates/katana/core/contracts/messaging/run_e2e.sh new file mode 100644 index 0000000000..88509a8134 --- /dev/null +++ b/crates/katana/core/contracts/messaging/run_e2e.sh @@ -0,0 +1,22 @@ + +# Setup anvil with messaging + Contract1.sol deployed. +make -sC ./solidity/ deploy_messaging_contracts + +# Declare and deploy contract_msg_l1.cairo. +make -sC ./cairo/ setup_for_l1_messaging + +# Send message L1 -> L2 with a single value. +make -sC solidity/ send_msg selector_str=msg_handler_value payload="[123]" + +# Send message L1 -> L2 with a serialized struct. +make -sC solidity/ send_msg selector_str=msg_handler_struct payload="[1,2]" + +# Send message L2 -> L1 to be manually consumed. +make -sC cairo/ send_msg_value_l1 value=2 + +# Now message sending is asynchronous, so the message must first be settled +# before being consumed. +sleep 20 + +# Consume the message previously sent. You can try to call it once and see the second one reverting. +make -sC solidity/ consume_msg payload="[2]" diff --git a/crates/katana/core/contracts/messaging/solidity/.gitignore b/crates/katana/core/contracts/messaging/solidity/.gitignore index bf3041c954..921f19d973 100644 --- a/crates/katana/core/contracts/messaging/solidity/.gitignore +++ b/crates/katana/core/contracts/messaging/solidity/.gitignore @@ -1,6 +1,8 @@ # Compiler files cache/ out/ +logs/ +lib/forge-std/ # Ignores development broadcast logs !/broadcast @@ -12,4 +14,3 @@ docs/ # Dotenv file .env -logs diff --git a/crates/katana/core/contracts/messaging/solidity/Makefile b/crates/katana/core/contracts/messaging/solidity/Makefile index cb6e611abc..1198b4d527 100644 --- a/crates/katana/core/contracts/messaging/solidity/Makefile +++ b/crates/katana/core/contracts/messaging/solidity/Makefile @@ -10,7 +10,7 @@ export $(shell sed 's/=.*//' .env) # Addresses fixed here for easy testing. C_MSG_L2_ADDR=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 L2_ACCOUNT=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 -L2_CONTRACT_ADDR=0x0429a64d97c1422a37a09fc7406f35c264be59b744aaff5a79d59393eb1bc7e1 +L2_CONTRACT_ADDR=0x04231f608ea4a233136f6cdfcd10eaad2e46362bbc4e5d5aa88d0d574ea120d8 deploy_messaging_contracts: forge script --broadcast --rpc-url ${ETH_RPC_URL} script/LocalTesting.s.sol:LocalSetup @@ -31,5 +31,5 @@ consume_msg_usage: consume_msg: cast send ${C_MSG_L2_ADDR} \ "consumeMessage(uint256,uint256[])" \ - ${L2_ACCOUNT} $(payload) \ + ${L2_CONTRACT_ADDR} $(payload) \ --private-key ${ACCOUNT_PRIVATE_KEY} diff --git a/crates/katana/core/contracts/messaging/solidity/README.md b/crates/katana/core/contracts/messaging/solidity/README.md index 931acc023f..19084a9a64 100644 --- a/crates/katana/core/contracts/messaging/solidity/README.md +++ b/crates/katana/core/contracts/messaging/solidity/README.md @@ -27,3 +27,4 @@ using: ``` (code: 3, message: execution reverted: INVALID_MESSAGE_TO_CONSUME, data: Some(String("0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a494e56414c49445f4d4553534147455f544f5f434f4e53554d45000000000000"))) ``` + diff --git a/crates/katana/core/contracts/messaging/solidity/foundry.toml b/crates/katana/core/contracts/messaging/solidity/foundry.toml index eaeb866692..f663de903a 100644 --- a/crates/katana/core/contracts/messaging/solidity/foundry.toml +++ b/crates/katana/core/contracts/messaging/solidity/foundry.toml @@ -4,4 +4,4 @@ out = "out" libs = ["lib"] fs_permissions = [{ access = "read-write", path = "./logs"}] -# See more config options https://github.com/foundry-rs/foundry/tree/master/config +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index ec99ceb777..e43e8df890 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -294,7 +294,7 @@ fn trace_msg_to_l1_sent(messages: &Vec, hashes: &Vec) { let (to_address, payload_str) = if format!("{}", m.to_address) == "0x4d5347" { (payload_str[0].clone(), &payload_str[1..]) } else { - (format!("{}", m.to_address), &payload_str[..]) + (format!("{:#64x}", m.to_address), &payload_str[..]) }; #[rustfmt::skip] diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs index 8f3e4450fe..fd6f994f19 100644 --- a/crates/katana/executor/src/blockifier/utils.rs +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -320,10 +320,19 @@ pub(super) fn l2_to_l1_messages_from_exec_info( fn get_messages_recursively(info: &CallInfo) -> Vec { let mut messages = vec![]; + // By default, `from_address` must correspond to the contract address that + // is sending the message. In the case of library calls, `code_address` is `None`, + // we then use the `caller_address` instead (which can also be an account). + let from_address = if let Some(code_address) = info.call.code_address { + *code_address.0.key() + } else { + *info.call.caller_address.0.key() + }; + messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MessageToL1 { to_address: FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), - from_address: info.call.caller_address.into(), + from_address: ContractAddress(from_address.into()), payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), })); diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 81c1409cfd..1398a4ec7a 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -20,8 +20,7 @@ cairo-lang-starknet.workspace = true flate2.workspace = true starknet_api.workspace = true -sha3 = { version = "0.10.7", default-features = false } -ethers = "2.0.8" +ethers = "2.0.11" [features] default = [ "blockifier", "rpc", "serde" ] diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs index 2c9cf6a0b4..9dae756811 100644 --- a/crates/katana/primitives/src/utils/transaction.rs +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -1,6 +1,6 @@ use ethers::types::H256; -use sha3::{Digest, Keccak256}; use starknet::core::crypto::compute_hash_on_elements; +use starknet::core::types::MsgToL1; use crate::FieldElement; @@ -166,16 +166,9 @@ pub fn compute_l1_message_hash( to_address: FieldElement, payload: &[FieldElement], ) -> H256 { - let mut buf: Vec = vec![]; - buf.extend(from_address.to_bytes_be()); - buf.extend(to_address.to_bytes_be()); - buf.extend(FieldElement::from(payload.len()).to_bytes_be()); - payload.iter().for_each(|p| buf.extend(p.to_bytes_be())); + let msg = MsgToL1 { from_address, to_address, payload: payload.to_vec() }; - let mut hasher = Keccak256::new(); - hasher.update(buf); - - H256::from_slice(&hasher.finalize()) + H256::from_slice(msg.hash().as_bytes()) } #[cfg(test)] From 3de37a62cfbd81063bd5eb1ad39f028b2d482049 Mon Sep 17 00:00:00 2001 From: Yun Date: Fri, 15 Dec 2023 08:59:46 -0800 Subject: [PATCH 138/192] Added grpc ids clause (#1280) Added grpc ids clause to retrieve entities endpoint --- crates/torii/grpc/proto/types.proto | 11 ++++++-- crates/torii/grpc/src/server/mod.rs | 44 +++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index 138130432a..dd27e2dc52 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -74,12 +74,17 @@ message Query { message Clause { oneof clause_type { - KeysClause keys = 1; - MemberClause member = 2; - CompositeClause composite = 3; + IdsClause ids = 1; + KeysClause keys = 2; + MemberClause member = 3; + CompositeClause composite = 4; } } +message IdsClause { + repeated bytes ids = 1; +} + message KeysClause { string model = 1; repeated bytes keys = 2; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index ba36a65869..87d3c9be7e 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -120,16 +120,47 @@ impl DojoWorld { limit: u32, offset: u32, ) -> Result, Error> { - let query = r#" + self.entities_by_ids(None, limit, offset).await + } + + async fn entities_by_ids( + &self, + ids_clause: Option, + limit: u32, + offset: u32, + ) -> Result, Error> { + // TODO: use prepared statement for where clause + let filter_ids = match ids_clause { + Some(ids_clause) => { + let ids = ids_clause + .ids + .iter() + .map(|id| { + Ok(FieldElement::from_byte_slice_be(id) + .map(|id| format!("entities.id = '{id:#x}'")) + .map_err(ParseError::FromByteSliceError)?) + }) + .collect::, Error>>()?; + + format!("WHERE {}", ids.join(" OR ")) + } + None => String::new(), + }; + + let query = format!( + r#" SELECT entities.id, group_concat(entity_model.model_id) as model_names FROM entities JOIN entity_model ON entities.id = entity_model.entity_id + {filter_ids} GROUP BY entities.id ORDER BY entities.event_id DESC LIMIT ? OFFSET ? - "#; + "# + ); + let db_entities: Vec<(String, String)> = - sqlx::query_as(query).bind(limit).bind(offset).fetch_all(&self.pool).await?; + sqlx::query_as(&query).bind(limit).bind(offset).fetch_all(&self.pool).await?; let mut entities = Vec::with_capacity(db_entities.len()); for (entity_id, models_str) in db_entities { @@ -297,6 +328,13 @@ impl DojoWorld { clause.clause_type.ok_or(QueryError::MissingParam("clause_type".into()))?; match clause_type { + ClauseType::Ids(ids) => { + if ids.ids.is_empty() { + return Err(QueryError::MissingParam("ids".into()).into()); + } + + self.entities_by_ids(Some(ids), query.limit, query.offset).await? + } ClauseType::Keys(keys) => { if keys.keys.is_empty() { return Err(QueryError::MissingParam("keys".into()).into()); From 953411be93c9ccf981c7dc520da06093518ab207 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 13:18:37 -0500 Subject: [PATCH 139/192] Update devcontainer image hash: 7d49bcd (#1284) Co-authored-by: tarrencev --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 36d37341ec..9c9fb7c50f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:a08f2cb", + "image": "ghcr.io/dojoengine/dojo-dev:7d49bcd", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", From 9ba9192b8ccd0e5c12c8b9fe592f53ebe5b20f7e Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 13:22:01 -0500 Subject: [PATCH 140/192] Update devcontainer action create pr token --- .github/workflows/devcontainer.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 52ad04c0f2..916f625fa4 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -71,15 +71,13 @@ jobs: - name: Update devcontainer.json run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - - name: Setup Git credentials - run: | - git config user.name "GitHub Action" - git config user.email "action@github.com" + - name: Update ci devcontainers + run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .github/workflows/ci.yml - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.CREATE_PR_TOKEN }} title: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" commit-message: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" branch: devcontainer-${{ needs.build-and-push.outputs.tag_name }} From cccb03cbe9acfc050b254e907bdbd34d8d97764c Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 14:39:54 -0500 Subject: [PATCH 141/192] Remove USER directive in devcontainer --- .devcontainer/Dockerfile | 9 ++++----- .github/workflows/ci.yml | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 12151326ed..51566e31bb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -20,11 +20,10 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" rustup component add clippy && \ rustup component add rustfmt -RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y +RUN rustup target add x86_64-pc-windows-msvc && \ + rustup target add wasm32-unknown-unknown -# Install dojoup and scarb for vscode user -USER vscode +RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -ENV PATH=${PATH}:/workspaces/dojo/target/release:/home/vscode/.dojo/bin -RUN /home/vscode/.dojo/bin/dojoup +RUN /root/.dojo/bin/dojoup diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d076ef8318..d7800c0023 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: with: fetch-depth: 0 - uses: Swatinem/rust-cache@v2 - - run: dojoup - run: cargo nextest run --all-features ensure-wasm: From de0cc019b7eeff4136a3cbd1cf685ede0094201b Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 14:46:58 -0500 Subject: [PATCH 142/192] Overwrite old devcontainer bump PRs --- .devcontainer/Dockerfile | 3 +++ .github/workflows/devcontainer.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 51566e31bb..86326e5261 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -23,6 +23,9 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" RUN rustup target add x86_64-pc-windows-msvc && \ rustup target add wasm32-unknown-unknown +RUN rustup toolchain install nightly && \ + rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly + RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 916f625fa4..da93787b8b 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -80,6 +80,6 @@ jobs: token: ${{ secrets.CREATE_PR_TOKEN }} title: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" commit-message: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" - branch: devcontainer-${{ needs.build-and-push.outputs.tag_name }} + branch: bump-devcontainer base: main delete-branch: true From 94838e8edb6a240f3ac769b1535823814285390c Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 15:54:49 -0500 Subject: [PATCH 143/192] Fix devcontainer path --- .devcontainer/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 86326e5261..3b1199441b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -29,4 +29,5 @@ RUN rustup toolchain install nightly && \ RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -RUN /root/.dojo/bin/dojoup +ENV PATH=${PATH}:/root/.dojo/bin +RUN dojoup From 630bd82637848cc02ce37a998a3b57bd4db35798 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 16:18:21 -0500 Subject: [PATCH 144/192] Fix devcontainer permissions --- .devcontainer/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3b1199441b..4bab5248dc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -31,3 +31,6 @@ RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash ENV PATH=${PATH}:/root/.dojo/bin RUN dojoup + +RUN chown -R root:root /usr/local/cargo +RUN chmod -R 700 /usr/local/cargo \ No newline at end of file From d416ff3f875fa6dd8339e11af55ff56667d44777 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sat, 16 Dec 2023 06:59:41 +0800 Subject: [PATCH 145/192] Introduce `katana-codecs` for DB encoding (#1285) --- Cargo.lock | 53 ++++++++- Cargo.toml | 2 + crates/katana/primitives/src/contract.rs | 2 +- crates/katana/primitives/src/lib.rs | 2 - crates/katana/primitives/src/receipt.rs | 4 +- crates/katana/primitives/src/serde/mod.rs | 4 - crates/katana/storage/codecs/Cargo.toml | 10 ++ .../katana/storage/codecs/derive/Cargo.toml | 15 +++ .../katana/storage/codecs/derive/src/lib.rs | 30 ++++++ crates/katana/storage/codecs/src/lib.rs | 102 ++++++++++++++++++ crates/katana/storage/db/Cargo.toml | 20 +++- crates/katana/storage/db/src/codecs.rs | 73 ------------- crates/katana/storage/db/src/codecs/mod.rs | 90 ++++++++++++++++ .../katana/storage/db/src/codecs/postcard.rs | 42 ++++++++ crates/katana/storage/db/src/error.rs | 4 +- .../db/src/models/contract.rs} | 96 +++++++++++++---- crates/katana/storage/db/src/models/mod.rs | 2 + .../katana/storage/db/src/models/storage.rs | 35 ++++++ crates/katana/storage/db/src/tables.rs | 42 ++++---- 19 files changed, 496 insertions(+), 132 deletions(-) delete mode 100644 crates/katana/primitives/src/serde/mod.rs create mode 100644 crates/katana/storage/codecs/Cargo.toml create mode 100644 crates/katana/storage/codecs/derive/Cargo.toml create mode 100644 crates/katana/storage/codecs/derive/src/lib.rs create mode 100644 crates/katana/storage/codecs/src/lib.rs delete mode 100644 crates/katana/storage/db/src/codecs.rs create mode 100644 crates/katana/storage/db/src/codecs/mod.rs create mode 100644 crates/katana/storage/db/src/codecs/postcard.rs rename crates/katana/{primitives/src/serde/blockifier.rs => storage/db/src/models/contract.rs} (82%) create mode 100644 crates/katana/storage/db/src/models/storage.rs diff --git a/Cargo.lock b/Cargo.lock index 6da3ab8b9d..e55fcfb03d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1855,6 +1855,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "coins-bip32" version = "0.8.7" @@ -2757,6 +2763,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + [[package]] name = "ena" version = "0.14.2" @@ -5310,6 +5322,24 @@ dependencies = [ "url", ] +[[package]] +name = "katana-codecs" +version = "0.4.1" +dependencies = [ + "bytes", + "katana-primitives", +] + +[[package]] +name = "katana-codecs-derive" +version = "0.4.1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.41", +] + [[package]] name = "katana-core" version = "0.4.1" @@ -5349,12 +5379,18 @@ version = "0.4.1" dependencies = [ "anyhow", "bincode 1.3.3", - "flate2", + "blockifier", + "cairo-vm", "katana-primitives", "page_size", "parking_lot 0.12.1", + "postcard", "reth-libmdbx", "serde", + "serde_json", + "starknet", + "starknet_api", + "tempfile", "thiserror", ] @@ -6816,6 +6852,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -7384,7 +7431,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#013b4c93db27e6c1926d3295319f9a4319d4f691" +source = "git+https://github.com/paradigmxyz/reth.git?rev=b34b0d3#b34b0d3c8de2598b2976f7ee2fc1a166c50b1b94" dependencies = [ "bitflags 2.4.1", "byteorder", @@ -7399,7 +7446,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "0.1.0-alpha.13" -source = "git+https://github.com/paradigmxyz/reth.git#013b4c93db27e6c1926d3295319f9a4319d4f691" +source = "git+https://github.com/paradigmxyz/reth.git?rev=b34b0d3#b34b0d3c8de2598b2976f7ee2fc1a166c50b1b94" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index a6b3ecc46a..560574e454 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ members = [ "crates/katana/rpc/rpc-types", "crates/katana/rpc/rpc-types-builder", "crates/katana/runner", + "crates/katana/storage/codecs", + "crates/katana/storage/codecs/derive", "crates/katana/storage/db", "crates/katana/storage/provider", "crates/metrics", diff --git a/crates/katana/primitives/src/contract.rs b/crates/katana/primitives/src/contract.rs index 64596156af..d9d778bad8 100644 --- a/crates/katana/primitives/src/contract.rs +++ b/crates/katana/primitives/src/contract.rs @@ -50,7 +50,7 @@ impl From for FieldElement { } /// Represents a generic contract instance information. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GenericContractInfo { /// The nonce of the contract. diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index c24e5d06ce..d09bf7c232 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -7,8 +7,6 @@ pub mod transaction; pub mod version; pub mod conversion; -#[cfg(feature = "serde")] -pub mod serde; pub mod state; pub mod utils; diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index a31d255c34..e053f4e845 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -27,7 +27,7 @@ pub struct MessageToL1 { } /// Receipt for a `Invoke` transaction. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvokeTxReceipt { /// Actual fee paid for the transaction. @@ -139,7 +139,7 @@ impl Receipt { /// Transaction execution resources. /// /// The resources consumed by a transaction during its execution. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxExecutionResources { /// The number of cairo steps used diff --git a/crates/katana/primitives/src/serde/mod.rs b/crates/katana/primitives/src/serde/mod.rs deleted file mode 100644 index 537efa7ca8..0000000000 --- a/crates/katana/primitives/src/serde/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! De/serializable counterparts for types that are not de/serializable out of the box. - -#[cfg(feature = "blockifier")] -pub mod blockifier; diff --git a/crates/katana/storage/codecs/Cargo.toml b/crates/katana/storage/codecs/Cargo.toml new file mode 100644 index 0000000000..7af7355661 --- /dev/null +++ b/crates/katana/storage/codecs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition.workspace = true +name = "katana-codecs" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1.5.0" +katana-primitives = { path = "../../primitives" } diff --git a/crates/katana/storage/codecs/derive/Cargo.toml b/crates/katana/storage/codecs/derive/Cargo.toml new file mode 100644 index 0000000000..ec7fb56f75 --- /dev/null +++ b/crates/katana/storage/codecs/derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition.workspace = true +name = "katana-codecs-derive" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.70" +quote = "1.0.33" +serde.workspace = true +syn = { version = "2.0.41", features = [ "extra-traits", "full" ] } diff --git a/crates/katana/storage/codecs/derive/src/lib.rs b/crates/katana/storage/codecs/derive/src/lib.rs new file mode 100644 index 0000000000..b4fae093f6 --- /dev/null +++ b/crates/katana/storage/codecs/derive/src/lib.rs @@ -0,0 +1,30 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_attribute] +#[rustfmt::skip] +#[allow(unreachable_code)] +pub fn main_codec(args: TokenStream, input: TokenStream) -> TokenStream { + #[cfg(feature = "postcard")] + return use_postcard(args, input); + + no_codec(args, input) +} + +#[proc_macro_attribute] +pub fn no_codec(_args: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + quote! { #ast }.into() +} + +#[proc_macro_attribute] +pub fn use_postcard(_args: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + quote! { + #[derive(serde::Serialize, serde::Deserialize)] + #ast + } + .into() +} diff --git a/crates/katana/storage/codecs/src/lib.rs b/crates/katana/storage/codecs/src/lib.rs new file mode 100644 index 0000000000..9e0c2e2043 --- /dev/null +++ b/crates/katana/storage/codecs/src/lib.rs @@ -0,0 +1,102 @@ +use katana_primitives::contract::ContractAddress; +use katana_primitives::FieldElement; + +/// Trait for writing compact representation of the types that implement it. +pub trait Compact: Sized { + /// Write the compact representation of `self` into `buf`, returning the number of bytes + /// written. + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>; + + /// Read the compact representation of `self` from `buf`, returning the value and the + /// remaining bytes in the buffer. + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]); +} + +macro_rules! impl_compact_for_uints { + ($($ty:ty),*) => { + $( + impl Compact for $ty { + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + // get the leading zeros in term of bytes + let zeros = self.leading_zeros() as usize / 8; + buf.put_slice(&self.to_be_bytes()[zeros..]); + std::mem::size_of::<$ty>() - zeros + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + if len > 0 { + let mut arr = [0; std::mem::size_of::<$ty>()]; + arr[std::mem::size_of::<$ty>() - len..].copy_from_slice(&buf[..len]); + return (<$ty>::from_be_bytes(arr), &buf[len..]) + } + (0, buf) + } + } + )* + }; +} + +macro_rules! impl_compact_felt { + ($($ty:ty),*) => { + $( + impl Compact for $ty { + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let zeros = self + .to_bits_le() + .iter() + .rev() + .position(|n| *n != false) + .map_or(32, |pos| pos / 8 as usize); + buf.put_slice(&self.to_bytes_be()[zeros..]); + 32 - zeros + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + if len > 0 { + let mut arr = [0u8; 32]; + arr[32 - len..].copy_from_slice(&buf[..len]); + (FieldElement::from_bytes_be(&arr).unwrap().into(), &buf[len..]) + } else { + (FieldElement::ZERO.into(), buf) + } + } + } + )* + } +} + +impl_compact_for_uints!(u64); +impl_compact_felt!(FieldElement, ContractAddress); + +#[cfg(test)] +mod tests { + use katana_primitives::FieldElement; + + use crate::Compact; + + #[test] + fn felt_compact() { + let mut compacted = vec![]; + let value = FieldElement::from(124123137u128); + let compacted_size = value.to_compact(&mut compacted); + let (uncompacted, _) = FieldElement::from_compact(&compacted, compacted_size); + assert_eq!(value, uncompacted); + } + + #[test] + fn uint_compact() { + let mut compacted = vec![]; + let value = 1312412337u64; + let compacted_size = value.to_compact(&mut compacted); + let (uncompacted, _) = u64::from_compact(&compacted, compacted_size); + assert_eq!(value, uncompacted); + } +} diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index ef4fdd5526..8891ed8569 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -11,13 +11,29 @@ katana-primitives = { path = "../../primitives" } anyhow.workspace = true bincode = "1.3.3" -flate2.workspace = true page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true +serde_json.workspace = true +tempfile = { version = "3.8.1", optional = true } thiserror.workspace = true +blockifier.workspace = true +cairo-vm.workspace = true +starknet_api.workspace = true + +# codecs +postcard = { version = "1.0.8", optional = true, default-features = false, features = [ "use-std" ] } + [dependencies.libmdbx] git = "https://github.com/paradigmxyz/reth.git" package = "reth-libmdbx" -version = "=0.1.0-alpha.13" +rev = "b34b0d3" + +[dev-dependencies] +starknet.workspace = true +tempfile = "3.8.1" + +[features] +default = [ "postcard" ] +postcard = [ "dep:postcard" ] diff --git a/crates/katana/storage/db/src/codecs.rs b/crates/katana/storage/db/src/codecs.rs deleted file mode 100644 index 0b880e4072..0000000000 --- a/crates/katana/storage/db/src/codecs.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::io::Read; - -use flate2::Compression; - -use crate::error::CodecError; - -/// A trait for encoding the key of a table. -pub trait Encode { - type Encoded: AsRef<[u8]> + Into>; - fn encode(self) -> Self::Encoded; -} - -pub trait Decode: Sized { - fn decode>(bytes: B) -> Result; -} - -/// A trait for compressing data that are stored in the db. -pub trait Compress { - type Compressed: AsRef<[u8]>; - fn compress(self) -> Self::Compressed; -} - -/// A trait for decompressing data that are read from the db. -pub trait Decompress: Sized { - fn decompress>(bytes: B) -> Result; -} - -impl Encode for T -where - T: serde::Serialize, -{ - type Encoded = Vec; - fn encode(self) -> Self::Encoded { - bincode::serialize(&self).expect("valid encoding") - } -} - -impl Decode for T -where - T: for<'a> serde::Deserialize<'a>, -{ - fn decode>(bytes: B) -> Result { - bincode::deserialize(bytes.as_ref()).map_err(|e| CodecError::Decode(e.to_string())) - } -} - -impl Compress for T -where - T: Encode + serde::Serialize, - ::Encoded: AsRef<[u8]>, -{ - type Compressed = Vec; - fn compress(self) -> Self::Compressed { - let mut compressed = Vec::new(); - flate2::read::DeflateEncoder::new(Encode::encode(self).as_ref(), Compression::best()) - .read_to_end(&mut compressed) - .unwrap(); - compressed - } -} - -impl Decompress for T -where - T: Decode + for<'a> serde::Deserialize<'a>, -{ - fn decompress>(bytes: B) -> Result { - let mut bin = Vec::new(); - flate2::read::DeflateDecoder::new(bytes.as_ref()) - .read_to_end(&mut bin) - .map_err(|e| CodecError::Decompress(e.to_string()))?; - Decode::decode(bin) - } -} diff --git a/crates/katana/storage/db/src/codecs/mod.rs b/crates/katana/storage/db/src/codecs/mod.rs new file mode 100644 index 0000000000..273dc15cf7 --- /dev/null +++ b/crates/katana/storage/db/src/codecs/mod.rs @@ -0,0 +1,90 @@ +#[cfg(feature = "postcard")] +pub mod postcard; + +use katana_primitives::block::FinalityStatus; +use katana_primitives::contract::ContractAddress; +use katana_primitives::FieldElement; + +use crate::error::CodecError; + +/// A trait for encoding the key of a table. +pub trait Encode { + type Encoded: AsRef<[u8]> + Into>; + fn encode(self) -> Self::Encoded; +} + +pub trait Decode: Sized { + fn decode>(bytes: B) -> Result; +} + +/// A trait for compressing data that are stored in the db. +pub trait Compress { + type Compressed: AsRef<[u8]>; + fn compress(self) -> Self::Compressed; +} + +/// A trait for decompressing data that are read from the db. +pub trait Decompress: Sized { + fn decompress>(bytes: B) -> Result; +} + +macro_rules! impl_encode_and_decode_for_uints { + ($($ty:ty),*) => { + $( + impl Encode for $ty { + type Encoded = [u8; std::mem::size_of::<$ty>()]; + fn encode(self) -> Self::Encoded { + self.to_be_bytes() + } + } + + impl Decode for $ty { + fn decode>(bytes: B) -> Result { + let mut buf = [0u8; std::mem::size_of::<$ty>()]; + buf.copy_from_slice(bytes.as_ref()); + Ok(Self::from_be_bytes(buf)) + } + } + )* + } +} + +macro_rules! impl_encode_and_decode_for_felts { + ($($ty:ty),*) => { + $( + impl Encode for $ty { + type Encoded = [u8; 32]; + fn encode(self) -> Self::Encoded { + self.to_bytes_be() + } + } + + impl Decode for $ty { + fn decode>(bytes: B) -> Result { + let felt = FieldElement::from_byte_slice_be(bytes.as_ref()); + Ok(felt.map_err(|e| CodecError::Decode(e.to_string()))?.into()) + } + } + )* + } +} + +impl_encode_and_decode_for_uints!(u64); +impl_encode_and_decode_for_felts!(FieldElement, ContractAddress); + +impl Compress for FinalityStatus { + type Compressed = [u8; 1]; + fn compress(self) -> Self::Compressed { + [self as u8] + } +} + +impl Decompress for FinalityStatus { + fn decompress>(bytes: B) -> Result { + match bytes.as_ref().first() { + Some(0) => Ok(FinalityStatus::AcceptedOnL2), + Some(1) => Ok(FinalityStatus::AcceptedOnL1), + _ => Err(CodecError::Decode("Invalid status".into())), + } + } +} diff --git a/crates/katana/storage/db/src/codecs/postcard.rs b/crates/katana/storage/db/src/codecs/postcard.rs new file mode 100644 index 0000000000..0564dc3370 --- /dev/null +++ b/crates/katana/storage/db/src/codecs/postcard.rs @@ -0,0 +1,42 @@ +use katana_primitives::block::Header; +use katana_primitives::contract::{ContractAddress, GenericContractInfo, SierraClass}; +use katana_primitives::receipt::Receipt; +use katana_primitives::transaction::Tx; +use katana_primitives::FieldElement; + +use super::{Compress, Decompress}; +use crate::error::CodecError; +use crate::models::block::StoredBlockBodyIndices; +use crate::models::contract::StoredContractClass; + +macro_rules! impl_compress_and_decompress_for_table_values { + ($($name:ty),*) => { + $( + impl Compress for $name { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + postcard::to_stdvec(&self).unwrap() + } + } + + impl Decompress for $name { + fn decompress>(bytes: B) -> Result { + postcard::from_bytes(bytes.as_ref()).map_err(|e| CodecError::Decode(e.to_string())) + } + } + )* + } +} + +impl_compress_and_decompress_for_table_values!( + u64, + Tx, + Header, + Receipt, + SierraClass, + FieldElement, + ContractAddress, + StoredContractClass, + GenericContractInfo, + StoredBlockBodyIndices +); diff --git a/crates/katana/storage/db/src/error.rs b/crates/katana/storage/db/src/error.rs index 70bb255153..05f12ab7af 100644 --- a/crates/katana/storage/db/src/error.rs +++ b/crates/katana/storage/db/src/error.rs @@ -1,4 +1,4 @@ -#[derive(Debug, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum DatabaseError { #[error("failed to open an environment: {0}")] OpenEnv(libmdbx::Error), @@ -40,7 +40,7 @@ pub enum DatabaseError { Clear(libmdbx::Error), } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum CodecError { #[error("failed to decode data: {0}")] Decode(String), diff --git a/crates/katana/primitives/src/serde/blockifier.rs b/crates/katana/storage/db/src/models/contract.rs similarity index 82% rename from crates/katana/primitives/src/serde/blockifier.rs rename to crates/katana/storage/db/src/models/contract.rs index 36e436d2bd..92894bb601 100644 --- a/crates/katana/primitives/src/serde/blockifier.rs +++ b/crates/katana/storage/db/src/models/contract.rs @@ -1,3 +1,5 @@ +//! Serializable without using custome functions + use std::collections::HashMap; use std::sync::Arc; @@ -16,26 +18,26 @@ use serde::{Deserialize, Serialize}; use starknet_api::core::EntryPointSelector; use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset, EntryPointType}; -#[derive(Debug, Serialize, Deserialize)] -pub enum SerializableContractClass { - V0(SerializableContractClassV0), - V1(SerializableContractClassV1), +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum StoredContractClass { + V0(StoredContractClassV0), + V1(StoredContractClassV1), } -#[derive(Debug, Serialize, Deserialize)] -pub struct SerializableContractClassV0 { +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct StoredContractClassV0 { pub program: SerializableProgram, pub entry_points_by_type: HashMap>, } -#[derive(Debug, Serialize, Deserialize)] -pub struct SerializableContractClassV1 { +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct StoredContractClassV1 { pub program: SerializableProgram, pub hints: HashMap>, pub entry_points_by_type: HashMap>, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SerializableEntryPoint { pub selector: EntryPointSelector, pub offset: SerializableEntryPointOffset, @@ -53,7 +55,7 @@ impl From for EntryPoint { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SerializableEntryPointOffset(pub usize); impl From for SerializableEntryPointOffset { @@ -68,7 +70,7 @@ impl From for EntryPointOffset { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SerializableEntryPointV1 { pub selector: EntryPointSelector, pub offset: SerializableEntryPointOffset, @@ -331,10 +333,10 @@ impl From for FlowTrackingData { } } -impl From for blockifier::execution::contract_class::ContractClass { - fn from(value: SerializableContractClass) -> Self { +impl From for ContractClass { + fn from(value: StoredContractClass) -> Self { match value { - SerializableContractClass::V0(v0) => { + StoredContractClass::V0(v0) => { ContractClass::V0(ContractClassV0(Arc::new(ContractClassV0Inner { program: v0.program.into(), entry_points_by_type: v0 @@ -344,7 +346,7 @@ impl From for blockifier::execution::contract_class:: .collect(), }))) } - SerializableContractClass::V1(v1) => { + StoredContractClass::V1(v1) => { ContractClass::V1(ContractClassV1(Arc::new(ContractClassV1Inner { hints: v1 .hints @@ -373,20 +375,24 @@ impl From for blockifier::execution::contract_class:: } } -impl From for SerializableContractClass { +impl From for StoredContractClass { fn from(value: ContractClass) -> Self { match value { - ContractClass::V0(v0) => SerializableContractClass::V0(SerializableContractClassV0 { - program: v0.program.clone().into(), - entry_points_by_type: v0 + ContractClass::V0(v0) => { + let entry_points_by_type = v0 .entry_points_by_type .clone() .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }), + .map(|(k, v)| (k, v.into_iter().map(SerializableEntryPoint::from).collect())) + .collect(); - ContractClass::V1(v1) => SerializableContractClass::V1(SerializableContractClassV1 { + StoredContractClass::V0(StoredContractClassV0 { + program: v0.program.clone().into(), + entry_points_by_type, + }) + } + + ContractClass::V1(v1) => StoredContractClass::V1(StoredContractClassV1 { program: v1.program.clone().into(), entry_points_by_type: v1 .entry_points_by_type @@ -411,3 +417,47 @@ impl From for Serializable } } } + +#[cfg(test)] +mod tests { + use starknet_api::hash::StarkFelt; + use starknet_api::stark_felt; + + use super::*; + + #[test] + fn serialize_deserialize_legacy_entry_points() { + let non_serde = vec![ + EntryPoint { + offset: EntryPointOffset(0x25f), + selector: EntryPointSelector(stark_felt!( + "0x289da278a8dc833409cabfdad1581e8e7d40e42dcaed693fa4008dcdb4963b3" + )), + }, + EntryPoint { + offset: EntryPointOffset(0x1b2), + selector: EntryPointSelector(stark_felt!( + "0x29e211664c0b63c79638fbea474206ca74016b3e9a3dc4f9ac300ffd8bdf2cd" + )), + }, + EntryPoint { + offset: EntryPointOffset(0x285), + selector: EntryPointSelector(stark_felt!( + "0x36fcbf06cd96843058359e1a75928beacfac10727dab22a3972f0af8aa92895" + )), + }, + ]; + + // convert to serde and back + let serde: Vec = + non_serde.iter().map(|e| e.clone().into()).collect(); + + // convert to json + let json = serde_json::to_vec(&serde).unwrap(); + let serde: Vec = serde_json::from_slice(&json).unwrap(); + + let same_non_serde: Vec = serde.iter().map(|e| e.clone().into()).collect(); + + assert_eq!(non_serde, same_non_serde); + } +} diff --git a/crates/katana/storage/db/src/models/mod.rs b/crates/katana/storage/db/src/models/mod.rs index a863eaad24..656ed6c7a2 100644 --- a/crates/katana/storage/db/src/models/mod.rs +++ b/crates/katana/storage/db/src/models/mod.rs @@ -1 +1,3 @@ pub mod block; +pub mod contract; +pub mod storage; diff --git a/crates/katana/storage/db/src/models/storage.rs b/crates/katana/storage/db/src/models/storage.rs new file mode 100644 index 0000000000..c4350421b9 --- /dev/null +++ b/crates/katana/storage/db/src/models/storage.rs @@ -0,0 +1,35 @@ +use katana_primitives::contract::{StorageKey, StorageValue}; + +use crate::codecs::{Compress, Decompress}; +use crate::error::CodecError; + +/// Represents a contract storage entry. +/// +/// `key` is the subkey for the dupsort table. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct StorageEntry { + /// The storage key. + pub key: StorageKey, + /// The storage value. + pub value: StorageValue, +} + +impl Compress for StorageEntry { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut buf = Vec::new(); + buf.extend_from_slice(&self.key.to_bytes_be()); + buf.extend_from_slice(&self.value.compress()); + buf + } +} + +impl Decompress for StorageEntry { + fn decompress>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let key = StorageKey::from_byte_slice_be(&bytes[0..32]) + .map_err(|e| CodecError::Decompress(e.to_string()))?; + let value = StorageValue::decompress(&bytes[32..])?; + Ok(Self { key, value }) + } +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index 82db42334a..6ec43b55c8 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -1,24 +1,20 @@ -use katana_primitives::block::{BlockHash, BlockNumber, Header}; +use katana_primitives::block::{BlockHash, BlockNumber, FinalityStatus, Header}; use katana_primitives::contract::{ ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, SierraClass, StorageKey, - StorageValue, }; use katana_primitives::receipt::Receipt; -use katana_primitives::serde::blockifier::SerializableContractClass; use katana_primitives::transaction::{Tx, TxHash, TxNumber}; -use serde::{Deserialize, Serialize}; use crate::codecs::{Compress, Decode, Decompress, Encode}; use crate::models::block::StoredBlockBodyIndices; +use crate::models::contract::StoredContractClass; +use crate::models::storage::StorageEntry; -pub trait Key: - Encode + Decode + Serialize + for<'a> Deserialize<'a> + Clone + std::fmt::Debug -{ -} +pub trait Key: Encode + Decode + Clone + std::fmt::Debug {} pub trait Value: Compress + Decompress + std::fmt::Debug {} -impl Key for T where T: Serialize + for<'a> Deserialize<'a> + Clone + std::fmt::Debug {} -impl Value for T where T: Serialize + for<'a> Deserialize<'a> + std::fmt::Debug {} +impl Key for T where T: Encode + Decode + Clone + std::fmt::Debug {} +impl Value for T where T: Compress + Decompress + std::fmt::Debug {} /// An asbtraction for a table. pub trait Table { @@ -47,7 +43,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 15; +pub const NUM_TABLES: usize = 17; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -99,7 +95,7 @@ macro_rules! define_tables_enum { return Ok(Tables::$table) },)* _ => { - return Err("Unknown table".to_string()) + return Err(format!("unknown table `{s}`")) } } } @@ -152,16 +148,18 @@ define_tables_enum! {[ (BlockHashes, TableType::Table), (BlockNumbers, TableType::Table), (BlockBodyIndices, TableType::Table), + (BlockStatusses, TableType::Table), (TxNumbers, TableType::Table), + (TxBlocks, TableType::Table), (TxHashes, TableType::Table), (Transactions, TableType::Table), (Receipts, TableType::Table), - (ClassDeclarations, TableType::Table), - (ContractDeployments, TableType::Table), (CompiledClassHashes, TableType::Table), (CompiledContractClasses, TableType::Table), (SierraClasses, TableType::Table), (ContractInfo, TableType::Table), + (ContractDeployments, TableType::DupSort), + (ClassDeclarations, TableType::DupSort), (ContractStorage, TableType::DupSort) ]} @@ -172,6 +170,8 @@ tables! { BlockHashes: (BlockNumber) => BlockHash, /// Stores block numbers according to its block hash BlockNumbers: (BlockHash) => BlockNumber, + /// Stores block finality status according to its block number + BlockStatusses: (BlockNumber) => FinalityStatus, /// Block number to its body indices which stores the tx number of /// the first tx in the block and the number of txs in the block. BlockBodyIndices: (BlockNumber) => StoredBlockBodyIndices, @@ -181,20 +181,22 @@ tables! { TxHashes: (TxNumber) => TxHash, /// Store canonical transactions Transactions: (TxNumber) => Tx, + /// Stores the block number of a transaction. + TxBlocks: (TxNumber) => BlockNumber, /// Store transaction receipts Receipts: (TxNumber) => Receipt, - /// Stores the list of class hashes according to the block number it was declared in. - ClassDeclarations: (BlockNumber) => Vec, - /// Store the list of contracts deployed in a block according to its block number. - ContractDeployments: (BlockNumber) => Vec, /// Store compiled classes CompiledClassHashes: (ClassHash) => CompiledClassHash, /// Store compiled contract classes according to its compiled class hash - CompiledContractClasses: (CompiledClassHash) => SerializableContractClass, + CompiledContractClasses: (ClassHash) => StoredContractClass, /// Store Sierra classes according to its class hash SierraClasses: (ClassHash) => SierraClass, /// Store contract information according to its contract address ContractInfo: (ContractAddress) => GenericContractInfo, /// Store contract storage - ContractStorage: (ContractAddress, StorageKey) => StorageValue + ContractStorage: (ContractAddress, StorageKey) => StorageEntry, + /// Stores the list of class hashes according to the block number it was declared in. + ClassDeclarations: (BlockNumber, ClassHash) => ClassHash, + /// Store the list of contracts deployed in a block according to its block number. + ContractDeployments: (BlockNumber, ContractAddress) => ContractAddress } From 743c9e55b0039e241743dcea406f3453ab1ffd6e Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sat, 16 Dec 2023 08:50:39 +0800 Subject: [PATCH 146/192] test(katana-db): add mdbx tests (#1293) --- crates/katana/core/src/backend/storage.rs | 4 +- crates/katana/storage/db/Cargo.toml | 1 + crates/katana/storage/db/src/mdbx/mod.rs | 308 ++++++++++++++++++ crates/katana/storage/provider/Cargo.toml | 2 +- .../provider/src/providers/fork/backend.rs | 5 +- .../provider/src/providers/fork/mod.rs | 12 +- .../provider/src/providers/in_memory/mod.rs | 12 +- 7 files changed, 319 insertions(+), 25 deletions(-) diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 7970dd9fc2..a977a53f99 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -7,7 +7,7 @@ use katana_primitives::state::StateUpdatesWithDeclaredClasses; use katana_primitives::version::CURRENT_STARKNET_VERSION; use katana_primitives::FieldElement; use katana_provider::traits::block::{BlockProvider, BlockWriter}; -use katana_provider::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use katana_provider::traits::contract::ContractClassWriter; use katana_provider::traits::state::{StateFactoryProvider, StateRootProvider, StateWriter}; use katana_provider::traits::state_update::StateUpdateProvider; use katana_provider::traits::transaction::{ @@ -27,7 +27,6 @@ pub trait Database: + ReceiptProvider + StateUpdateProvider + StateRootProvider - + ContractInfoProvider + StateWriter + ContractClassWriter + StateFactoryProvider @@ -45,7 +44,6 @@ impl Database for T where + TransactionsProviderExt + ReceiptProvider + StateUpdateProvider - + ContractInfoProvider + StateRootProvider + StateWriter + ContractClassWriter diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 8891ed8569..a0432e0fb0 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -37,3 +37,4 @@ tempfile = "3.8.1" [features] default = [ "postcard" ] postcard = [ "dep:postcard" ] +test-utils = [ "dep:tempfile" ] diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 8a2535830e..76fc9b1cf3 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -95,4 +95,312 @@ impl DbEnv { pub fn tx_mut(&self) -> Result, DatabaseError> { Ok(Tx::new(self.0.begin_rw_txn().map_err(DatabaseError::CreateRWTx)?)) } + + /// Takes a function and passes a read-write transaction into it, making sure it's always + /// committed in the end of the execution. + pub fn update(&self, f: F) -> Result + where + F: FnOnce(&Tx) -> T, + { + let tx = self.tx_mut()?; + let res = f(&tx); + tx.commit()?; + Ok(res) + } +} + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils { + use std::path::Path; + use std::sync::Arc; + + use super::{DbEnv, DbEnvKind}; + + const ERROR_DB_CREATION: &str = "Not able to create the mdbx file."; + + /// Create database for testing + pub fn create_test_db(kind: DbEnvKind) -> Arc { + Arc::new(create_test_db_with_path( + kind, + &tempfile::TempDir::new().expect("Failed to create temp dir.").into_path(), + )) + } + + /// Create database for testing with specified path + pub fn create_test_db_with_path(kind: DbEnvKind, path: &Path) -> DbEnv { + let env = DbEnv::open(path, kind).expect(ERROR_DB_CREATION); + env.create_tables().expect("Failed to create tables."); + env + } +} + +#[cfg(test)] +mod tests { + + use katana_primitives::block::Header; + use katana_primitives::contract::{ContractAddress, GenericContractInfo}; + use katana_primitives::FieldElement; + use starknet::macros::felt; + + use super::*; + use crate::codecs::Encode; + use crate::mdbx::cursor::Walker; + use crate::mdbx::test_utils::create_test_db; + use crate::models::storage::StorageEntry; + use crate::tables::{BlockHashes, ContractInfo, ContractStorage, Headers, Table}; + + const ERROR_PUT: &str = "Not able to insert value into table."; + const ERROR_DELETE: &str = "Failed to delete value from table."; + const ERROR_GET: &str = "Not able to get value from table."; + const ERROR_COMMIT: &str = "Not able to commit transaction."; + const ERROR_RETURN_VALUE: &str = "Mismatching result."; + const ERROR_UPSERT: &str = "Not able to upsert the value to the table."; + const ERROR_INIT_TX: &str = "Failed to create a MDBX transaction."; + const ERROR_INIT_CURSOR: &str = "Failed to create cursor."; + const ERROR_GET_AT_CURSOR_POS: &str = "Failed to get value at cursor position."; + + #[test] + fn db_creation() { + create_test_db(DbEnvKind::RW); + } + + #[test] + fn db_manual_put_get() { + let env = create_test_db(DbEnvKind::RW); + + let value = Header::default(); + let key = 1u64; + + // PUT + let tx = env.tx_mut().expect(ERROR_INIT_TX); + tx.put::(key, value.clone()).expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + // GET + let tx = env.tx().expect(ERROR_INIT_TX); + let result = tx.get::(key).expect(ERROR_GET); + let total_entries = tx.entries::().expect(ERROR_GET); + tx.commit().expect(ERROR_COMMIT); + + assert!(total_entries == 1); + assert!(result.expect(ERROR_RETURN_VALUE) == value); + } + + #[test] + fn db_delete() { + let env = create_test_db(DbEnvKind::RW); + + let value = Header::default(); + let key = 1u64; + + // PUT + let tx = env.tx_mut().expect(ERROR_INIT_TX); + tx.put::(key, value).expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + let entries = env.tx().expect(ERROR_INIT_TX).entries::().expect(ERROR_GET); + assert!(entries == 1); + + // DELETE + let tx = env.tx_mut().expect(ERROR_INIT_TX); + tx.delete::(key, None).expect(ERROR_DELETE); + tx.commit().expect(ERROR_COMMIT); + + let entries = env.tx().expect(ERROR_INIT_TX).entries::().expect(ERROR_GET); + assert!(entries == 0); + } + + #[test] + fn db_manual_cursor_walk() { + let env = create_test_db(DbEnvKind::RW); + + let key1 = 1u64; + let key2 = 2u64; + let key3 = 3u64; + let header1 = Header::default(); + let header2 = Header::default(); + let header3 = Header::default(); + + // PUT + let tx = env.tx_mut().expect(ERROR_INIT_TX); + tx.put::(key1, header1.clone()).expect(ERROR_PUT); + tx.put::(key2, header2.clone()).expect(ERROR_PUT); + tx.put::(key3, header3.clone()).expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + // CURSOR + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().expect(ERROR_INIT_CURSOR); + let (_, result1) = cursor.next().expect(ERROR_GET_AT_CURSOR_POS).expect(ERROR_RETURN_VALUE); + let (_, result2) = cursor.next().expect(ERROR_GET_AT_CURSOR_POS).expect(ERROR_RETURN_VALUE); + let (_, result3) = cursor.next().expect(ERROR_GET_AT_CURSOR_POS).expect(ERROR_RETURN_VALUE); + tx.commit().expect(ERROR_COMMIT); + + assert!(result1 == header1); + assert!(result2 == header2); + assert!(result3 == header3); + } + + #[test] + fn db_cursor_upsert() { + let db = create_test_db(DbEnvKind::RW); + let tx = db.tx_mut().expect(ERROR_INIT_TX); + + let mut cursor = tx.cursor::().unwrap(); + let key: ContractAddress = felt!("0x1337").into(); + + let account = GenericContractInfo::default(); + cursor.upsert(key, account).expect(ERROR_UPSERT); + assert_eq!(cursor.set(key), Ok(Some((key, account)))); + + let account = GenericContractInfo { nonce: 1u8.into(), ..Default::default() }; + cursor.upsert(key, account).expect(ERROR_UPSERT); + assert_eq!(cursor.set(key), Ok(Some((key, account)))); + + let account = GenericContractInfo { nonce: 1u8.into(), ..Default::default() }; + cursor.upsert(key, account).expect(ERROR_UPSERT); + assert_eq!(cursor.set(key), Ok(Some((key, account)))); + + let mut dup_cursor = tx.cursor::().unwrap(); + let subkey = felt!("0x9"); + + let value = FieldElement::from(1u8); + let entry1 = StorageEntry { key: subkey, value }; + dup_cursor.upsert(key, entry1).expect(ERROR_UPSERT); + assert_eq!(dup_cursor.seek_by_key_subkey(key, subkey), Ok(Some(entry1))); + + let value = FieldElement::from(2u8); + let entry2 = StorageEntry { key: subkey, value }; + dup_cursor.upsert(key, entry2).expect(ERROR_UPSERT); + assert_eq!(dup_cursor.seek_by_key_subkey(key, subkey), Ok(Some(entry1))); + assert_eq!(dup_cursor.next_dup_val(), Ok(Some(entry2))); + } + + #[test] + fn db_cursor_walk() { + let env = create_test_db(DbEnvKind::RW); + + let value = Header::default(); + let key = 1u64; + + // PUT + let tx = env.tx_mut().expect(ERROR_INIT_TX); + tx.put::(key, value.clone()).expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + // Cursor + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().expect(ERROR_INIT_CURSOR); + + let first = cursor.first().unwrap(); + assert!(first.is_some(), "First should be our put"); + + // Walk + let walk = cursor.walk(Some(key)).unwrap(); + let first = walk.into_iter().next().unwrap().unwrap(); + assert_eq!(first.1, value, "First next should be put value"); + } + + #[test] + fn db_walker() { + let db = create_test_db(DbEnvKind::RW); + + // PUT (0, 0), (1, 0), (2, 0) + let tx = db.tx_mut().expect(ERROR_INIT_TX); + (0..3).try_for_each(|key| tx.put::(key, FieldElement::ZERO)).expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + let tx = db.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().expect(ERROR_INIT_CURSOR); + let mut walker = Walker::new(&mut cursor, None); + + assert_eq!(walker.next(), Some(Ok((0, FieldElement::ZERO)))); + assert_eq!(walker.next(), Some(Ok((1, FieldElement::ZERO)))); + assert_eq!(walker.next(), Some(Ok((2, FieldElement::ZERO)))); + assert_eq!(walker.next(), None); + } + + #[test] + fn db_cursor_insert() { + let db = create_test_db(DbEnvKind::RW); + + // PUT + let tx = db.tx_mut().expect(ERROR_INIT_TX); + (0..=4) + .try_for_each(|key| tx.put::(key, FieldElement::ZERO)) + .expect(ERROR_PUT); + tx.commit().expect(ERROR_COMMIT); + + let key_to_insert = 5; + let tx = db.tx_mut().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().expect(ERROR_INIT_CURSOR); + + // INSERT + assert_eq!(cursor.insert(key_to_insert, FieldElement::ZERO), Ok(())); + assert_eq!(cursor.current(), Ok(Some((key_to_insert, FieldElement::ZERO)))); + + // INSERT (failure) + assert_eq!( + cursor.insert(key_to_insert, FieldElement::ZERO), + Err(DatabaseError::Write { + table: BlockHashes::NAME, + error: libmdbx::Error::KeyExist, + key: Box::from(key_to_insert.encode()) + }) + ); + assert_eq!(cursor.current(), Ok(Some((key_to_insert, FieldElement::ZERO)))); + + tx.commit().expect(ERROR_COMMIT); + + // Confirm the result + let tx = db.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().expect(ERROR_INIT_CURSOR); + let res = cursor.walk(None).unwrap().map(|res| res.unwrap().0).collect::>(); + assert_eq!(res, vec![0, 1, 2, 3, 4, 5]); + tx.commit().expect(ERROR_COMMIT); + } + + #[test] + fn db_dup_sort() { + let env = create_test_db(DbEnvKind::RW); + let key = ContractAddress::from(felt!("0xa2c122be93b0074270ebee7f6b7292c7deb45047")); + + // PUT (0,0) + let value00 = StorageEntry::default(); + env.update(|tx| tx.put::(key, value00).expect(ERROR_PUT)).unwrap(); + + // PUT (2,2) + let value22 = StorageEntry { key: felt!("2"), value: felt!("2") }; + env.update(|tx| tx.put::(key, value22).expect(ERROR_PUT)).unwrap(); + + // // PUT (1,1) + let value11 = StorageEntry { key: felt!("1"), value: felt!("1") }; + env.update(|tx| tx.put::(key, value11).expect(ERROR_PUT)).unwrap(); + + // Iterate with cursor + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().unwrap(); + + // Notice that value11 and value22 have been ordered in the DB. + assert!(Some(value00) == cursor.next_dup_val().unwrap()); + assert!(Some(value11) == cursor.next_dup_val().unwrap()); + assert!(Some(value22) == cursor.next_dup_val().unwrap()); + } + + // Seek value with exact subkey + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor::().unwrap(); + let mut walker = cursor.walk_dup(Some(key), Some(felt!("1"))).unwrap(); + + assert_eq!( + (key, value11), + walker + .next() + .expect("element should exist.") + .expect("should be able to retrieve it.") + ); + } + } } diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index abd85129d3..dc59a8d1b0 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -7,7 +7,7 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -katana-db = { path = "../db" } +katana-db = { path = "../db", features = [ "test-utils" ] } katana-primitives = { path = "../../primitives" } anyhow.workspace = true diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 01c9c9f07d..4c4d3353f0 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -179,7 +179,7 @@ impl Future for Backend { } } -/// A thread safe handler to the [`ForkedBackend`]. This is the primary interface for sending +/// A thread safe handler to the [`Backend`]. This is the primary interface for sending /// request to the backend thread to fetch data from the forked provider. pub struct ForkedBackend(Mutex>); @@ -190,6 +190,9 @@ impl Clone for ForkedBackend { } impl ForkedBackend { + /// Create a new [`ForkedBackend`] with a dedicated backend thread. + /// + /// This method will spawn a new thread that will run the [`Backend`]. pub fn new_with_backend_thread( provider: Arc>, block_id: BlockHashOrNumber, diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 09d8c77985..04e9a59c6e 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -11,8 +11,7 @@ use katana_primitives::block::{ SealedBlockWithStatus, }; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, - SierraClass, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, SierraClass, }; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; @@ -29,7 +28,7 @@ use crate::traits::block::{ BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, HeaderProvider, }; -use crate::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use crate::traits::contract::ContractClassWriter; use crate::traits::state::{StateFactoryProvider, StateProvider, StateRootProvider, StateWriter}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ @@ -336,13 +335,6 @@ impl ReceiptProvider for ForkedProvider { } } -impl ContractInfoProvider for ForkedProvider { - fn contract(&self, address: ContractAddress) -> Result> { - let contract = self.state.contract_state.read().get(&address).cloned(); - Ok(contract) - } -} - impl StateRootProvider for ForkedProvider { fn state_root( &self, diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index 1f074f8831..7cb6e77cc1 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -11,8 +11,7 @@ use katana_primitives::block::{ SealedBlockWithStatus, }; use katana_primitives::contract::{ - ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, - SierraClass, + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, SierraClass, }; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; @@ -25,7 +24,7 @@ use crate::traits::block::{ BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, HeaderProvider, }; -use crate::traits::contract::{ContractClassWriter, ContractInfoProvider}; +use crate::traits::contract::ContractClassWriter; use crate::traits::state::{StateFactoryProvider, StateProvider, StateRootProvider, StateWriter}; use crate::traits::state_update::StateUpdateProvider; use crate::traits::transaction::{ @@ -333,13 +332,6 @@ impl ReceiptProvider for InMemoryProvider { } } -impl ContractInfoProvider for InMemoryProvider { - fn contract(&self, address: ContractAddress) -> Result> { - let contract = self.state.contract_state.read().get(&address).cloned(); - Ok(contract) - } -} - impl StateUpdateProvider for InMemoryProvider { fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { let block_num = match block_id { From e573f5e0a6982ba3def0a8739173fb3533f3c65e Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 20:32:06 -0500 Subject: [PATCH 147/192] Update devcontainer image hash: 630bd82 (#1291) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9c9fb7c50f..6d3d3fb386 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:7d49bcd", + "image": "ghcr.io/dojoengine/dojo-dev:630bd82", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7800c0023..47200e99a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-4-cores container: - image: ghcr.io/dojoengine/dojo-dev:a08f2cb + image: ghcr.io/dojoengine/dojo-dev:630bd82 steps: - uses: actions/checkout@v3 with: From 4be6e40dd246908957be982841a39bc344f6c008 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 21:12:20 -0500 Subject: [PATCH 148/192] Regenerate lockfile --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e55fcfb03d..4732f7ffd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1802,9 +1802,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fdbb015d790cfb378aca82caf9cc52a38be96a7eecdb92f31b4366a8afc019" +checksum = "3c90e95e5bd4e8ac34fa6f37c774b0c6f8ed06ea90c79931fd448fcf941a9767" dependencies = [ "clap", "log", @@ -1988,9 +1988,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" @@ -2838,9 +2838,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3286168faae03a0e583f6fde17c02c8b8bba2dcc2061d0f7817066e5b0af706" +checksum = "4adbf0983fe06bd3a5c19c8477a637c2389feb0994eca7a59e3b961054aa7c0a" dependencies = [ "serde", ] @@ -3213,9 +3213,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ "indenter", "once_cell", @@ -4155,9 +4155,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac23ed741583c792f573c028785db683496a6dfcd672ec701ee54ba6a77e1ff" +checksum = "3b2069adc212cf7f3317ef55f6444abd06c50f28479dbbac5a86acf3b05cbbfe" dependencies = [ "gix-actor", "gix-date", @@ -4619,11 +4619,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -9194,18 +9194,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", @@ -10791,18 +10791,18 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", From 54f063596d4450a078ea862714875fd505d1e51d Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 21:50:28 -0500 Subject: [PATCH 149/192] Add codecov preview to devcontainer (#1294) --- .devcontainer/Dockerfile | 8 ++++++++ .../src/manifest_test_data/compiler_cairo_v240/Scarb.lock | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4bab5248dc..f8542075ff 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,6 +2,8 @@ # [Choice] Debian OS version (use bookworm on local arm64/Apple Silicon): buster, bullseye, bookworm ARG VARIANT +ARG TARGETPLATFORM + FROM mcr.microsoft.com/vscode/devcontainers/rust:${VARIANT} # Install additional packages @@ -27,6 +29,12 @@ RUN rustup toolchain install nightly && \ rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ + rustup component add llvm-tools-preview --toolchain 1.70.0-aarch64-unknown-linux-gnu; \ + elif [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ + rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu; \ + fi + RUN curl -L https://install.dojoengine.org | bash RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash ENV PATH=${PATH}:/root/.dojo/bin diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock index 2d90330d2f..e9d8135cc3 100644 --- a/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo_v240/Scarb.lock @@ -10,7 +10,7 @@ dependencies = [ [[package]] name = "dojo" -version = "0.4.0-rc1" +version = "0.4.1" dependencies = [ "dojo_plugin", ] From a43ba061ee6e38b53cfb3224c2d1995fdfad4834 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Fri, 15 Dec 2023 22:01:50 -0500 Subject: [PATCH 150/192] Update devcontainer image hash: 54f0635 (#1295) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6d3d3fb386..ea62ddc04c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:630bd82", + "image": "ghcr.io/dojoengine/dojo-dev:54f0635", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47200e99a8..7e795d84e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-4-cores container: - image: ghcr.io/dojoengine/dojo-dev:630bd82 + image: ghcr.io/dojoengine/dojo-dev:54f0635 steps: - uses: actions/checkout@v3 with: From 570f2d0c74073366084c337398b212aa648bade6 Mon Sep 17 00:00:00 2001 From: glihm Date: Sun, 17 Dec 2023 15:25:36 -0600 Subject: [PATCH 151/192] fix(katana-core): add missing submodule file (#1298) fix: add missing submodule file --- crates/katana/core/contracts/messaging/solidity/.gitignore | 1 - crates/katana/core/contracts/messaging/solidity/lib/forge-std | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 160000 crates/katana/core/contracts/messaging/solidity/lib/forge-std diff --git a/crates/katana/core/contracts/messaging/solidity/.gitignore b/crates/katana/core/contracts/messaging/solidity/.gitignore index 921f19d973..e7bddf5b3f 100644 --- a/crates/katana/core/contracts/messaging/solidity/.gitignore +++ b/crates/katana/core/contracts/messaging/solidity/.gitignore @@ -2,7 +2,6 @@ cache/ out/ logs/ -lib/forge-std/ # Ignores development broadcast logs !/broadcast diff --git a/crates/katana/core/contracts/messaging/solidity/lib/forge-std b/crates/katana/core/contracts/messaging/solidity/lib/forge-std new file mode 160000 index 0000000000..2f11269750 --- /dev/null +++ b/crates/katana/core/contracts/messaging/solidity/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 From fc24a5a23584a847d2a69523671d4926dd899fe7 Mon Sep 17 00:00:00 2001 From: glihm Date: Sun, 17 Dec 2023 19:27:42 -0600 Subject: [PATCH 152/192] feat(katana-rpc): add ContractErrorData when it applies (#1297) * feat(katana-rpc): add ContractErrorData when it applies * fix: adjust StarknetApiError enum to include data * fix: clippy * fix: rework enum for StarknetApiError * fix: sort StarknetApiError code into ascending order * fix: group common fields at the bottom * fix: get StarknetApiError error fields before consuming it --- crates/katana/rpc/src/api/starknet.rs | 111 ++++++++++++++++++-------- crates/katana/rpc/src/starknet.rs | 12 ++- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index cefd0f1bcb..e619dc8156 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -16,73 +16,120 @@ use katana_rpc_types::transaction::{ DeclareTxResult, DeployAccountTxResult, InvokeTxResult, Tx, }; use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; -use starknet::core::types::TransactionStatus; +use starknet::core::types::{ContractErrorData, TransactionStatus}; -#[derive(thiserror::Error, Clone, Copy, Debug)] +#[derive(thiserror::Error, Clone, Debug)] +#[repr(i32)] pub enum StarknetApiError { #[error("Failed to write transaction")] - FailedToReceiveTxn = 1, + FailedToReceiveTxn, #[error("Contract not found")] - ContractNotFound = 20, + ContractNotFound, #[error("Invalid message selector")] - InvalidMessageSelector = 21, + InvalidMessageSelector, #[error("Invalid call data")] - InvalidCallData = 22, + InvalidCallData, #[error("Block not found")] - BlockNotFound = 24, + BlockNotFound, #[error("Transaction hash not found")] - TxnHashNotFound = 29, + TxnHashNotFound, #[error("Invalid transaction index in a block")] - InvalidTxnIndex = 27, + InvalidTxnIndex, #[error("Class hash not found")] - ClassHashNotFound = 28, + ClassHashNotFound, #[error("Requested page size is too big")] - PageSizeTooBig = 31, + PageSizeTooBig, #[error("There are no blocks")] - NoBlocks = 32, + NoBlocks, #[error("The supplied continuation token is invalid or unknown")] - InvalidContinuationToken = 33, + InvalidContinuationToken, #[error("Contract error")] - ContractError = 40, + ContractError { revert_error: String }, #[error("Invalid contract class")] - InvalidContractClass = 50, + InvalidContractClass, #[error("Class already declared")] - ClassAlreadyDeclared = 51, + ClassAlreadyDeclared, #[error("Invalid transaction nonce")] - InvalidTransactionNonce = 52, + InvalidTransactionNonce, #[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")] - InsufficientMaxFee = 53, + InsufficientMaxFee, #[error("Account balance is smaller than the transaction's max_fee")] - InsufficientAccountBalance = 54, + InsufficientAccountBalance, #[error("Account validation failed")] - ValidationFailure = 55, + ValidationFailure, #[error("Compilation failed")] - CompilationFailed = 56, + CompilationFailed, #[error("Contract class size is too large")] - ContractClassSizeIsTooLarge = 57, + ContractClassSizeIsTooLarge, #[error("Sender address in not an account contract")] - NonAccount = 58, + NonAccount, #[error("A transaction with the same hash already exists in the mempool")] - DuplicateTransaction = 59, + DuplicateTransaction, #[error("The compiled class hash did not match the one supplied in the transaction")] - CompiledClassHashMismatch = 60, + CompiledClassHashMismatch, #[error("The transaction version is not supported")] - UnsupportedTransactionVersion = 61, + UnsupportedTransactionVersion, #[error("The contract class version is not supported")] - UnsupportedContractClassVersion = 62, + UnsupportedContractClassVersion, #[error("An unexpected error occured")] - UnexpectedError = 63, + UnexpectedError, #[error("Too many storage keys requested")] - ProofLimitExceeded = 10000, + ProofLimitExceeded, #[error("Too many keys provided in a filter")] - TooManyKeysInFilter = 34, + TooManyKeysInFilter, #[error("Failed to fetch pending transactions")] - FailedToFetchPendingTransactions = 38, + FailedToFetchPendingTransactions, +} + +impl StarknetApiError { + fn code(&self) -> i32 { + match self { + StarknetApiError::FailedToReceiveTxn => 1, + StarknetApiError::ContractNotFound => 20, + StarknetApiError::InvalidMessageSelector => 21, + StarknetApiError::InvalidCallData => 22, + StarknetApiError::BlockNotFound => 24, + StarknetApiError::InvalidTxnIndex => 27, + StarknetApiError::ClassHashNotFound => 28, + StarknetApiError::TxnHashNotFound => 29, + StarknetApiError::PageSizeTooBig => 31, + StarknetApiError::NoBlocks => 32, + StarknetApiError::InvalidContinuationToken => 33, + StarknetApiError::TooManyKeysInFilter => 34, + StarknetApiError::FailedToFetchPendingTransactions => 38, + StarknetApiError::ContractError { .. } => 40, + StarknetApiError::InvalidContractClass => 50, + StarknetApiError::ClassAlreadyDeclared => 51, + StarknetApiError::InvalidTransactionNonce => 52, + StarknetApiError::InsufficientMaxFee => 53, + StarknetApiError::InsufficientAccountBalance => 54, + StarknetApiError::ValidationFailure => 55, + StarknetApiError::CompilationFailed => 56, + StarknetApiError::ContractClassSizeIsTooLarge => 57, + StarknetApiError::NonAccount => 58, + StarknetApiError::DuplicateTransaction => 59, + StarknetApiError::CompiledClassHashMismatch => 60, + StarknetApiError::UnsupportedTransactionVersion => 61, + StarknetApiError::UnsupportedContractClassVersion => 62, + StarknetApiError::UnexpectedError => 63, + StarknetApiError::ProofLimitExceeded => 10000, + } + } } impl From for Error { fn from(err: StarknetApiError) -> Self { - Error::Call(CallError::Custom(ErrorObject::owned(err as i32, err.to_string(), None::<()>))) + let code = err.code(); + let message = err.to_string(); + + let data = match err { + StarknetApiError::ContractError { revert_error } => { + Some(ContractErrorData { revert_error }) + } + _ => None, + }; + + Error::Call(CallError::Custom(ErrorObject::owned(code, message, data))) } } diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index 6e8ea78d0b..8d8d13ff1f 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -367,7 +367,9 @@ impl StarknetApiServer for StarknetApi { let res = self.sequencer.call(request, block_id).map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, - SequencerError::EntryPointExecution(_) => StarknetApiError::ContractError, + SequencerError::EntryPointExecution(e) => { + StarknetApiError::ContractError { revert_error: e.to_string() } + } _ => StarknetApiError::UnexpectedError, })?; @@ -449,7 +451,9 @@ impl StarknetApiServer for StarknetApi { let res = self.sequencer.estimate_fee(transactions, block_id).map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, + SequencerError::TransactionExecution(e) => { + StarknetApiError::ContractError { revert_error: e.to_string() } + } _ => StarknetApiError::UnexpectedError, })?; @@ -473,7 +477,9 @@ impl StarknetApiServer for StarknetApi { .estimate_fee(vec![tx], block_id) .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, + SequencerError::TransactionExecution(e) => { + StarknetApiError::ContractError { revert_error: e.to_string() } + } _ => StarknetApiError::UnexpectedError, })? .pop() From 1411e2a772b56cdf2174fbd2f97555aaf1f36ee9 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Mon, 18 Dec 2023 23:21:41 +0800 Subject: [PATCH 153/192] Benchmark contract class de/compression (#1292) * add bench for contract compression --- Cargo.lock | 121 + crates/katana/storage/db/Cargo.toml | 6 + .../db/benches/artifacts/dojo_world_240.json | 12613 ++++++++++++++++ crates/katana/storage/db/benches/codec.rs | 47 + 4 files changed, 12787 insertions(+) create mode 100644 crates/katana/storage/db/benches/artifacts/dojo_world_240.json create mode 100644 crates/katana/storage/db/benches/codec.rs diff --git a/Cargo.lock b/Cargo.lock index 4732f7ffd8..974617abcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.5" @@ -1729,6 +1735,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.83" @@ -1769,6 +1781,33 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -2102,6 +2141,42 @@ dependencies = [ "winapi", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits 0.2.17", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam" version = "0.8.2" @@ -4474,6 +4549,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "handlebars" version = "4.5.0" @@ -5380,7 +5461,9 @@ dependencies = [ "anyhow", "bincode 1.3.3", "blockifier", + "cairo-lang-starknet", "cairo-vm", + "criterion", "katana-primitives", "page_size", "parking_lot 0.12.1", @@ -6816,6 +6899,34 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits 0.2.17", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "2.8.0" @@ -9282,6 +9393,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index a0432e0fb0..75870a70fd 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -31,6 +31,8 @@ package = "reth-libmdbx" rev = "b34b0d3" [dev-dependencies] +cairo-lang-starknet.workspace = true +criterion = "0.5.1" starknet.workspace = true tempfile = "3.8.1" @@ -38,3 +40,7 @@ tempfile = "3.8.1" default = [ "postcard" ] postcard = [ "dep:postcard" ] test-utils = [ "dep:tempfile" ] + +[[bench]] +harness = false +name = "codec" diff --git a/crates/katana/storage/db/benches/artifacts/dojo_world_240.json b/crates/katana/storage/db/benches/artifacts/dojo_world_240.json new file mode 100644 index 0000000000..51587e589e --- /dev/null +++ b/crates/katana/storage/db/benches/artifacts/dojo_world_240.json @@ -0,0 +1,12613 @@ +{ + "sierra_program": [ + "0x1", + "0x4", + "0x0", + "0x2", + "0x4", + "0x0", + "0x8b0", + "0x750", + "0xe4", + "0x506564657273656e", + "0x800000000000000100000000000000000000000000000000", + "0x556e696e697469616c697a6564", + "0x800000000000000200000000000000000000000000000001", + "0x1", + "0x0", + "0x75313238", + "0x800000000000000700000000000000000000000000000000", + "0x537472756374", + "0x800000000000000700000000000000000000000000000003", + "0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3", + "0x2", + "0x7538", + "0x4e6f6e5a65726f", + "0x800000000000000700000000000000000000000000000001", + "0x4", + "0x800000000000000700000000000000000000000000000002", + "0x5", + "0x800000000000000f00000000000000000000000000000001", + "0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672", + "0x4172726179", + "0x800000000000000300000000000000000000000000000001", + "0x22", + "0x800000000000000300000000000000000000000000000003", + "0x7", + "0x8", + "0x456e756d", + "0x61021b87bfddaed5690a1345623b249d8c10a98a5a713f2a4a521d1567126f", + "0x6", + "0x9", + "0x25e2ca4b84968c2d8b83ef476ca8549410346b00836ce79beaf538155990bb2", + "0xb", + "0xc", + "0x2a5d3c8622e550c6ea23180508b4fc17d3289f39036e68ddad11bd6a05c4ae5", + "0xd", + "0x3288d594b9a45d15bb2fcb7903f06cdb06b27f0ba88186ec4cfaa98307cb972", + "0xf", + "0x10", + "0x3212619536443ad5ebedb7943a63ca0a270a26dd62320088e42057755d6b4b7", + "0x12", + "0x3490b45e9ebba149ad563aedee5ac231cce0b220754f2e0b3a3fb3075bae907", + "0x13", + "0x553132384d756c47756172616e746565", + "0x28edf843b90fd4464a9cf1779d01f8e7ce719fb66247954f300bf315f31bb23", + "0x17", + "0x38e5e97b4fd4b5ec8653ac59ee5e53c9a5f1b69275cba05f7228126a7004485", + "0x18", + "0x12867ecd09c884a5cf1f6d9eb0193b4695ce3bb3b2d796a8367d0c371f59cb2", + "0x1c", + "0x2ce4352eafa6073ab4ecf9445ae96214f99c2c33a29c01fcae68ba501d10e2c", + "0x536e617073686f74", + "0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62", + "0x20", + "0x66656c74323532", + "0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511", + "0x800000000000000700000000000000000000000000000005", + "0x21", + "0x23", + "0x6674d95cd1bd0dbe726032fc9199ab86923a7c44cdd22efabbc3bdeb4dd800", + "0x24", + "0x26", + "0x34c1a4ee6ef3ec231b7e21635f0ab0f5e73f747e42beb02d65fc54c8e0e0575", + "0x27", + "0x28", + "0xc048ae671041dedb3ca1f250ad42a27aeddf8a7f491e553e7f2a70ff2e1800", + "0x800000000000000300000000000000000000000000000005", + "0x148ac8a1a92f797fc0df7a766d21db68e3b15883eaacab5f2f750e883d74f3", + "0x2b", + "0x426f78", + "0x53ab85eada0a6ea028c03d62be3bee85e33846f2cb70861f36156d3c342647", + "0x2d", + "0x800000000000000300000000000000000000000000000007", + "0x366297339d1e0201b5a58c410076507621ab70e220d351d6614955cea9d16f6", + "0x30", + "0x2360086d8de14207bc705f7c51c3fc6bb6de6b826f1a4576e4db739d8b5edaf", + "0x32", + "0x34dec06b88f7854dce5a4288dde0eaff2dfb5f30900147a9c479e8300ae4ea0", + "0x34", + "0x1f5d91ca543c7f9a0585a1c8beffc7a207d4af73ee640223a154b1da196a40d", + "0x36", + "0x38", + "0x39", + "0x28f8d296e28032baef1f420f78ea9d933102ba47a50b1c5f80fc8a3a1041da", + "0x800000000000000300000000000000000000000000000004", + "0x3b", + "0x322c567022c4c08d2a5871dd773376f5aaadec29efd48cea7c33970c963c577", + "0x3c", + "0x14edfa2cfd8b5d67fab5028191e2fb4b5621cd19f55a2393ef2bfd5fa83988c", + "0x3e", + "0x949e9fd3b1ab222a7de8378c2d7759b5d9450bd4a5d812efdca682d9654cba", + "0x40", + "0x53746f726167654261736541646472657373", + "0x42", + "0x10a4ad544c3e0608b1a9e1ff69b5fdc230bace25740547273d3877854c8b722", + "0x6c", + "0xfeece2ea7edbbbebeeb5f270b77f64c680a68a089b794478dd9eca75e0196a", + "0x45", + "0x1166fe35572d4e7764dac0caf1fd7fc591901fd01156db2561a07b68ab8dca2", + "0x436f6e747261637441646472657373", + "0x1a99c1b614cd93625c69ae02a5553101decaee53ffa7db718e59eff185daf16", + "0x48", + "0x2451e06797647a3e95fc97c1e1f47212701fe5152a5120d4247f7dab43172c4", + "0x49", + "0x800000000000000300000000000000000000000000000002", + "0x4a", + "0x1122eaa6e4c0bd118679fcf107da7e4e36cc6b9bab8c946a30ecea3a78b4185", + "0x4b", + "0x2bcddf47e99bd2b365b672ce4ab3c5daf69bca568e14d0c1ccc9cee29ffaf43", + "0x4d", + "0x594b3f1a08277a00b497fbb1188a95b20ea7057b5de533ee96a550d3b80167", + "0x19d4b8cccf30503fa12e56f1e2d3f4febb93fbc007a09cd46d3020df9cd39ff", + "0x50", + "0x2a594b95e3522276fe0ac7ac7a7e4ad8c47eaa6223bc0fd6991aa683b7ee495", + "0x52", + "0x800000000000000700000000000000000000000000000004", + "0x753332", + "0x39a088813bcc109470bd475058810a7465bd632650a449e0ab3aee56f2e4e69", + "0x55", + "0x1289347a53bd537cb2be622dc3ef1bae97ae391de352ed7871b08a409f130a8", + "0x161ee0e6962e56453b5d68e09d1cabe5633858c1ba3a7e73fee8c70867eced0", + "0x436c61737348617368", + "0x14a7ddbb1150a2edc3d078a24d9dd07049784d38d10f9253fc3ece33c2f46a3", + "0x59", + "0x248e8fae2f16a35027771ffd74d6a6f3c379424b55843563a18f566bba3d905", + "0x104eb68e98232f2362ae8fd62c9465a5910d805fa88b305d1f7721b8727f04", + "0x5c", + "0x2b7d7ba3941973848ac6cf54700aef16585308fda69a60339f4fcd1909a664f", + "0x1208056def375849c0911a718ade519d5927c964cb7a78beb7d643e7a9d33ac", + "0x3fea2de5dea9ec02619bd14314e9e3ccb68732927bf526d266ecaadbe93d7f", + "0x1cbb1269d92ec36a7bf9fd3d23e2ad0b9f621a30a3f8f4f1993644a73dfbd0", + "0x98bcad38a1a94be3866c9d87e811e547fe33dd80c6f2176fe024ad550b9090", + "0x15fc3f2996a740deed181ceb9631ab6c546686707b024102a190759806ad46e", + "0x3e541896aa9b9130a72dc3eb093cae9289ec3be07dee8a8d5b9e571bc1f71c6", + "0x35448bc088c3a4485029bb4d2378b76f535f0302c7f3509ea17daeaeeb1e3a3", + "0x37a5fc2e601efdb2e85df52d91d2dae963e63eaabeab6b2184460ad25a00f2", + "0x223a83099e5edb29230e1cfd796cc57ce903e56ba7139eaed8d8349a81ca007", + "0x25aaa7227de9c717e2b86fab2ab1e3d8ee1a9fd1bcc5f636024c170fab865de", + "0x80000000000000070000000000000000000000000000000c", + "0x566a72c78d3c6621a5923e72ad255532a593e418cdab28b16b485bfd0d6c5e", + "0x5e", + "0x5f", + "0x60", + "0x61", + "0x62", + "0x63", + "0x64", + "0x65", + "0x66", + "0x67", + "0x68", + "0x6e", + "0x7a", + "0x800000000000000700000000000000000000000000000006", + "0x7d4d99e9ed8d285b5c61b493cedb63976bc3d9da867933d829f49ce838b5e7", + "0x6a", + "0x6b", + "0x753634", + "0x3808c701a5d13e100ab11b6c02f91f752ecae7e420d21b56c90ec0a475cc7e5", + "0x6d", + "0x2ca39cde64b91db1514d78c135ee79d71b3b57fffee52f1a3ef96618a34d8c8", + "0x6f", + "0x90d0203c41ad646d024845257a6eceb2f8b59b29ce7420dd518053d2edeedc", + "0x53746f7261676541646472657373", + "0xd2796e631062a6e7753698c5a7e289cd7bfa0c5bad46cd79c9a48fbab165aa", + "0x800000000000000f00000000000000000000000000000003", + "0x73", + "0x11210600628e948dc4cbd7a4c1a072850b384b77f05ac8982539ffdb74d676b", + "0x74", + "0x79", + "0x76", + "0x1597b831feeb60c71f259624b79cf66995ea4f7e383403583674ab9c33b9cec", + "0x77", + "0x3342418ef16b3e2799b906b1e4e89dbb9b111332dd44f72458ce44f9895b508", + "0x80000000000000070000000000000000000000000000000e", + "0x348a62b7a38c0673e61e888d83a3ac1bf334ee7361a8514593d3d9532ed8b39", + "0x78", + "0xa36a0a15af8cf1727a3a4fd9137671f23256b1f42299af56605a6910c522ce", + "0x7b", + "0x249125c42ab4a3dd2cca39221975297c741616320e73cf9c1c38606846766fa", + "0x7d", + "0x2219c07543986c5816cf8bd258df534e58f0c680e29dee0626e9be2a8854c6a", + "0x7e", + "0x1834d9f21285dab3ceae2a1d7c63c33d50a37dc56a931cf4fd0155cce48b722", + "0x80", + "0x2218e77a9d79bf2e592d48c40da370b545e224269ca2374c6281c9f50438541", + "0x4f", + "0x8d223e2f861f40c8e59a552d286b25ad43d4944c8215cd83f2af9dc8e75813", + "0x7da71e1dc546b96d9fd53438ce53f427347947c6c30c6495690af26972951f", + "0x84", + "0x172b2d029d59f97d93dd24b7cc98c01ca8efd7bf422afd18e9041d6a1a5c170", + "0x87", + "0x30f87c80a9ff91f3ba0997da70c24279680d81f2429f998f2964b1a555ebb1a", + "0x88", + "0x101dc0399934cc08fa0d6f6f2daead4e4a38cabeea1c743e1fc28d2d6e58e99", + "0x303117fb6f52227932d33a0574f22f70e3772c669c8fc05beadcb42e937ab71", + "0x8b", + "0x944a38f602dab9d32eb7dff5d1e279b127a3fcd64c78c5598d46d55a12a8c5", + "0x8c", + "0x3ab802bcce3a9ca953b0e1f31a5b29eb27a9b727c891e24300e1b5cc57387ba", + "0x8e", + "0xeafea52cfe71ec7c04decc186b7ba777db00e3392a885fbc3cefa21dc94638", + "0x91", + "0x525df73114b9ba6aedae0cfaac67628b09a84fea935dd481de14e22c9049da", + "0x92", + "0x242ab892b168865613d6bf48e23e6f2bf6bd4155b5adb58517a5ceeef69ebb", + "0x1f7c3c6dd5c14e521341ad5548817e5d1b9064f53c07f40114e072754c4f3f9", + "0x96", + "0x1203c3503955628887ebd724e5b8433579aa40dae9f37b31bfcf22e83dd73f0", + "0x97", + "0x9a", + "0x3ae40d407f8074730e48241717c3dd78b7128d346cf81094e31806a3a5bdf", + "0x9b", + "0x9d", + "0x16538afa8d67796a3f3ad75502c66231f14f82fce99a967cfaea2898dafbb", + "0x9f", + "0x2605bf00882f2fb47fd8f563bd0d1ba133e374a6860ea821465e1c5547e6f85", + "0xa0", + "0x30e3eb8f25604d564c2a8ee4a19405da626c3280586a18d89a2eafc5c9e86ce", + "0xa2", + "0x254bc03a4cb72e461fc4d3c101146f4423544989f203597f8044f96a19d2ea4", + "0xa3", + "0x3c93a24cd28b2b55d07633b11b14589ee9e0ec6c77fdf60576a04b17b045b26", + "0xa6", + "0x216ddce31c4b83af240278b16337bc80e15f78e1da60146aa3866fae1e8d9b1", + "0xa7", + "0x33cd6920cb494447f7ccaf0dbf3afce50039f3b0eb2a0369b285e06e8a31d7d", + "0xa9", + "0x27d6228f579b54e8a8059dc6c6a78c72c7e94e752c12c80bcba98e53610bcd2", + "0xab", + "0x10203be321c62a7bd4c060d69539c1fbe065baa9e253c74d2cc48be163e259", + "0xad", + "0x3e1934b18d91949ab9afdbdd1866a30ccca06c2b1e6581582c6b27f8b4f6555", + "0xb0", + "0x2975894f46cb99cad1bc7b4a8dac743f915530add47fff44edfb2fc60b6fced", + "0xb1", + "0x32ae48d6764693e076d982989640a356d60f77ede38bc686485a133d5845fda", + "0x19b9ae4ba181a54f9e7af894a81b44a60aea4c9803939708d6cc212759ee94c", + "0x42697477697365", + "0xb7", + "0x506f736569646f6e", + "0xb9", + "0x1285071ce26920dc861d902176f38b138552fe3ec227c3561fcaff97a2dd005", + "0xbb", + "0x119c26188880fdcee8029867a41d2495e669cebdd4311df3065292f4b52505c", + "0xbc", + "0x3b9ddf97bd58cc7301a2107c3eabad82196f38221c880cd3645d07c3aac1422", + "0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7", + "0x800000000000000f00000000000000000000000000000002", + "0xcc5e86243f861d2d64b08c35db21013e773ac5cf10097946fe0011304886d5", + "0xc2", + "0x17b6ecc31946835b0d9d92c2dd7a9c14f29af0371571ae74a1b228828b2242", + "0xc4", + "0x34f9bd7c6cb2dd4263175964ad75f1ff1461ddc332fbfb274e0fb2a5d7ab968", + "0xc5", + "0x800000000000000f00000000000000000000000000000008", + "0x3e74e36f022d24067823b338fa5223c9d6e4fb2d36a7b76deca37686422a908", + "0xc8", + "0x1a4b9d6a46239fb7607a1f85f5201d6c8446b5d0cd0c227a2e70e314c1a4176", + "0xc9", + "0x61e8b04c2d3e4e254d754b346ee4e01b07a0ea7bed1e8486d11b0361dda457", + "0xcb", + "0x387efa6e0840a32fa82f7c5edb3a29dbe8ee992b95bc65bef574a0617a4853", + "0xcd", + "0x224a57ba2504f0018c4fd92c0f0e6ef13a37bf8d3d6479b8faa16fc36f624d2", + "0xcf", + "0x11771f2d3e7dc3ed5afe7eae405dfd127619490dec57ceaa021ac8bc2b9b315", + "0xa853c166304d20fb0711becf2cbdf482dee3cac4e9717d040b7a7ab1df7eec", + "0xd2", + "0x3d37ad6eafb32512d2dd95a2917f6bf14858de22c27a1114392429f2e5c15d7", + "0x1fbbaba2608edc21a853642c7ca4b44f476d497f59dbacfc98e12f81eac5dd3", + "0xd5", + "0x1d49f7a4b277bf7b55a2664ce8cef5d6922b5ffb806b89644b9e0cdbbcac378", + "0xd7", + "0x13fdd7105045794a99550ae1c4ac13faa62610dfab62c16422bfcf5803baa6e", + "0xd8", + "0x74584e9f10ffb1a40aa5a3582e203f6758defc4a497d1a2d5a89f274a320e9", + "0xdb", + "0x4275696c74696e436f737473", + "0x53797374656d", + "0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6", + "0xdd", + "0x4761734275696c74696e", + "0x52616e6765436865636b", + "0xdf", + "0x411", + "0x616c6c6f635f6c6f63616c", + "0x66696e616c697a655f6c6f63616c73", + "0x7265766f6b655f61705f747261636b696e67", + "0x77697468647261775f676173", + "0x6272616e63685f616c69676e", + "0x73746f72655f74656d70", + "0x66756e6374696f6e5f63616c6c", + "0x3", + "0xe2", + "0x656e756d5f6d61746368", + "0x7374727563745f6465636f6e737472756374", + "0x61727261795f6c656e", + "0x736e617073686f745f74616b65", + "0x64726f70", + "0x7533325f636f6e7374", + "0x72656e616d65", + "0x7533325f6571", + "0xe3", + "0x61727261795f6e6577", + "0x66656c743235325f636f6e7374", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x61727261795f617070656e64", + "0x7374727563745f636f6e737472756374", + "0x656e756d5f696e6974", + "0xe0", + "0xe1", + "0x6765745f6275696c74696e5f636f737473", + "0xde", + "0x77697468647261775f6761735f616c6c", + "0x19", + "0x73746f72655f6c6f63616c", + "0x1a", + "0xdc", + "0x4f7574206f6620676173", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", + "0x1b", + "0xd9", + "0xd6", + "0x4661696c656420746f20646573657269616c697a6520706172616d202332", + "0xda", + "0x1d", + "0xd4", + "0x1e", + "0xd3", + "0x1f", + "0x25", + "0xd1", + "0xd0", + "0x29", + "0xce", + "0x2a", + "0xcc", + "0x2c", + "0xca", + "0x7533325f746f5f66656c74323532", + "0xc6", + "0x2e", + "0xc3", + "0xc7", + "0x656e61626c655f61705f747261636b696e67", + "0x61727261795f736e617073686f745f706f705f66726f6e74", + "0xbf", + "0x6a756d70", + "0x756e626f78", + "0x2f", + "0xbe", + "0x64697361626c655f61705f747261636b696e67", + "0xbd", + "0x31", + "0x4661696c656420746f20646573657269616c697a6520706172616d202335", + "0xc1", + "0x4661696c656420746f20646573657269616c697a6520706172616d202334", + "0xc0", + "0x4661696c656420746f20646573657269616c697a6520706172616d202333", + "0x33", + "0xb5", + "0xb8", + "0xba", + "0xb6", + "0x35", + "0xb3", + "0xb2", + "0x37", + "0xb4", + "0x3a", + "0xaf", + "0x3d", + "0x647570", + "0xae", + "0x66656c743235325f616464", + "0x3f", + "0xac", + "0x41", + "0x6e6f74206f776e6572", + "0xff", + "0x7533325f6f766572666c6f77696e675f737562", + "0x6d6574616461746120746f6f206c6f6e67", + "0x43", + "0xaa", + "0x44", + "0xa8", + "0x21adb5788e32c84f69a1863d85ef9394b7bf761a0ce1190f826984e5075c371", + "0xa5", + "0x46", + "0x47", + "0xa4", + "0x6e6f74206f776e6572206f7220777269746572", + "0xa1", + "0x4c", + "0x636c6173735f686173685f7472795f66726f6d5f66656c74323532", + "0x361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60", + "0x4e", + "0x9c", + "0x636c6173735f686173685f636f6e7374", + "0x636c6173735f686173685f746f5f66656c74323532", + "0x66656c743235325f737562", + "0x66656c743235325f69735f7a65726f", + "0x99", + "0x6f6e6c79206f776e65722063616e20757064617465", + "0x98", + "0x51", + "0x9e", + "0x6465706c6f795f73797363616c6c", + "0x94", + "0x53", + "0x93", + "0x90", + "0x54", + "0x636f6e74726163745f616464726573735f746f5f66656c74323532", + "0x95", + "0x56", + "0x57", + "0x58", + "0x8f", + "0x5a", + "0x8d", + "0x656d69745f6576656e745f73797363616c6c", + "0x8a", + "0x5b", + "0x75385f7472795f66726f6d5f66656c74323532", + "0x89", + "0x5d", + "0x85", + "0x75385f636f6e7374", + "0x86", + "0x7533325f7472795f66726f6d5f66656c74323532", + "0x83", + "0x517565726965732062792076616c756573206e6f7420696d706c", + "0x82", + "0x6f6e6c79206f776e65722063616e20736574206578656375746f72", + "0x81", + "0x69", + "0x7f", + "0x626f6f6c5f6e6f745f696d706c", + "0x696e76616c696420636c6173735f68617368", + "0x7c", + "0x6f6e6c79206f776e65722063616e2075706772616465", + "0x7265706c6163655f636c6173735f73797363616c6c", + "0x75", + "0x70", + "0x71", + "0x73746f726167655f616464726573735f66726f6d5f62617365", + "0x73746f726167655f726561645f73797363616c6c", + "0x72", + "0x73746f726167655f77726974655f73797363616c6c", + "0x626f6f6c5f746f5f66656c74323532", + "0x61727261795f676574", + "0x496e646578206f7574206f6620626f756e6473", + "0x4e6f6e20436c61737348617368", + "0x73746f726167655f626173655f616464726573735f636f6e7374", + "0x36d13f3f96a254c70c8330b71d2bd1ddec5e51f41977e0a2581409a42320efc", + "0xf2f7c15cbe06c8d94597cd91fd7f3369eae842359235712def5584f8d270cd", + "0x63616c6c5f636f6e74726163745f73797363616c6c", + "0x6e6f7420777269746572", + "0x2b1577440dd7bedf920cb6de2f9fc6bf7ba98c78c85a3fa1f8311aac95e1759", + "0x53746f7265553332202d206e6f6e20753332", + "0x7533325f6f766572666c6f77696e675f616464", + "0x7533325f616464204f766572666c6f77", + "0x646f6a6f5f73746f72616765", + "0x1e1a4f440a09e62552e8b84a50e697fb4320c97ca40a405efbfac1d14b300c2", + "0x526573756c743a3a756e77726170206661696c65642e", + "0x1cdc902ca80aea4fa41f94d35654158d368cd858e75e9f2426d204944eef764", + "0x706564657273656e", + "0xad292db4ff05a993c318438c1b6c8a8303266af2da151aa28ccece6726f1f1", + "0x6765745f657865637574696f6e5f696e666f5f76325f73797363616c6c", + "0x20f4a9eb90c382f623710b9c287e5c670ffc7e969860c063aa3ea54cf7325e7", + "0x26b160f10156dea0639bec90696772c640b9706a47f5b8c52ea1abe5858b34d", + "0x8248d60b6c95dac1563fa392e8dfc0cb968be73d80faedfe9a90e66811b45f", + "0x2399aa6e1326b07d2fd6c8c467f11b0fb5ed23db5f8723a777dca0f37d48bcf", + "0x290118457a640990dbcdeb696bd7f53f1d7d71d19b7d566efd42da398c908d3", + "0x2474f33346a1e5cfe0f6bedbc1facc0224efeb1be69ea9160b8dd8886a100dd", + "0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d", + "0x669efaa2192f54841ccb92b165625cca30ec440cfe380667975cb01226a100", + "0x22cf28ad915d4655dcd87526f5461f2c386e0d8ae93d31dff5725b4777cb549", + "0x1062404958dc8c1108761b486c079063d18e574094add29f4cecada8fed72d", + "0x22e066b6a70387f706dbbcab0d2a7e9090fca9489df0398d5ad334b26875acc", + "0x1cba22b2cafd524314ce673fe23829450404e65620365072db7e950762157aa", + "0x11d1dfbcd3e2040f863a35b6bab2a2134c9c4423d1eb9352c6e60807dafff85", + "0x31a75a0d711dfe3639aae96eb8f9facc2fd74df5aa611067f2511cc9fefc229", + "0x52657475726e6564206461746120746f6f2073686f7274", + "0x29c799c764f5d22f9cdca7532b4515f2506acb3d56008546d9cc1994a04373", + "0x68616465735f7065726d75746174696f6e", + "0x4e6f6e20436f6e747261637441646472657373", + "0xa", + "0x75385f746f5f66656c74323532", + "0x6d69736d61746368656420696e707574206c656e73", + "0x61727261795f706f705f66726f6e74", + "0x2679d68052ccd03a53755ca9169677965fbd93e489df62f5f40d4f03c24f7a4", + "0x646f6a6f5f696e6465785f6c656e73", + "0x646f6a6f5f696e6465785f696473", + "0x646f6a6f5f696e6465786573", + "0x757063617374", + "0xfb", + "0x646f6a6f5f696e6465785f6b65795f6c656e", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x75385f6f766572666c6f77696e675f616464", + "0x75385f616464204f766572666c6f77", + "0x646f6a6f5f696e6465785f6b6579", + "0x75385f6f766572666c6f77696e675f737562", + "0x62697477697365", + "0x753132385f636f6e7374", + "0x75385f6571", + "0x75385f737562204f766572666c6f77", + "0x75313238735f66726f6d5f66656c74323532", + "0x8000000000000110000000000000000", + "0x753132385f6f766572666c6f77696e675f737562", + "0x753132385f6571", + "0x753132385f746f5f66656c74323532", + "0x100000000000000000000000000000000", + "0x66656c743235325f6d756c", + "0x753235365f737562204f766572666c6f77", + "0x753235365f6d756c204f766572666c6f77", + "0x753235365f736166655f6469766d6f64", + "0x16", + "0x15", + "0x4469766973696f6e2062792030", + "0x14", + "0x75385f736166655f6469766d6f64", + "0x11", + "0xe", + "0x753132385f6d756c5f67756172616e7465655f766572696679", + "0x753132385f67756172616e7465655f6d756c", + "0x753132385f6f766572666c6f77696e675f616464", + "0x753235362069732030", + "0x75382069732030", + "0x753235365f69735f7a65726f", + "0x75385f69735f7a65726f", + "0x39f1", + "0xffffffffffffffff", + "0x131", + "0x120", + "0x117", + "0x106", + "0xf6", + "0xee", + "0x1cc", + "0x1bc", + "0x1ab", + "0x168", + "0x19b", + "0x193", + "0x25e", + "0x24e", + "0x23d", + "0x202", + "0x22d", + "0x225", + "0x2f0", + "0x2e0", + "0x2cf", + "0x294", + "0x2bf", + "0x2b7", + "0x38b", + "0x37b", + "0x36a", + "0x327", + "0x35a", + "0x352", + "0x41e", + "0x40e", + "0x3fd", + "0x3c2", + "0x3ed", + "0x3e5", + "0x4b1", + "0x4a1", + "0x490", + "0x455", + "0x480", + "0x478", + "0x52b", + "0x51b", + "0x4e2", + "0x50c", + "0x504", + "0x5ad", + "0x59d", + "0x55c", + "0x58e", + "0x586", + "0x647", + "0x637", + "0x626", + "0x5e4", + "0x616", + "0x60e", + "0x6e1", + "0x6d1", + "0x6c0", + "0x67e", + "0x6b0", + "0x6a8", + "0x745", + "0x70c", + "0x738", + "0x731", + "0x7ee", + "0x7e6", + "0x7d6", + "0x7ce", + "0x7be", + "0x785", + "0x7af", + "0x7a8", + "0x94d", + "0x937", + "0x929", + "0x913", + "0x81d", + "0x822", + "0x8fb", + "0x8f2", + "0x8e4", + "0x8ce", + "0x8c0", + "0x8aa", + "0x861", + "0x895", + "0x88b", + "0x903", + "0xa38", + "0xa24", + "0xa18", + "0xa04", + "0x9f8", + "0x9e4", + "0x99f", + "0x9d1", + "0x9c7", + "0xbd7", + "0xbbf", + "0xbaf", + "0xb97", + "0xa6e", + "0xa73", + "0xb7d", + "0xb71", + "0xa86", + "0xa8b", + "0xb57", + "0xb4b", + "0xb3b", + "0xb23", + "0xac2", + "0xb0c", + "0xb03", + "0xafa", + "0xb62", + "0xb88", + "0xd59", + "0xd41", + "0xd29", + "0xd19", + "0xd01", + "0xc16", + "0xc1b", + "0xce7", + "0xcdb", + "0xccb", + "0xcb3", + "0xc52", + "0xc9c", + "0xc93", + "0xc8a", + "0xcf2", + "0xdfb", + "0xde9", + "0xd97", + "0xdd8", + "0xdd0", + "0xdc8", + "0xe77", + "0xe67", + "0xe2e", + "0xe58", + "0xe50", + "0xedd", + "0xea2", + "0xed0", + "0xec9", + "0xf42", + "0xf07", + "0xf35", + "0xf2e", + "0xfbb", + "0xfab", + "0xf72", + "0xf9c", + "0xf94", + "0x104e", + "0x103e", + "0x102d", + "0xff2", + "0x101d", + "0x1015", + "0x1064", + "0x1069", + "0x1073", + "0x10b2", + "0x10aa", + "0x10d7", + "0x10e5", + "0x10ea", + "0x1114", + "0x110e", + "0x1106", + "0x11d1", + "0x11c5", + "0x1147", + "0xe5", + "0xe6", + "0xe7", + "0xe8", + "0xe9", + "0x1165", + "0xea", + "0xeb", + "0xec", + "0x11b5", + "0xed", + "0xef", + "0xf0", + "0x11ab", + "0xf1", + "0xf2", + "0xf3", + "0x119d", + "0xf4", + "0xf5", + "0x11f0", + "0xf7", + "0x11e9", + "0xf8", + "0xf9", + "0xfa", + "0xfc", + "0xfd", + "0xfe", + "0x120f", + "0x100", + "0x101", + "0x102", + "0x103", + "0x121e", + "0x104", + "0x1222", + "0x105", + "0x12cf", + "0x107", + "0x12c2", + "0x1261", + "0x1255", + "0x108", + "0x126c", + "0x109", + "0x1280", + "0x10a", + "0x10b", + "0x12b2", + "0x10c", + "0x10d", + "0x10e", + "0x10f", + "0x12aa", + "0x1381", + "0x1374", + "0x1313", + "0x1307", + "0x131e", + "0x1332", + "0x110", + "0x1364", + "0x135c", + "0x111", + "0x13a6", + "0x1454", + "0x1447", + "0x13e6", + "0x13da", + "0x13f1", + "0x1405", + "0x112", + "0x113", + "0x114", + "0x1437", + "0x115", + "0x116", + "0x118", + "0x142f", + "0x1538", + "0x152b", + "0x149c", + "0x148e", + "0x14a6", + "0x14c9", + "0x14bd", + "0x14d5", + "0x14e9", + "0x151b", + "0x1513", + "0x1556", + "0x119", + "0x154f", + "0x11a", + "0x11b", + "0x11c", + "0x11d", + "0x1678", + "0x11e", + "0x11f", + "0x121", + "0x166c", + "0x122", + "0x123", + "0x124", + "0x1660", + "0x125", + "0x126", + "0x127", + "0x128", + "0x164c", + "0x129", + "0x12a", + "0x12b", + "0x12c", + "0x15d2", + "0x15bf", + "0x1600", + "0x12d", + "0x163e", + "0x15f8", + "0x12e", + "0x12f", + "0x130", + "0x162c", + "0x132", + "0x133", + "0x134", + "0x1624", + "0x135", + "0x136", + "0x169c", + "0x137", + "0x138", + "0x139", + "0x13a", + "0x13b", + "0x13c", + "0x13d", + "0x1767", + "0x13e", + "0x16cd", + "0x13f", + "0x140", + "0x141", + "0x16d2", + "0x142", + "0x143", + "0x144", + "0x1753", + "0x145", + "0x146", + "0x147", + "0x148", + "0x149", + "0x14a", + "0x173f", + "0x14b", + "0x172a", + "0x1719", + "0x14c", + "0x14d", + "0x14e", + "0x1710", + "0x14f", + "0x150", + "0x151", + "0x152", + "0x153", + "0x154", + "0x17d8", + "0x155", + "0x17cd", + "0x17c2", + "0x156", + "0x157", + "0x158", + "0x17b9", + "0x159", + "0x15a", + "0x15b", + "0x15c", + "0x15d", + "0x15e", + "0x15f", + "0x1825", + "0x160", + "0x161", + "0x162", + "0x1816", + "0x163", + "0x164", + "0x1808", + "0x165", + "0x166", + "0x167", + "0x169", + "0x183b", + "0x1840", + "0x185c", + "0x1856", + "0x16a", + "0x16b", + "0x16c", + "0x16d", + "0x16e", + "0x1895", + "0x16f", + "0x187f", + "0x170", + "0x171", + "0x1884", + "0x172", + "0x173", + "0x188f", + "0x174", + "0x175", + "0x176", + "0x177", + "0x178", + "0x18a3", + "0x179", + "0x17a", + "0x18a8", + "0x17b", + "0x18b1", + "0x18b6", + "0x18e0", + "0x17c", + "0x17d", + "0x17e", + "0x17f", + "0x18da", + "0x180", + "0x181", + "0x18d2", + "0x182", + "0x183", + "0x184", + "0x185", + "0x186", + "0x187", + "0x188", + "0x189", + "0x18a", + "0x1972", + "0x1960", + "0x18b", + "0x194e", + "0x18c", + "0x18d", + "0x193f", + "0x18e", + "0x18f", + "0x190", + "0x1935", + "0x191", + "0x1a8d", + "0x1a79", + "0x1a61", + "0x192", + "0x194", + "0x195", + "0x1a49", + "0x196", + "0x1a31", + "0x197", + "0x198", + "0x1a1c", + "0x199", + "0x1a09", + "0x19a", + "0x19c", + "0x19ff", + "0x19d", + "0x19e", + "0x19f", + "0x1aa8", + "0x1a0", + "0x1a1", + "0x1aad", + "0x1a2", + "0x1ad8", + "0x1a3", + "0x1acf", + "0x1b18", + "0x1afa", + "0x1af5", + "0x1b08", + "0x1a4", + "0x1a5", + "0x1b11", + "0x1a6", + "0x1a7", + "0x1b0c", + "0x1a8", + "0x1b3d", + "0x1a9", + "0x1aa", + "0x1ac", + "0x1ad", + "0x1ae", + "0x1b56", + "0x1af", + "0x1b0", + "0x1b1", + "0x1b7e", + "0x1b2", + "0x1b3", + "0x1b78", + "0x1b4", + "0x1b5", + "0x1b9b", + "0x1c27", + "0x1c1d", + "0x1bcb", + "0x1b6", + "0x1b7", + "0x1b8", + "0x1b9", + "0x1c0d", + "0x1ba", + "0x1bb", + "0x1bd", + "0x1bfd", + "0x1be", + "0x1bf", + "0x1c0", + "0x1c1", + "0x1c2", + "0x1c3", + "0x1bf5", + "0x1c4", + "0x1c49", + "0x1c5", + "0x1c6", + "0x1c7", + "0x1c8", + "0x1c65", + "0x1c86", + "0x1c8b", + "0x1c9", + "0x1ca0", + "0x1ca", + "0x1cb", + "0x1d1b", + "0x1cd", + "0x1ce", + "0x1cf", + "0x1d0", + "0x1d1", + "0x1d11", + "0x1cd5", + "0x1d2", + "0x1d3", + "0x1ce1", + "0x1ce6", + "0x1d4", + "0x1d06", + "0x1d5", + "0x1d6", + "0x1d7", + "0x1cfe", + "0x1dc4", + "0x1d8", + "0x1db3", + "0x1d9", + "0x1da", + "0x1da3", + "0x1d94", + "0x1db", + "0x1d84", + "0x1dc", + "0x1dd", + "0x1de", + "0x1df", + "0x1d7c", + "0x1e0", + "0x1e1", + "0x1e2", + "0x1e3", + "0x1e4", + "0x1e5", + "0x1e6", + "0x1dde", + "0x1e7", + "0x1e8", + "0x1de3", + "0x1e9", + "0x1ea", + "0x1def", + "0x1eb", + "0x1ec", + "0x1ed", + "0x1ee", + "0x1e4c", + "0x1e0e", + "0x1ef", + "0x1f0", + "0x1f1", + "0x1e40", + "0x1e38", + "0x1f2", + "0x1e9b", + "0x1e6c", + "0x1e71", + "0x1e8d", + "0x1e87", + "0x1e94", + "0x1ee2", + "0x1eba", + "0x1f3", + "0x1ed8", + "0x1ed2", + "0x1f4", + "0x1f5", + "0x1f03", + "0x1f6", + "0x1f7", + "0x1f8", + "0x1f9", + "0x1fa", + "0x1fb", + "0x1fc", + "0x1f19", + "0x1f1e", + "0x1f2a", + "0x1fd", + "0x1fe", + "0x1ff", + "0x200", + "0x201", + "0x203", + "0x204", + "0x205", + "0x206", + "0x207", + "0x1f6f", + "0x208", + "0x1f55", + "0x1f5a", + "0x1f66", + "0x1fcf", + "0x1f85", + "0x1f8a", + "0x1fc3", + "0x1fb9", + "0x1fb1", + "0x209", + "0x20a", + "0x20b", + "0x20c", + "0x20d", + "0x200c", + "0x2001", + "0x2006", + "0x20e", + "0x20f", + "0x2011", + "0x210", + "0x211", + "0x201d", + "0x212", + "0x213", + "0x203b", + "0x2040", + "0x204c", + "0x214", + "0x215", + "0x216", + "0x217", + "0x218", + "0x2091", + "0x2077", + "0x207c", + "0x2088", + "0x219", + "0x20c5", + "0x20ba", + "0x20bf", + "0x20ca", + "0x20d6", + "0x21a", + "0x20ef", + "0x20f4", + "0x2100", + "0x21b", + "0x21c", + "0x21d", + "0x21e", + "0x21f", + "0x2145", + "0x212b", + "0x2130", + "0x213c", + "0x2175", + "0x220", + "0x216e", + "0x221", + "0x2186", + "0x222", + "0x223", + "0x224", + "0x226", + "0x227", + "0x21b6", + "0x228", + "0x21a7", + "0x229", + "0x22a", + "0x21bc", + "0x22b", + "0x22c", + "0x21c8", + "0x21e1", + "0x21e6", + "0x21f2", + "0x22e", + "0x22f", + "0x230", + "0x231", + "0x232", + "0x2237", + "0x221d", + "0x2222", + "0x222e", + "0x233", + "0x234", + "0x2262", + "0x2254", + "0x2268", + "0x2273", + "0x235", + "0x2280", + "0x236", + "0x237", + "0x238", + "0x239", + "0x23a", + "0x23b", + "0x23c", + "0x229b", + "0x23e", + "0x22a0", + "0x23f", + "0x240", + "0x22ab", + "0x241", + "0x242", + "0x22ed", + "0x22d3", + "0x22d8", + "0x22e4", + "0x2399", + "0x2388", + "0x237e", + "0x2375", + "0x236d", + "0x2364", + "0x235c", + "0x2355", + "0x243", + "0x2394", + "0x2390", + "0x244", + "0x245", + "0x246", + "0x247", + "0x248", + "0x23df", + "0x23c5", + "0x23ca", + "0x23d6", + "0x249", + "0x240a", + "0x23fc", + "0x24a", + "0x24b", + "0x2410", + "0x24c", + "0x24d", + "0x24f", + "0x250", + "0x241b", + "0x251", + "0x252", + "0x253", + "0x2428", + "0x254", + "0x255", + "0x242c", + "0x256", + "0x257", + "0x258", + "0x2437", + "0x244c", + "0x2451", + "0x245b", + "0x259", + "0x25a", + "0x25b", + "0x25c", + "0x25d", + "0x2468", + "0x24c6", + "0x247f", + "0x25f", + "0x260", + "0x261", + "0x2489", + "0x248e", + "0x24b8", + "0x24b2", + "0x262", + "0x24ac", + "0x263", + "0x264", + "0x24bf", + "0x265", + "0x266", + "0x267", + "0x268", + "0x269", + "0x24ed", + "0x26a", + "0x26b", + "0x26c", + "0x2517", + "0x26d", + "0x255c", + "0x2542", + "0x2547", + "0x2553", + "0x25a7", + "0x26e", + "0x26f", + "0x259e", + "0x2596", + "0x2590", + "0x270", + "0x271", + "0x272", + "0x273", + "0x274", + "0x25cc", + "0x275", + "0x2610", + "0x25f6", + "0x25fb", + "0x2607", + "0x276", + "0x263a", + "0x277", + "0x278", + "0x2679", + "0x279", + "0x27a", + "0x266f", + "0x27b", + "0x27c", + "0x27d", + "0x27e", + "0x27f", + "0x280", + "0x281", + "0x26a0", + "0x282", + "0x26e8", + "0x283", + "0x26df", + "0x284", + "0x285", + "0x26d7", + "0x286", + "0x26fd", + "0x287", + "0x2705", + "0x288", + "0x289", + "0x28a", + "0x28b", + "0x272c", + "0x28c", + "0x28d", + "0x28e", + "0x2725", + "0x28f", + "0x290", + "0x291", + "0x292", + "0x293", + "0x2747", + "0x295", + "0x296", + "0x297", + "0x298", + "0x278a", + "0x2770", + "0x2775", + "0x2781", + "0x27a4", + "0x299", + "0x29a", + "0x29b", + "0x29c", + "0x29d", + "0x29e", + "0x27b5", + "0x29f", + "0x27f5", + "0x27db", + "0x27e0", + "0x27ec", + "0x280d", + "0x2812", + "0x281c", + "0x2a0", + "0x2a1", + "0x2a2", + "0x2a3", + "0x2835", + "0x2a4", + "0x2877", + "0x285d", + "0x2862", + "0x286e", + "0x2a5", + "0x2a6", + "0x2a7", + "0x2a8", + "0x2a9", + "0x2aa", + "0x2893", + "0x2ab", + "0x28a0", + "0x2ac", + "0x2ad", + "0x28a5", + "0x2ae", + "0x2af", + "0x28af", + "0x2b0", + "0x2b1", + "0x2b2", + "0x2b3", + "0x2b4", + "0x2b5", + "0x28c8", + "0x28d6", + "0x28e4", + "0x28f2", + "0x290b", + "0x2919", + "0x2932", + "0x294b", + "0x2959", + "0x2967", + "0x2b6", + "0x2974", + "0x2b8", + "0x2b9", + "0x2ba", + "0x2bb", + "0x2bc", + "0x2bd", + "0x2be", + "0x2905", + "0x2c0", + "0x2c1", + "0x2c2", + "0x2c3", + "0x2c4", + "0x2c5", + "0x292c", + "0x2c6", + "0x2c7", + "0x2945", + "0x2c8", + "0x2c9", + "0x2ca", + "0x2cb", + "0x2cc", + "0x2cd", + "0x2ce", + "0x2d0", + "0x2d1", + "0x2d2", + "0x2995", + "0x2d3", + "0x2d4", + "0x2d5", + "0x2a18", + "0x2d6", + "0x29df", + "0x29e4", + "0x2a10", + "0x2a09", + "0x29fb", + "0x2d7", + "0x2d8", + "0x2d9", + "0x2a35", + "0x2a3a", + "0x2da", + "0x2a41", + "0x2db", + "0x2dc", + "0x2a50", + "0x2dd", + "0x2de", + "0x2df", + "0x2a62", + "0x2a6f", + "0x2b32", + "0x2e1", + "0x2a87", + "0x2a8c", + "0x2b09", + "0x2a9a", + "0x2a9f", + "0x2adf", + "0x2e2", + "0x2e3", + "0x2acc", + "0x2ac5", + "0x2e4", + "0x2e5", + "0x2e6", + "0x2e7", + "0x2e8", + "0x2e9", + "0x2ea", + "0x2af7", + "0x2b01", + "0x2b20", + "0x2b2a", + "0x2eb", + "0x2ec", + "0x2b8b", + "0x2ed", + "0x2ee", + "0x2ef", + "0x2b7f", + "0x2f1", + "0x2f2", + "0x2b76", + "0x2f3", + "0x2f4", + "0x2f5", + "0x2f6", + "0x2f7", + "0x2c76", + "0x2bc4", + "0x2f8", + "0x2f9", + "0x2c67", + "0x2fa", + "0x2c59", + "0x2fb", + "0x2c4c", + "0x2c40", + "0x2fc", + "0x2c35", + "0x2c2d", + "0x2fd", + "0x2cd6", + "0x2fe", + "0x2ccc", + "0x2ff", + "0x300", + "0x2cc3", + "0x301", + "0x302", + "0x303", + "0x304", + "0x2cff", + "0x305", + "0x306", + "0x307", + "0x308", + "0x309", + "0x30a", + "0x30b", + "0x2d54", + "0x30c", + "0x2d18", + "0x30d", + "0x30e", + "0x30f", + "0x2d1d", + "0x310", + "0x311", + "0x312", + "0x2d46", + "0x313", + "0x2d3f", + "0x314", + "0x2d39", + "0x2d4d", + "0x315", + "0x316", + "0x317", + "0x2d99", + "0x318", + "0x2d91", + "0x319", + "0x31a", + "0x31b", + "0x31c", + "0x2e09", + "0x2dfe", + "0x2df3", + "0x31d", + "0x2deb", + "0x2e2f", + "0x2e42", + "0x31e", + "0x2e3a", + "0x2e6e", + "0x31f", + "0x2e60", + "0x320", + "0x321", + "0x322", + "0x323", + "0x324", + "0x325", + "0x326", + "0x2e7d", + "0x2e8e", + "0x328", + "0x2e9e", + "0x329", + "0x32a", + "0x32b", + "0x2eb3", + "0x32c", + "0x32d", + "0x32e", + "0x32f", + "0x330", + "0x331", + "0x332", + "0x333", + "0x334", + "0x2f19", + "0x335", + "0x336", + "0x337", + "0x338", + "0x2f76", + "0x339", + "0x33a", + "0x2f6f", + "0x33b", + "0x33c", + "0x33d", + "0x2f98", + "0x33e", + "0x33f", + "0x340", + "0x341", + "0x342", + "0x343", + "0x3006", + "0x344", + "0x345", + "0x346", + "0x347", + "0x348", + "0x3020", + "0x349", + "0x34a", + "0x34b", + "0x3084", + "0x34c", + "0x3035", + "0x303a", + "0x3077", + "0x34d", + "0x34e", + "0x3046", + "0x304a", + "0x34f", + "0x350", + "0x351", + "0x306c", + "0x3065", + "0x353", + "0x354", + "0x355", + "0x356", + "0x30c8", + "0x30b9", + "0x30be", + "0x357", + "0x3109", + "0x30ee", + "0x30f3", + "0x3100", + "0x358", + "0x313d", + "0x312f", + "0x3133", + "0x359", + "0x31c0", + "0x31b4", + "0x316e", + "0x3173", + "0x31a7", + "0x319c", + "0x3195", + "0x35b", + "0x35c", + "0x35d", + "0x31ea", + "0x35e", + "0x326a", + "0x3201", + "0x3206", + "0x3259", + "0x324b", + "0x35f", + "0x3242", + "0x360", + "0x361", + "0x362", + "0x363", + "0x364", + "0x365", + "0x32e6", + "0x32a3", + "0x366", + "0x32d8", + "0x32d0", + "0x3350", + "0x3313", + "0x3343", + "0x333b", + "0x33ba", + "0x337b", + "0x33ad", + "0x33a5", + "0x33d3", + "0x33d8", + "0x367", + "0x3459", + "0x33ea", + "0x33ef", + "0x344d", + "0x368", + "0x33fc", + "0x369", + "0x36b", + "0x3401", + "0x36c", + "0x36d", + "0x36e", + "0x3438", + "0x36f", + "0x370", + "0x371", + "0x342f", + "0x372", + "0x3428", + "0x373", + "0x374", + "0x375", + "0x376", + "0x377", + "0x378", + "0x379", + "0x37a", + "0x3473", + "0x37c", + "0x3477", + "0x37d", + "0x37e", + "0x37f", + "0x3482", + "0x380", + "0x381", + "0x382", + "0x383", + "0x384", + "0x34e9", + "0x3497", + "0x349c", + "0x385", + "0x34de", + "0x386", + "0x387", + "0x34d5", + "0x388", + "0x34cb", + "0x34c4", + "0x389", + "0x38a", + "0x38c", + "0x38d", + "0x38e", + "0x38f", + "0x390", + "0x357c", + "0x391", + "0x392", + "0x356e", + "0x393", + "0x394", + "0x395", + "0x396", + "0x397", + "0x3563", + "0x398", + "0x399", + "0x39a", + "0x39b", + "0x39c", + "0x3552", + "0x3549", + "0x39d", + "0x39e", + "0x39f", + "0x3a0", + "0x3a1", + "0x3a2", + "0x3a3", + "0x3590", + "0x3a4", + "0x36ab", + "0x3625", + "0x3a5", + "0x3a6", + "0x3618", + "0x3a7", + "0x360b", + "0x3a8", + "0x3a9", + "0x35ff", + "0x3aa", + "0x35f4", + "0x3ab", + "0x3ac", + "0x3ad", + "0x3ae", + "0x3af", + "0x3b0", + "0x3632", + "0x3637", + "0x369c", + "0x3b1", + "0x3b2", + "0x368e", + "0x3683", + "0x3678", + "0x36c1", + "0x36c5", + "0x3b3", + "0x36d0", + "0x3b4", + "0x36de", + "0x36e2", + "0x3b5", + "0x3b6", + "0x3702", + "0x3b7", + "0x36fc", + "0x3b8", + "0x3b9", + "0x3ba", + "0x3bb", + "0x3bc", + "0x3bd", + "0x3be", + "0x373d", + "0x3bf", + "0x3c0", + "0x3c1", + "0x371b", + "0x3733", + "0x3731", + "0x3c3", + "0x3c4", + "0x3c5", + "0x3c6", + "0x3752", + "0x3c7", + "0x377d", + "0x3c8", + "0x3777", + "0x3815", + "0x3809", + "0x3801", + "0x3c9", + "0x37f9", + "0x3ca", + "0x3cb", + "0x37f0", + "0x37e8", + "0x3cc", + "0x37e0", + "0x37cc", + "0x37da", + "0x3cd", + "0x382e", + "0x3ce", + "0x3cf", + "0x3d0", + "0x3845", + "0x3d1", + "0x3d2", + "0x384b", + "0x3d3", + "0x3d4", + "0x3d5", + "0x386e", + "0x3d6", + "0x3d7", + "0x3861", + "0x3d8", + "0x3d9", + "0x3da", + "0x3db", + "0x3dc", + "0x3dd", + "0x3de", + "0x3df", + "0x388f", + "0x3e0", + "0x3e1", + "0x3882", + "0x3e2", + "0x38b0", + "0x38a3", + "0x3e3", + "0x38c1", + "0x38c7", + "0x38d4", + "0x3e4", + "0x38da", + "0x3e6", + "0x38e2", + "0x3e7", + "0x3e8", + "0x38f6", + "0x38ec", + "0x38f4", + "0x3e9", + "0x3ea", + "0x3eb", + "0x3901", + "0x3ec", + "0x3ee", + "0x3ef", + "0x3f0", + "0x3f1", + "0x3f2", + "0x3f3", + "0x3f4", + "0x3913", + "0x3f5", + "0x3f6", + "0x3f7", + "0x3f8", + "0x3f9", + "0x3fa", + "0x3fb", + "0x3fc", + "0x3fe", + "0x3999", + "0x394e", + "0x3991", + "0x3962", + "0x3966", + "0x398d", + "0x3976", + "0x398a", + "0x3982", + "0x3988", + "0x3995", + "0x39a3", + "0x39aa", + "0x39b1", + "0x3ff", + "0x39bf", + "0x400", + "0x401", + "0x402", + "0x403", + "0x404", + "0x405", + "0x39d2", + "0x406", + "0x407", + "0x408", + "0x409", + "0x40a", + "0x40b", + "0x39e3", + "0x40c", + "0x40d", + "0x39e6", + "0x39ed", + "0x40f", + "0x410", + "0x39f0", + "0x42d", + "0x4c0", + "0x53a", + "0x5bc", + "0x656", + "0x6f0", + "0x753", + "0x7fd", + "0x962", + "0xa4b", + "0xbee", + "0xd70", + "0xe0c", + "0xe86", + "0xeeb", + "0xf50", + "0xfca", + "0x105d", + "0x107a", + "0x10bd", + "0x10dd", + "0x111e", + "0x11de", + "0x11f6", + "0x1217", + "0x1229", + "0x12db", + "0x138d", + "0x13ae", + "0x1460", + "0x1544", + "0x155c", + "0x1684", + "0x16a4", + "0x16ae", + "0x177a", + "0x1784", + "0x17e6", + "0x1833", + "0x1866", + "0x189d", + "0x18a9", + "0x18ea", + "0x1987", + "0x1aa2", + "0x1aae", + "0x1ae5", + "0x1b1d", + "0x1b5f", + "0x1b85", + "0x1ba3", + "0x1c33", + "0x1c50", + "0x1c6c", + "0x1d27", + "0x1dd0", + "0x1df7", + "0x1e5e", + "0x1ea9", + "0x1ef1", + "0x1f09", + "0x1f33", + "0x1f77", + "0x1fe0", + "0x2025", + "0x202a", + "0x2055", + "0x2099", + "0x20de", + "0x2109", + "0x214d", + "0x217f", + "0x2191", + "0x21d0", + "0x21fb", + "0x223f", + "0x227a", + "0x2286", + "0x22b1", + "0x22f5", + "0x23a3", + "0x23e7", + "0x2422", + "0x243c", + "0x2462", + "0x246e", + "0x24d5", + "0x24f4", + "0x2520", + "0x2564", + "0x25b6", + "0x25d4", + "0x2618", + "0x2643", + "0x2648", + "0x2686", + "0x26a6", + "0x270d", + "0x2733", + "0x274e", + "0x2792", + "0x27aa", + "0x27b9", + "0x27fd", + "0x2823", + "0x283b", + "0x287f", + "0x288d", + "0x2899", + "0x28b5", + "0x28b8", + "0x297b", + "0x298f", + "0x299b", + "0x299e", + "0x29b2", + "0x29b5", + "0x2a21", + "0x2a2f", + "0x2a3b", + "0x2a47", + "0x2a4a", + "0x2a56", + "0x2a59", + "0x2a5c", + "0x2a68", + "0x2a78", + "0x2b42", + "0x2b9a", + "0x2b9d", + "0x2c85", + "0x2c88", + "0x2ce4", + "0x2d08", + "0x2d63", + "0x2da5", + "0x2e4d", + "0x2e77", + "0x2e83", + "0x2e94", + "0x2e97", + "0x2ea7", + "0x2eaa", + "0x2ead", + "0x2eb9", + "0x2eca", + "0x2ee5", + "0x2ef6", + "0x2eff", + "0x2f20", + "0x2f3b", + "0x2f7e", + "0x2f9f", + "0x2fba", + "0x2fd5", + "0x2fe6", + "0x3027", + "0x3095", + "0x30d0", + "0x30da", + "0x3112", + "0x311d", + "0x3147", + "0x3152", + "0x31d2", + "0x31f1", + "0x327f", + "0x328a", + "0x32fa", + "0x3363", + "0x33cd", + "0x33d9", + "0x346d", + "0x3487", + "0x34fc", + "0x3508", + "0x3589", + "0x3599", + "0x36bb", + "0x36d5", + "0x36d7", + "0x36e3", + "0x3709", + "0x3747", + "0x375e", + "0x3784", + "0x3823", + "0x383a", + "0x384e", + "0x3874", + "0x3895", + "0x38b6", + "0x38ca", + "0x38f7", + "0x3905", + "0x3909", + "0x3917", + "0x39b7", + "0x39ca", + "0x39dd", + "0x39e7", + "0x1ee63", + "0xe00800d00c00500c00500b00600a009008007006005004003002001000", + "0x601800901700501601501400601300901200500b00601100901000500f", + "0x500c00501f00600a00901e00501d00501c00601801b01a00501900500b", + "0x901e00502300502200601801b02100500b00601100902000500f00e00c", + "0x1b02600500c00500b00600a00902500502500502400600a01b00b006013", + "0x502a00502900601801b02800500b00601100902500501200502700600a", + "0x902500502100502c00600a01b00202b02000502000500b00600a00901e", + "0x502000500b00600a00901e00502f00502e00601801b02d00500b006011", + "0x1b03100500400302000500b00601100902500502000503000600a01b026", + "0x503400601100901a00500f03302000500400301e005031005032006018", + "0x501700503900500b00603800902500501700503700600a01b008036035", + "0x903d00500f03301000501601501e00503c00503b00601801b03a005010", + "0x604200901000501000504100600a01b04000500400303f00503e006011", + "0x500f04501e00504400504300601801b02500501a00501000501700500b", + "0x504000500b00604800903f00500400302500504700504600600a01b010", + "0x601100901e00504a00504900601801b02500501a005010005017005039", + "0x504000503900500b00604800901e00504c00504b00601801b01000500b", + "0x1b03900500f04501e00504e00504d00601801b02500501a005010005017", + "0x905200500400305100500f03303900501601502500505000504f00600a", + "0x505600601801b05500505100503900500b006054009052005053006011", + "0x505900505800601801b02500501000501a00500b00605400901e005057", + "0x505b00505a00601801b02500504000503900501a00500b00604200901e", + "0x4502600501700501700501700505e00603800905d00500400300805c01e", + "0x900806301700506200601100901a00506100506000601801b05f00500f", + "0x1b06900500b00606800901a00506700506600601801b065005064006011", + "0x506d00506c00601801b01a00500b00606800901e00506b00506a006018", + "0x506f00601801b05500500b00601100901700501700506e00600a00901e", + "0x901e00507200507100601801b01700503900500b00600a00901e005070", + "0x1b07600507600507500600a01b00807401700501700501700500b006073", + "0x601801b00807901a00503900507800601801b01a005076005077006018", + "0x501a00500b00605400901a00502600507c00601801b01a00507b00507a", + "0x906500506500507f00600a00901e00507e00507d00601801b02500501a", + "0x601100906500507b00508100600a00906500507b005017005080006073", + "0x507b00501700508400607300903900501700508300600a00907b005082", + "0x501700508600600a00903900501000503900501700508500603800907b", + "0x5017005065005088006073009026005065005017005087006073009039", + "0x508e00508d00508c00508b00608a01b06500506500508900600a009026", + "0x4509700500f04509600509500509400509300509200509100509000508f", + "0x900809d01700506500506500509c00509b00509a00609900909800500f", + "0x50a000601801b06100500b00601100906500509f00509f00509e006073", + "0x60a50090a40060130090080a301a0050170050a200601801b01e0050a1", + "0x500f0330a900501601501e0050a80050a700601801b0250050a600500b", + "0x60ae00900c00509f0050170050ad0060730090ac0050ab0060110090aa", + "0x500c0050b000501700501700501700503900500c0050650050170050af", + "0x50b20050b100601801b09c00500b006011009039005076005076005039", + "0x50b60050b500601801b0250050b400500b0060a50090b300601300901e", + "0x50b900600a01b01e0050b80050b700601801b06700500b00601100901e", + "0x507600501a00500b0060540090250050250050bb00600a01b0250050ba", + "0x503d0050be00601801b0bd00500400301e0050bd0050bc00601801b025", + "0x601801b01e0050c10050c000601801b0bf00503900500b006018009025", + "0x601801b0250050c400500b0060a50090c300601300901a0050250050c2", + "0x901e0050c80050c700601801b07600500b00601100901e0050c60050c5", + "0x1b0ca00500b00601100903900506500500b00600a0090650050c9006011", + "0x906500500400301a0050ca0050cd00601801b01e0050cc0050cb006018", + "0xe01e0050d10050d000601801b0250050cf00500b0060a50090ce006013", + "0x50d40050d300601801b0d200500b00601100901700500f04501700500f", + "0x500b0060a50090d60060130090d500500400306500500b00601100901e", + "0x500b0060a50090da00601300901e0050d90050d800601801b0250050d7", + "0x906500501700500b00600a00901e0050dd0050dc00601801b0250050db", + "0x50e000601801b0250050170050df00503900500b0060380090de006013", + "0x901e0050e30050e200601801b0250050df00500b0060a500901e0050e1", + "0x601100901e0050e50050e400601801b02500501700501a00500b006054", + "0x901e0050d50050e800601801b01e0050e70050e600601801b01700500b", + "0x50eb0050ea00601801b0e900500b00601100905500503900500b00600a", + "0x50760050ed00600a01b03a00500400302500503a0050ec00600a01b01e", + "0x600a01b0f10050040030020f00ef0050040030020ee076005004003025", + "0x50f50050f400601801b0f300503900500b00600a0090250050400050f2", + "0x50040030250050d20050f700600a01b0250050100050f600600a01b01e", + "0x1b01e0050fa0050f900601801b02500500b0060f8009039005004003010", + "0x50fd00601801b0fc00503900500b00601800902500501a0050fb006018", + "0x50df0050c40050a60050b40051000060ff00901a00500400301e0050fe", + "0x510300510200601801b07600510100500b00600a0090d70050db0050cf", + "0x600a00901e00510500510400601801b07b00510100500b00600a00901e", + "0x1b07b00500b00601100901e00510700510600601801b06500510100500b", + "0x500b00601100902500507b00510a00600a01b01e005109005108006018", + "0x60a500902500506500510d00600a01b01e00510c00510b00601801b026", + "0x503900511000600a01b01e00510f00510e00601801b02500510100500b", + "0x500400301e00511300511200601801b11100503900500b00600a009025", + "0x601100901e00511500511400601801b02500501a00500b006018009017", + "0x300211b00211a01e00511900511800601801b00211700811603900500b", + "0x612200612100612000611f11c00500511e00600500511e11d11c005004", + "0x3900500512803a00500512712600500512302f125005124039005005123", + "0x7600500512d00600c00512c07600500512b07600500512a017005005129", + "0x513001700500512b12f00500512b00500500512b00612e076005005123", + "0x513401900500513401700500513301700500512313200c005131017005", + "0x11c00500512313700500512300600500512300500c13600500c13501e005", + "0xa60050051340b400500513400613a139005005123006138136005005123", + "0xd70050051340db0050051340cf0050051340df0050051340c4005005134", + "0x600500513c13b12500512410100500512b10100500512a101005005134", + "0x3900500512b03900500512a11900500512813600500512711c00500513c", + "0x2500500512b11500500512813e00500512713d12500512401a005005123", + "0x13600500c13511900500513403900500513401a00500512b01a00500512a", + "0x512401700500513c01700500511e14000c00513113f00c00513100600c", + "0x5127031125005124111005005127113005005128142005005127141125", + "0x512714612500512414500500512b14400c00513110f00500512b143005", + "0x512814900500512714812500512406500500512306500500512b147005", + "0x512414a12500512402600500512302600500512b02600500512a10c005", + "0x512403c12500512403a125005124017125005124039125005124035125", + "0x512403d12500512407b00500512307b00500512b14c00500512714b125", + "0x512404012500512407b00500512a10900500512814d00500512703f125", + "0x512415012500512406500500512a10700500512814f00500512714e125", + "0x5128153005005127152125005124105005005128151005005127044125", + "0xfe00500512815500500512704712500512401a00500511e006154103005", + "0xfa00500512b15700500512715612500512401a00500513c0fc005005127", + "0x515a03900500513c00615901000500511e03900500511e15800500512b", + "0x513400615c15b00500512303500500512300600c15b00500c135017005", + "0x1700500512d01700500515d15b00500512700500c15b00500c135025005", + "0x512704a12500512401000500513c00616015f00500512715e125005124", + "0x512301000500512b04000500512b0f30050051270f5005005128161005", + "0x51311621250051240400050051230100050051230f10050051230ef005", + "0x513116600500512b03500500512b16500c00513116400500512b16300c", + "0x512407600500511e0ef00500511e0f100500511e04c12500512416700c", + "0x512416b00500512b16a00500512b07600500513c169005005127168125", + "0x512403a00500511e16c00500512b0f100500513c0ef00500513c04e125", + "0x512403a00500512303a00500512b03a00500513c16e00500512716d125", + "0x51230e900500512b0e900500512a0eb00500512816f005005127050125", + "0x512405212500512405112500512417100500512b1701250051240e9005", + "0x51240571250051240551250051240d5005005128173005005127172125", + "0x512810100500517500500c03a00500c13500600c03a00500c135174125", + "0x512b0cf00500512b0c400500512b0a600500512b0b400500512b101005", + "0x51281760050051270591250051240170050051750d700500512b0db005", + "0xe500500512817900500512717812500512400617700500c0051310e7005", + "0x13e00500c13511500500513405b12500512403900500512d039005005175", + "0x11100500c13517a12500512400500c13e00500c13513e00500512300600c", + "0x11100500c13514200500512300600c14200500c13511300500513400600c", + "0x17b00c00513102600500512705d12500512400500c14200500c13500500c", + "0x617d07600500517517c00c00512c14300500512300500c14300500c135", + "0x900050051340e300500512818000500512717f12500512417e00c005131", + "0x18200500512706112500512410f005005128181125005124090005005123", + "0x14700500c13500618300600c14300500c13510f0050051340e1005005128", + "0x1840050051340df00500512b00500c14700500c13514700500512300600c", + "0x512300600c14900500c13510c005005134185125005124184005005123", + "0x1700500512a00600c00513102600500512d00500c14900500c135149005", + "0x512400500c02600500c13510100500512d065005005175186125005124", + "0x51240950050051230950050051340dd005005128187005005127065125", + "0x6b12500512418800c00513106912500512400600c02600500c135067125", + "0x18a1250051240940050051230940050051340d9005005128189005005127", + "0x511e00500c14c00500c13514c00500512300600c14c00500c13500618b", + "0x512d06d1250051240d500500513c07b00500517518c00c0051310d5005", + "0x512a00600c00518f0d400500512818e00500512718d125005124035005", + "0x19300500512b00619200619100619007b00500512d0ba1250051240cf005", + "0x910050051340d100500512819500500512707012500512419400c005131", + "0x10900500513419700500512b0d500500512b196125005124091005005123", + "0x6500500511e00500c14d00500c13514d00500512300600c14d00500c135", + "0x600c19900500c1350ca0050051340061980721250051240a600500512a", + "0x512819b00500512719a12500512400500c19900500c135199005005123", + "0x512419c00500512319c00500513406500500513c0ca0050051280cc005", + "0x10700500513407612500512408d00500512308d00500513400619e19d125", + "0x19f00500512b00500c14f00500c13514f00500512300600c14f00500c135", + "0x1a112500512408e00500512308e0050051341a012500512406500500512d", + "0x500c15100500c13515100500512300600c15100500c135105005005134", + "0x500c00512c0c80050051281a30050051271a21250051240c400500512a", + "0x1030050051340c60050051281a50050051271a412500512407b125005124", + "0xfe00500513400500c15300500c13515300500512300600c15300500c135", + "0xfc00500c13500500c15500500c13515500500512300600c15500500c135", + "0x500c1a700500c1351a700500512300600c1a700500c1350061a600500c", + "0xc13515700500512300600c15700500c1350fa0050051341a8125005124", + "0x15f00500c13515f00500512300600c15f00500c1350061a900500c157005", + "0x51281aa00500512707e12500512403d00500512301000500513000500c", + "0xc13504000500513403d00500512b03d00500512a0bf0050051270c1005", + "0xc13516100500512300600c16100500c1350f500500513400600c0f3005", + "0x51240100050051751ab12500512400500c16100500c13500500c0f3005", + "0x512b0bd00500511e08d12500512409200500512309200500513408c125", + "0x51280bd00500513c1ac00500512708e125005124040005005175109005", + "0x512409300500512309300500513408f12500512400600c0051ad0bd005", + "0x512300600c16900500c1350061af1ae00500512b0bd00500512b090125", + "0x512300500c16e00500c13509112500512400500c16900500c135169005", + "0x1b100c00513100600c16e00500c1351b000500512709212500512416e005", + "0x1b200500512300500c1b200500c13516f00500512300500c16f00500c135", + "0x51230e900500512800600c16f00500c1350eb005005134093125005124", + "0x512a1b300c00513109512500512405500500512b094125005124055005", + "0x51240670050051230670050051341b40050051270961250051240b4005", + "0x51340b60050051280670050051280b80050051281b60050051271b5125", + "0xc1350d50050051340b800500512b09b125005124096005005123096005", + "0x1b800c0051310061b700500c17300500c13517300500512300600c173005", + "0x9800500512809800500515d0b20050051281b900500512709c125005124", + "0x513405f1250051240061bb1ba00c0051310b000500512b00c00500512b", + "0x512709712500512409800500512309f12500512408f00500512308f005", + "0x512408c00500512308c0050051340a80050051280a11250051241bc005", + "0x512409800500512b0dd00500512b0b600500512b0a800500512b1bd125", + "0x1be00500c1351be00500512300600c1be00500c1350061c00061bf1be125", + "0x17600500512300600c17600500c1350e70050051341c112500512400500c", + "0x17900500512300600c17900500c1350e500500513400500c17600500c135", + "0x1bd0050051270a612500512400600c0fc00500c13500500c17900500c135", + "0x9c00500512b09b00500512b05f00500512805f00500515d0a1005005128", + "0x600c18000500c1350e30050051340fa0050051280061c20df00500512a", + "0x512b1b500500512a0a812500512400500c18000500c135180005005123", + "0x513407e0050051281ab0050051271bc1250051241b50050051231b5005", + "0x512400500c18200500c13518200500512300600c18200500c1350e1005", + "0x512400500c1a800500c1351a800500512300600c1a800500c1350aa125", + "0x512300600c18700500c1350dd0050051340061c30db00500512a0ac125", + "0xd700500512a0a91250051240b012500512400500c18700500c135187005", + "0x500c18900500c13518900500512300600c18900500c1350d9005005134", + "0x18e00500c1350d40050051340170050051c40b2125005124098125005124", + "0x1b912500512400500c18e00500c1351c500c00513118e00500512300600c", + "0xc1351c600c0051311a400500512300600c1a400500c1350b4125005124", + "0x512300600c19500500c1350d10050051340b612500512400500c1a4005", + "0x1c10050051231c800c0051c71b612500512400500c19500500c135195005", + "0xc13519b00500512300600c19b00500c1350cc005005134199005005127", + "0x600c1a200500c1350061ca1c900c00513119c00500512800500c19b005", + "0x512411900500512b0b812500512400500c1a200500c1351a2005005123", + "0x512d00600500512d13700500512d12600500512d1cb00c0051311b4125", + "0x1a100500512300600c1a100500c1351cc00c0051c71b212500512411c005", + "0x500c1a100500c1351a300500512300500c1a300500c1351cd00c005131", + "0x1a000500c1350061ce00600c1a300500c1350c80050051341b0125005124", + "0xbd1250051241cf00c00513100500c1a000500c1351a000500512300600c", + "0x500c1a500500c1351a500500512300600c1a500500c1350c6005005134", + "0x600c1aa00500c1350c100500513400600c0bf00500c1351a7005005127", + "0xc13502500500512d00500c1aa00500c1350100050051331aa005005123", + "0x19a0050051271ac12500512419d00500512319d00500513400500c0bf005", + "0x400050051280bf1250051241ae1250051241d000c005131072005005128", + "0xc1351ac00500512300600c1ac00500c1350bd005005134010005005129", + "0x1b000500c1351a71250051241aa1250051240c112500512400500c1ac005", + "0x51340700050051281960050051270c41250051241b000500512300600c", + "0x512403900500512905500500512805500500512d0550050051750e9005", + "0x512818d0050051271a51250051240ba0050051281b20050051270c6125", + "0x51271a312500512405d0050051231d100c0051c70c812500512406d005", + "0xc1350b800500513419c12500512406900500512306b00500512818a005", + "0x51340ca12500512400500c1b400500c1351b400500512300600c1b4005", + "0x512400500c1b600500c1351b600500512300600c1b600500c1350b6005", + "0x1b900500c1351b900500512300600c1b900500c1350b20050051340cc125", + "0xc1350a800500513419912500512419b1250051241d200c00513100500c", + "0x513119f12500512400500c1bc00500c1351bc00500512300600c1bc005", + "0x61d61be0050051270061d50061d41860050051281860050051341d300c", + "0xcf12500512400500c18500500c13518500500512300600c18500500c135", + "0x500c1bd00500c1351bd00500512300600c1bd00500c1350a1005005134", + "0x51310d11250051241d700c0051311b500500512701000c1b500500c135", + "0x51241da00c0051311931250051241d900c0051311951250051241d800c", + "0x1ab00500512300500c1ab00500c1350d41250051241db00c0051310d2125", + "0x1de00c0051310d51250051241dd00c00513118e1250051241dc00c005131", + "0xd91250051241e000c0051310d71250051241df00c005131197125005124", + "0x512800600c1ab00500c13507e0050051341891250051241e100c005131", + "0x1e300c00513101e00c1b500500c1351a80050051271e200c005131184005", + "0x513106700500512b1e500c0051311e400c00513101a00c1b500500c135", + "0x1b500500c1351a200500512701200c1b500500c1351a40050051271e600c", + "0x19d0050051281a00050051271a100500512700c00c1b500500c13500500c", + "0x19a00500512300600c19a00500c1350720050051341390050051750061e7", + "0x512b18100500512818100500513413900500512b00500c19a00500c135", + "0x512817a0050051270db12500512405d00500513c05d00500511e19d005", + "0x512b05d00500512b05900500512b1780050051270dd12500512405b005", + "0xdf12500512418412500512418712500512401d00c1b500500c13517f005", + "0x512401900c1b500500c1350e31250051241821250051240e1125005124", + "0x51240510050051230390050051300e5125005124059005005128180125", + "0xc13507000500513405100500512b057005005128174005005127179125", + "0x515a05200500511e00500c19600500c13519600500512300600c196005", + "0x17000500c13517000500512305200500512300600c17000500c135039005", + "0x512b05500500513403900500515d17000500512705200500513c00500c", + "0xc13506d0050051341761250051240e712500512417200500512b052005", + "0x512417312500512400500c18d00500c13518d00500512300600c18d005", + "0x18a00500c13506b00500513400600c06900500c1350eb1250051240e9125", + "0x6900500c13500500c18a00500c1351e800c00513118a00500512300600c", + "0xc13512500c1b500500c1351e900c1b500500c13506900500512700500c", + "0x8d00500517508c00500512808c00500517518500500512700600c1b5005", + "0x9000500517508f00500512808e00500512808e00500517508d005005128", + "0x92005005128092005005175091005005128091005005175090005005128", + "0x517509300500512809300500517509200500512b0061ea01000500512d", + "0x5128096005005175095005005128095005005175094005005128094005", + "0x16f12500512417a00500512300500c17a00500c1351eb00c005131096005", + "0x51ec00600c17a00500c13505b00500513404e00500512816d005005127", + "0x16800500512716e12500512400500c0051ad0061ed05d005005175017005", + "0xc13517800500512300600c17800500c13505900500513404c005005128", + "0x1000500c1f11f000c0051311ef00c0051311ee00c00513100500c178005", + "0x513304a0050051281620050051271711250051241f200c0051ad076005", + "0x17400500c13517400500512300600c17400500c135057005005134039005", + "0x515a03f00500511e1691250051241f300c00513105100500512a00500c", + "0x15600500c13515600500512303f00500512300600c15600500c135010005", + "0x512716c12500512401000500515d15600500512703f00500513c00500c", + "0x16d00500512300600c16d00500c13504e005005134044005005128152005", + "0x61f515e00500512b1f400c00513103f00500512b00500c16d00500c135", + "0x1f600c00513100500c15000500c13515000500512300600c15000500c135", + "0xc13516800500512300600c16800500c13504c0050051340ef125005124", + "0x14b00500512716a12500512404000500513c04000500511e00500c168005", + "0xc13516200500512300600c16200500c13504a00500513403c005005128", + "0xf112500512402000500511e1f700c00513114e00500512b00500c162005", + "0x512402000500513c0200050051230f31250051240061f816b125005124", + "0x200050051340061f90310050051280200050051281480050051270f5125", + "0xc13515200500512300600c15200500c135044005005134161125005124", + "0x3100500511e15000500512714a00500512b02000500512b00500c152005", + "0x15b12500512403100500513c15f12500512400600c0051fa00500c0051fa", + "0xc13514b00500512300600c14b00500c13503c00500513400c005005123", + "0x51310061fb01000500512a14600500512b03100500512b00500c14b005", + "0x3100500513416412500512416612500512400c00c0051fa0061fd1fc00c", + "0x1fe00c0051fa00500c14800500c13514800500512300600c14800500c135", + "0x513100620100620000c00500512d00c00500512a0061ff00c005005175", + "0x15712500512420400c0051311410050051270fa12500512400620320200c", + "0x1551250051240fe1250051240fc12500512400c00c0051ad020005005175", + "0x512300600c14100500c13513d00500512815812500512420500c005131", + "0x2f00500512813b00500512710112500512400500c14100500c135141005", + "0x512820800500513410312500512420700500512300620602d005005127", + "0x512702a00500512820a00500512715312500512420900c005131208005", + "0x20c00500512820c00500512320c00500513410512500512400620b028005", + "0x20d00500512715112500512413d00500512d13d00500512313d005005134", + "0x600c13b00500c13502f00500513400600c02d00500c135023005005128", + "0x1e900500512710712500512400620e00500c13b00500c13513b005005123", + "0x600c20a00500c13502a00500513400600c02800500c13501d005005128", + "0x12500500512812500500513400620f00500c20a00500c13520a005005123", + "0x20d00500512300600c20d00500c13502300500513414f125005124006210", + "0xc13501d00500513410912500512400500c20d00500c13521100c005131", + "0x621300500c1e900500c13521200c0051311e900500512300600c1e9005", + "0x512300500c02800500c13500621402d00500512300500c02d00500c135", + "0x600c00601a00521600500600500601d005216005006006006215028005", + "0xc41e901e00c21600c00c00500c010006006216005006125006006216005", + "0x501900601000521600501000501d00600621600500601200602102000c", + "0x21600c20d00501e00601e00521600501e00501a00620d02300c216005010", + "0x620c0052160050230051e90060062160050060120060260050a9025005", + "0x2a00502300620a02a00c21600502800502100602800521600520c005020", + "0x602d20700c21600520800502100620800521600500620d006006216005", + "0x21600502d00502500602f00521600520a005025006006216005207005023", + "0x603f00621600c13b02f00c20c00602f00521600502f00502600613b005", + "0x621600501a00502a00600621600501d005028006006216005006012006", + "0x521600500620700613d00521600500620800600621600502500520a006", + "0x13b00603100521600514113d00c02f00614100521600514100502d006141", + "0x21600514800514100614800521600503114600c13d006146005216005006", + "0x14600601e00521600501e00501a00600600521600500600503100614a005", + "0x21600514a00514a0061250052160051250051480061e90052160051e9005", + "0x21600500603500600621600500601200614a1251e901e00601200514a005", + "0x3900c21600c0351e901e125017006035005216005035005039006035005", + "0x3c00614b00521600500603a00600621600500601200603c03a00c096017", + "0x604000521600500603d00603f00521600500614b00603d005216005006", + "0x4400521600500614e00615000521600500604000614e00521600500603f", + "0x21600515200504400615200521600504415014e04003f03d14b019150006", + "0x14600603900521600503900501a00600621600504700515200615604700c", + "0x216005125005148006006005216005006005031006017005216005017005", + "0x502515612500601703901d04700602500521600502500502d006125005", + "0x1a00c15e00601200521600501201d00c15600616201901204a15e012216", + "0x500601200616800508e04c00521600c16200504a006019005216005019", + "0x504c00616d00521600504c00516200604e005216005006208006006216", + "0x521600515e00501a00600621600505000516800617005000c21600516d", + "0x504e00617000521600517000501d00604a00521600504a00514600615e", + "0x5000617205205112521600504e17004a15e01016d00604e00521600504e", + "0x50550051700060062160050060120060570051a205500521600c172005", + "0x5b17800c21600517400505200600621600505900505100605917400c216", + "0x517a00505700617a00521600505b005055006006216005178005172006", + "0x601200521600501200503100617f00521600505d00517400605d005216", + "0x501900514800605200521600505200514600605100521600505100501a", + "0x1200617f01905205101201200517f00521600517f00514a006019005216", + "0x12005216005012005031006181005216005057005141006006216005006", + "0x1900514800605200521600505200514600605100521600505100501a006", + "0x618101905205101201200518100521600518100514a006019005216005", + "0x5216005012005031006061005216005168005141006006216005006012", + "0x514800604a00521600504a00514600615e00521600515e00501a006012", + "0x6101904a15e01201200506100521600506100514a006019005216005019", + "0x621600501a00502a00600621600501d005028006006216005006012006", + "0x521600500605900618500521600500620800600621600502500520a006", + "0x13b00606500521600518618500c02f00618600521600518600502d006186", + "0x21600506900514100606900521600506506700c13d006067005216005006", + "0x14600603a00521600503a00501a00600600521600500600503100606b005", + "0x21600506b00514a00612500521600512500514800603c00521600503c005", + "0x502600505100600621600500601200606b12503c03a00601200506b005", + "0x2300516800600621600501a00502a00600621600501d005028006006216", + "0x502d00606d00521600500617800618a005216005006208006006216005", + "0x521600500613b00618d00521600506d18a00c02f00606d00521600506d", + "0x3100619600521600507000514100607000521600518d0ba00c13d0060ba", + "0x2160051e900514600601e00521600501e00501a006006005216005006005", + "0x1200519600521600519600514a0061250052160051250051480061e9005", + "0x16800600621600501a00502a0060062160050060120061961251e901e006", + "0x607200521600500620800600621600501d005028006006216005010005", + "0x519a07200c02f00619a00521600519a00502d00619a005216005006059", + "0x61a000521600519d07600c13d00607600521600500613b00619d005216", + "0x502000501a0060060052160050060050310061a10052160051a0005141", + "0x6125005216005125005148006021005216005021005146006020005216", + "0x521600500605b0061a11250210200060120051a10052160051a100514a", + "0x21600c00c00500c01000600621600500612500600621600500600c00601d", + "0x521600501000501d0060062160050060120061e901e00c21701a01900c", + "0x1e00601900521600501900501a00602102000c216005010005019006010", + "0x501900501a00600621600500601200602300521801200521600c021005", + "0x602000521600502000501d00601a00521600501a005146006019005216", + "0x2602520d12521600502001a01912505d00601200521600501201d00c17a", + "0x518100600621600500601200602800521920c00521600c02600517f006", + "0x601200620700521a20800521600c20a00506100620a02a00c21600520c", + "0x602f00521600502d00502000602d00521600502a0051e9006006216005", + "0x21600500620d00600621600513b00502300613d13b00c21600502f005021", + "0x2500600621600503100502300614603100c216005141005021006141005", + "0x21600514800502600614a00521600514600502500614800521600513d005", + "0x16800600621600500601200600610500621600c14a14800c20c006148005", + "0x603500521600500620800600621600501200520a006006216005208005", + "0x503903500c02f00603900521600503900502d006039005216005006207", + "0x603c00521600501703a00c13d00603a00521600500613b006017005216", + "0x520d00501a00600600521600500600503100614b00521600503c005141", + "0x612500521600512500514800602500521600502500514600620d005216", + "0x621600500601200614b12502520d00601200514b00521600514b00514a", + "0x2520d12501700603d00521600503d00503900603d005216005006035006", + "0x500603a00600621600500601200615014e00c21b04003f00c21600c03d", + "0x603d00604700521600500614b00615200521600500603c006044005216", + "0x14e00604a00521600500604000615e00521600500603f006156005216005", + "0x604c00521600516204a15e156047152044019150006162005216005006", + "0x500600503100604000521600504000514600603f00521600503f00501a", + "0x601200521600501200502d006125005216005125005148006006005216", + "0x1221600520801204c12500604003f01918500620800521600520800501d", + "0x500601200605200521c05100521600c17000518600617005016d04e168", + "0x172005052006172005216005006208006006216005051005065006006216", + "0x17400521600505700505500600621600505500517200605705500c216005", + "0x16d005031006178005216005059005174006059005216005174005057006", + "0x4e00521600504e00514600616800521600516800501a00616d005216005", + "0x16816d01200517800521600517800514a006050005216005050005148006", + "0x503100605b00521600505200514100600621600500601200617805004e", + "0x521600504e00514600616800521600516800501a00616d00521600516d", + "0x16d01200505b00521600505b00514a00605000521600505000514800604e", + "0x520a00600621600520800516800600621600500601200605b05004e168", + "0x2d00605d00521600500605900617a005216005006208006006216005012", + "0x21600500613b00617f00521600505d17a00c02f00605d00521600505d005", + "0x618500521600506100514100606100521600517f18100c13d006181005", + "0x515000514600614e00521600514e00501a006006005216005006005031", + "0x518500521600518500514a006125005216005125005148006150005216", + "0x600621600520700505100600621600500601200618512515014e006012", + "0x18600521600500620800600621600501200520a00600621600502a005168", + "0x6518600c02f00606500521600506500502d006065005216005006067006", + "0x6b00521600506706900c13d00606900521600500613b006067005216005", + "0x20d00501a00600600521600500600503100618a00521600506b005141006", + "0x12500521600512500514800602500521600502500514600620d005216005", + "0x21600500601200618a12502520d00601200518a00521600518a00514a006", + "0x600503100606d00521600502800514100600621600501200520a006006", + "0x2500521600502500514600620d00521600520d00501a006006005216005", + "0x20d00601200506d00521600506d00514a006125005216005125005148006", + "0x2000516800600621600502300505100600621600500601200606d125025", + "0x617800618d00521600500620800600621600501d005069006006216005", + "0x52160050ba18d00c02f0060ba0052160050ba00502d0060ba005216005", + "0x514100607200521600507019600c13d00619600521600500613b006070", + "0x521600501900501a00600600521600500600503100619a005216005072", + "0x514a00612500521600512500514800601a00521600501a005146006019", + "0x16800600621600500601200619a12501a01900601200519a00521600519a", + "0x619d00521600500620800600621600501d005069006006216005010005", + "0x507619d00c02f00607600521600507600502d006076005216005006059", + "0x61a20052160051a01a100c13d0061a100521600500613b0061a0005216", + "0x501e00501a00600600521600500600503100607b0052160051a2005141", + "0x61250052160051250051480061e90052160051e900514600601e005216", + "0x621600500612500607b1251e901e00601200507b00521600507b00514a", + "0x621600500601200601a01900c21d01d01200c21600c00c00500c010006", + "0x1200c06b00601000521600501000501d00601200521600501200501a006", + "0x1200602300521e02100521600c02000518a0060201e901e125216005010", + "0x20d00c2160051e90050190061e90052160051e900501d006006216005006", + "0x1e900600621600500601200620c00521f02600521600c02500501e006025", + "0x21600502a00502100602a00521600502800502000602800521600520d005", + "0x502100620700521600500620d00600621600520a00502300620820a00c", + "0x521600520800502500600621600502d00502300602f02d00c216005207", + "0xc20c00613b00521600513b00502600613d00521600502f00502500613b", + "0x621600502600520a00600621600500601200600622000621600c13d13b", + "0x521600500620700614100521600500620800600621600502100506d006", + "0x13b00614600521600503114100c02f00603100521600503100502d006031", + "0x21600514a00514100614a00521600514614800c13d006148005216005006", + "0x14600601e00521600501e00501a006006005216005006005031006035005", + "0x21600503500514a00612500521600512500514800601d00521600501d005", + "0x21600500603500600621600500601200603512501d01e006012005035005", + "0x1700c21600c03901d01e125017006039005216005039005039006039005", + "0x3c00603d00521600500603a00600621600500601200614b03c00c22103a", + "0x614e00521600500603d00604000521600500614b00603f005216005006", + "0x15200521600500614e00604400521600500604000615000521600500603f", + "0x21600504700504400604700521600515204415014e04003f03d019150006", + "0x14600601700521600501700501a00600621600515600515200615e15600c", + "0x21600512500514800600600521600500600503100603a00521600503a005", + "0xba00602600521600502600502d00602100521600502100518d006125005", + "0x4e00507000604e16804c16204a01221600502602115e12500603a017019", + "0x17000521600500620800600621600500601200605000522216d00521600c", + "0x519a00617205200c21600505100507200605100521600516d005196006", + "0x17000521600517000504e00617200521600517200519d006006216005052", + "0x505200600621600505700505100605705500c21600517017200c076006", + "0x521600505900505500600621600517400517200605917400c216005055", + "0x503100617a00521600505b00517400605b005216005178005057006178", + "0x521600516200514600604a00521600504a00501a00604c00521600504c", + "0x4c01200517a00521600517a00514a006168005216005168005148006162", + "0x3100605d00521600505000514100600621600500601200617a16816204a", + "0x21600516200514600604a00521600504a00501a00604c00521600504c005", + "0x1200505d00521600505d00514a006168005216005168005148006162005", + "0x6d00600621600502600520a00600621600500601200605d16816204a04c", + "0x618100521600500605900617f005216005006208006006216005021005", + "0x500613b00606100521600518117f00c02f00618100521600518100502d", + "0x6500521600518600514100618600521600506118500c13d006185005216", + "0x14b00514600603c00521600503c00501a006006005216005006005031006", + "0x6500521600506500514a00612500521600512500514800614b005216005", + "0x621600520c00505100600621600500601200606512514b03c006012005", + "0x521600500620800600621600502100506d00600621600520d005168006", + "0x6700c02f00606900521600506900502d006069005216005006067006067", + "0x521600506b18a00c13d00618a00521600500613b00606b005216005069", + "0x501a00600600521600500600503100618d00521600506d00514100606d", + "0x521600512500514800601d00521600501d00514600601e00521600501e", + "0x500601200618d12501d01e00601200518d00521600518d00514a006125", + "0x50062080060062160051e9005168006006216005023005051006006216", + "0x2f00607000521600507000502d0060700052160050061780060ba005216", + "0x519607200c13d00607200521600500613b0061960052160050700ba00c", + "0x600600521600500600503100619d00521600519a00514100619a005216", + "0x512500514800601d00521600501d00514600601e00521600501e00501a", + "0x1200619d12501d01e00601200519d00521600519d00514a006125005216", + "0x59006076005216005006208006006216005010005168006006216005006", + "0x2160051a007600c02f0061a00052160051a000502d0061a0005216005006", + "0x14100607b0052160051a11a200c13d0061a200521600500613b0061a1005", + "0x21600501900501a0060060052160050060050310061a400521600507b005", + "0x14a00612500521600512500514800601a00521600501a005146006019005", + "0x60062160050061250061a412501a0190060120051a40052160051a4005", + "0x600621600500601200601a01900c22301d01200c21600c00c00500c010", + "0x1001200c06b00601000521600501000501d00601200521600501200501a", + "0x601200602300522402100521600c02000518a0060201e901e125216005", + "0x2520d00c2160051e90050190061e90052160051e900501d006006216005", + "0x51e900600621600500601200620c00522502600521600c02500501e006", + "0xc21600502a00502100602a00521600502800502000602800521600520d", + "0x20700502100620700521600500620d00600621600520a00502300620820a", + "0x13b00521600520800502500600621600502d00502300602f02d00c216005", + "0x13b00c20c00613b00521600513b00502600613d00521600502f005025006", + "0x600621600502600520a00600621600500601200600622600621600c13d", + "0x3100521600500620700614100521600500620800600621600502100506d", + "0x613b00614600521600503114100c02f00603100521600503100502d006", + "0x521600514a00514100614a00521600514614800c13d006148005216005", + "0x514600601e00521600501e00501a006006005216005006005031006035", + "0x521600503500514a00612500521600512500514800601d00521600501d", + "0x521600500603500600621600500601200603512501d01e006012005035", + "0x3a01700c21600c03901d01e125017006039005216005039005039006039", + "0x603c00603d00521600500603a00600621600500601200614b03c00c227", + "0x3f00614e00521600500603d00604000521600500614b00603f005216005", + "0x615200521600500614e006044005216005006040006150005216005006", + "0x521600501700501a00604700521600515204415014e04003f03d019150", + "0x514800600600521600500600503100603a00521600503a005146006017", + "0x521600502600502d00602100521600502100518d006125005216005125", + "0x604c16204a15e15601221600502602104712500603a0170191a0006026", + "0x16800506500600621600500601200604e00522816800521600c04c005186", + "0x617005000c21600516d00505200616d005216005006208006006216005", + "0x216005051005057006051005216005170005055006006216005050005172", + "0x1a00604a00521600504a005031006172005216005052005174006052005", + "0x21600516200514800615e00521600515e005146006156005216005156005", + "0x601200617216215e15604a01200517200521600517200514a006162005", + "0x604a00521600504a00503100605500521600504e005141006006216005", + "0x516200514800615e00521600515e00514600615600521600515600501a", + "0x1200605516215e15604a01200505500521600505500514a006162005216", + "0x20800600621600502100506d00600621600502600520a006006216005006", + "0x17400521600517400502d006174005216005006059006057005216005006", + "0x17800c13d00617800521600500613b00605900521600517405700c02f006", + "0x521600500600503100617a00521600505b00514100605b005216005059", + "0x514800614b00521600514b00514600603c00521600503c00501a006006", + "0x17a12514b03c00601200517a00521600517a00514a006125005216005125", + "0x621600520d00516800600621600520c005051006006216005006012006", + "0x521600500606700605d00521600500620800600621600502100506d006", + "0x13b00618100521600517f05d00c02f00617f00521600517f00502d00617f", + "0x21600518500514100618500521600518106100c13d006061005216005006", + "0x14600601e00521600501e00501a006006005216005006005031006186005", + "0x21600518600514a00612500521600512500514800601d00521600501d005", + "0x502300505100600621600500601200618612501d01e006012005186005", + "0x50061780060650052160050062080060062160051e9005168006006216", + "0x6900521600506706500c02f00606700521600506700502d006067005216", + "0x18a00514100618a00521600506906b00c13d00606b00521600500613b006", + "0x1e00521600501e00501a00600600521600500600503100606d005216005", + "0x6d00514a00612500521600512500514800601d00521600501d005146006", + "0x516800600621600500601200606d12501d01e00601200506d005216005", + "0x2d0060ba00521600500605900618d005216005006208006006216005010", + "0x21600500613b0060700052160050ba18d00c02f0060ba0052160050ba005", + "0x619a00521600507200514100607200521600507019600c13d006196005", + "0x501a00514600601900521600501900501a006006005216005006005031", + "0x519a00521600519a00514a00612500521600512500514800601a005216", + "0xc21600c00c00500c01000600621600500612500619a12501a019006012", + "0x1200521600501200501a00600621600500601200601a01900c22901d012", + "0x60201e901e12521600501001200c06b00601000521600501000501d006", + "0x1e900501d00600621600500601200602300522a02100521600c02000518a", + "0x521600c02500501e00602520d00c2160051e90050190061e9005216005", + "0x2000602800521600520d0051e900600621600500601200620c00522b026", + "0x520a00502300620820a00c21600502a00502100602a005216005028005", + "0x2300602f02d00c21600520700502100620700521600500620d006006216", + "0x521600502f00502500613b00521600520800502500600621600502d005", + "0x600622c00621600c13d13b00c20c00613b00521600513b00502600613d", + "0x600621600502100506d00600621600502600520a006006216005006012", + "0x521600503100502d006031005216005006207006141005216005006208", + "0xc13d00614800521600500613b00614600521600503114100c02f006031", + "0x21600500600503100603500521600514a00514100614a005216005146148", + "0x14800601d00521600501d00514600601e00521600501e00501a006006005", + "0x12501d01e00601200503500521600503500514a006125005216005125005", + "0x216005039005039006039005216005006035006006216005006012006035", + "0x601200614b03c00c22d03a01700c21600c03901d01e125017006039005", + "0x614b00603f00521600500603c00603d00521600500603a006006216005", + "0x4000615000521600500603f00614e00521600500603d006040005216005", + "0x15014e04003f03d01915000615200521600500614e006044005216005006", + "0x21600503a00514600601700521600501700501a006047005216005152044", + "0x18d00612500521600512500514800600600521600500600503100603a005", + "0x603a0170191a100602600521600502600502d006021005216005021005", + "0x16800521600c04c00518600604c16204a15e156012216005026021047125", + "0x500620800600621600516800506500600621600500601200604e00522e", + "0x600621600505000517200617005000c21600516d00505200616d005216", + "0x5052005174006052005216005051005057006051005216005170005055", + "0x615600521600515600501a00604a00521600504a005031006172005216", + "0x517200514a00616200521600516200514800615e00521600515e005146", + "0x4e00514100600621600500601200617216215e15604a012005172005216", + "0x15600521600515600501a00604a00521600504a005031006055005216005", + "0x5500514a00616200521600516200514800615e00521600515e005146006", + "0x520a00600621600500601200605516215e15604a012005055005216005", + "0x5900605700521600500620800600621600502100506d006006216005026", + "0x21600517405700c02f00617400521600517400502d006174005216005006", + "0x14100605b00521600505917800c13d00617800521600500613b006059005", + "0x21600503c00501a00600600521600500600503100617a00521600505b005", + "0x14a00612500521600512500514800614b00521600514b00514600603c005", + "0x600621600500601200617a12514b03c00601200517a00521600517a005", + "0x621600502100506d00600621600520d00516800600621600520c005051", + "0x21600517f00502d00617f00521600500606700605d005216005006208006", + "0x13d00606100521600500613b00618100521600517f05d00c02f00617f005", + "0x500600503100618600521600518500514100618500521600518106100c", + "0x601d00521600501d00514600601e00521600501e00501a006006005216", + "0x1d01e00601200518600521600518600514a006125005216005125005148", + "0x51e9005168006006216005023005051006006216005006012006186125", + "0x6700502d006067005216005006178006065005216005006208006006216", + "0x6b00521600500613b00606900521600506706500c02f006067005216005", + "0x503100606d00521600518a00514100618a00521600506906b00c13d006", + "0x521600501d00514600601e00521600501e00501a006006005216005006", + "0x601200506d00521600506d00514a00612500521600512500514800601d", + "0x620800600621600501000516800600621600500601200606d12501d01e", + "0x60ba0052160050ba00502d0060ba00521600500605900618d005216005", + "0x7019600c13d00619600521600500613b0060700052160050ba18d00c02f", + "0x600521600500600503100619a005216005072005141006072005216005", + "0x12500514800601a00521600501a00514600601900521600501900501a006", + "0x619a12501a01900601200519a00521600519a00514a006125005216005", + "0x601a01900c22f01d01200c21600c00c00500c010006006216005006125", + "0xc21600501000501900601000521600501000501d006006216005006012", + "0x523002000521600c1e900501e00601200521600501200501a0061e901e", + "0x501e00501d00601200521600501200501a006006216005006012006021", + "0x21600c02500518a00602520d02312521600501e01200c06b00601e005216", + "0x602800521600520d0051e900600621600500601200620c005231026005", + "0x20a00502300620820a00c21600502a00502100602a005216005028005020", + "0x602f02d00c21600520700502100620700521600500620d006006216005", + "0x21600502f00502500613b00521600520800502500600621600502d005023", + "0x623200621600c13d13b00c20c00613b00521600513b00502600613d005", + "0x621600502000520a00600621600502600506d006006216005006012006", + "0x21600503100502d006031005216005006207006141005216005006208006", + "0x13d00614800521600500613b00614600521600503114100c02f006031005", + "0x500600503100603500521600514a00514100614a00521600514614800c", + "0x601d00521600501d00514600602300521600502300501a006006005216", + "0x1d02300601200503500521600503500514a006125005216005125005148", + "0x5039005039006039005216005006035006006216005006012006035125", + "0x1200614b03c00c23303a01700c21600c03901d023125017006039005216", + "0x14b00603f00521600500603c00603d00521600500603a006006216005006", + "0x615000521600500603f00614e00521600500603d006040005216005006", + "0x14e04003f03d01915000615200521600500614e006044005216005006040", + "0x15600515200615e15600c216005047005044006047005216005152044150", + "0x603a00521600503a00514600601700521600501700501a006006216005", + "0x502000502d006125005216005125005148006006005216005006005031", + "0x2015e12500603a0170191a200602600521600502600518d006020005216", + "0x5000523416d00521600c04e00507000604e16804c16204a012216005026", + "0x521600516d005196006170005216005006208006006216005006012006", + "0x519d00600621600505200519a00617205200c216005051005072006051", + "0x21600517017200c07600617000521600517000504e006172005216005172", + "0x605917400c21600505500505200600621600505700505100605705500c", + "0x216005178005057006178005216005059005055006006216005174005172", + "0x1a00604c00521600504c00503100617a00521600505b00517400605b005", + "0x21600516800514800616200521600516200514600604a00521600504a005", + "0x601200617a16816204a04c01200517a00521600517a00514a006168005", + "0x604c00521600504c00503100605d005216005050005141006006216005", + "0x516800514800616200521600516200514600604a00521600504a00501a", + "0x1200605d16816204a04c01200505d00521600505d00514a006168005216", + "0x20800600621600502000520a00600621600502600506d006006216005006", + "0x18100521600518100502d00618100521600500605900617f005216005006", + "0x18500c13d00618500521600500613b00606100521600518117f00c02f006", + "0x5216005006005031006065005216005186005141006186005216005061", + "0x514800614b00521600514b00514600603c00521600503c00501a006006", + "0x6512514b03c00601200506500521600506500514a006125005216005125", + "0x621600520d00516800600621600520c005051006006216005006012006", + "0x521600500606700606700521600500620800600621600502000520a006", + "0x13b00606b00521600506906700c02f00606900521600506900502d006069", + "0x21600506d00514100606d00521600506b18a00c13d00618a005216005006", + "0x14600602300521600502300501a00600600521600500600503100618d005", + "0x21600518d00514a00612500521600512500514800601d00521600501d005", + "0x502100505100600621600500601200618d12501d02300601200518d005", + "0x50061780060ba00521600500620800600621600501e005168006006216", + "0x1960052160050700ba00c02f00607000521600507000502d006070005216", + "0x19a00514100619a00521600519607200c13d00607200521600500613b006", + "0x1200521600501200501a00600600521600500600503100619d005216005", + "0x19d00514a00612500521600512500514800601d00521600501d005146006", + "0x516800600621600500601200619d12501d01200601200519d005216005", + "0x2d0061a0005216005006059006076005216005006208006006216005010", + "0x21600500613b0061a10052160051a007600c02f0061a00052160051a0005", + "0x61a400521600507b00514100607b0052160051a11a200c13d0061a2005", + "0x501a00514600601900521600501900501a006006005216005006005031", + "0x51a40052160051a400514a00612500521600512500514800601a005216", + "0xc21600c00c00500c0100060062160050061250061a412501a019006012", + "0x1000521600501000501d00600621600500601200601a01900c23501d012", + "0x501e00601200521600501200501a0061e901e00c216005010005019006", + "0x21600501200501a00600621600500601200602100523602000521600c1e9", + "0x20d02312521600501e01200c06b00601e00521600501e00501d006012005", + "0x1e900600621600500601200620c00523702600521600c02500518a006025", + "0x21600502a00502100602a00521600502800502000602800521600520d005", + "0x502100620700521600500620d00600621600520a00502300620820a00c", + "0x521600520800502500600621600502d00502300602f02d00c216005207", + "0xc20c00613b00521600513b00502600613d00521600502f00502500613b", + "0x621600502600506d00600621600500601200600623800621600c13d13b", + "0x521600500620700614100521600500620800600621600502000520a006", + "0x13b00614600521600503114100c02f00603100521600503100502d006031", + "0x21600514a00514100614a00521600514614800c13d006148005216005006", + "0x14600602300521600502300501a006006005216005006005031006035005", + "0x21600503500514a00612500521600512500514800601d00521600501d005", + "0x21600500603500600621600500601200603512501d023006012005035005", + "0x1700c21600c03901d023125017006039005216005039005039006039005", + "0x3c00603d00521600500603a00600621600500601200614b03c00c23903a", + "0x614e00521600500603d00604000521600500614b00603f005216005006", + "0x15200521600500614e00604400521600500604000615000521600500603f", + "0x21600501700501a00604700521600515204415014e04003f03d019150006", + "0x14800600600521600500600503100603a00521600503a005146006017005", + "0x21600502600518d00602000521600502000502d006125005216005125005", + "0x4c16204a15e15601221600502602004712500603a01701907b006026005", + "0x506500600621600500601200604e00523a16800521600c04c005186006", + "0x17005000c21600516d00505200616d005216005006208006006216005168", + "0x5051005057006051005216005170005055006006216005050005172006", + "0x604a00521600504a005031006172005216005052005174006052005216", + "0x516200514800615e00521600515e00514600615600521600515600501a", + "0x1200617216215e15604a01200517200521600517200514a006162005216", + "0x4a00521600504a00503100605500521600504e005141006006216005006", + "0x16200514800615e00521600515e00514600615600521600515600501a006", + "0x605516215e15604a01200505500521600505500514a006162005216005", + "0x600621600502000520a00600621600502600506d006006216005006012", + "0x521600517400502d006174005216005006059006057005216005006208", + "0xc13d00617800521600500613b00605900521600517405700c02f006174", + "0x21600500600503100617a00521600505b00514100605b005216005059178", + "0x14800614b00521600514b00514600603c00521600503c00501a006006005", + "0x12514b03c00601200517a00521600517a00514a006125005216005125005", + "0x21600520d00516800600621600520c00505100600621600500601200617a", + "0x21600500606700605d00521600500620800600621600502000520a006006", + "0x618100521600517f05d00c02f00617f00521600517f00502d00617f005", + "0x518500514100618500521600518106100c13d00606100521600500613b", + "0x602300521600502300501a006006005216005006005031006186005216", + "0x518600514a00612500521600512500514800601d00521600501d005146", + "0x2100505100600621600500601200618612501d023006012005186005216", + "0x617800606500521600500620800600621600501e005168006006216005", + "0x521600506706500c02f00606700521600506700502d006067005216005", + "0x514100618a00521600506906b00c13d00606b00521600500613b006069", + "0x521600501200501a00600600521600500600503100606d00521600518a", + "0x514a00612500521600512500514800601d00521600501d005146006012", + "0x16800600621600500601200606d12501d01200601200506d00521600506d", + "0x60ba00521600500605900618d005216005006208006006216005010005", + "0x500613b0060700052160050ba18d00c02f0060ba0052160050ba00502d", + "0x19a00521600507200514100607200521600507019600c13d006196005216", + "0x1a00514600601900521600501900501a006006005216005006005031006", + "0x19a00521600519a00514a00612500521600512500514800601a005216005", + "0x21600c00c00500c01000600621600500612500619a12501a019006012005", + "0x521600501000501d00600621600500601200601a01900c23b01d01200c", + "0x1e00601200521600501200501a0061e901e00c216005010005019006010", + "0x501200501a00600621600500601200602100523c02000521600c1e9005", + "0x2312521600501e01200c06b00601e00521600501e00501d006012005216", + "0x600621600500601200620c00523d02600521600c02500518a00602520d", + "0x502a00502100602a00521600502800502000602800521600520d0051e9", + "0x2100620700521600500620d00600621600520a00502300620820a00c216", + "0x21600520800502500600621600502d00502300602f02d00c216005207005", + "0x20c00613b00521600513b00502600613d00521600502f00502500613b005", + "0x21600502600506d00600621600500601200600623e00621600c13d13b00c", + "0x21600500620700614100521600500620800600621600502000520a006006", + "0x614600521600503114100c02f00603100521600503100502d006031005", + "0x514a00514100614a00521600514614800c13d00614800521600500613b", + "0x602300521600502300501a006006005216005006005031006035005216", + "0x503500514a00612500521600512500514800601d00521600501d005146", + "0x500603500600621600500601200603512501d023006012005035005216", + "0xc21600c03901d023125017006039005216005039005039006039005216", + "0x603d00521600500603a00600621600500601200614b03c00c23f03a017", + "0x14e00521600500603d00604000521600500614b00603f00521600500603c", + "0x521600500614e00604400521600500604000615000521600500603f006", + "0x501700501a00604700521600515204415014e04003f03d019150006152", + "0x600600521600500600503100603a00521600503a005146006017005216", + "0x502600518d00602000521600502000502d006125005216005125005148", + "0x16204a15e15601221600502602004712500603a0170191a4006026005216", + "0x6500600621600500601200604e00524016800521600c04c00518600604c", + "0x5000c21600516d00505200616d005216005006208006006216005168005", + "0x51005057006051005216005170005055006006216005050005172006170", + "0x4a00521600504a005031006172005216005052005174006052005216005", + "0x16200514800615e00521600515e00514600615600521600515600501a006", + "0x617216215e15604a01200517200521600517200514a006162005216005", + "0x521600504a00503100605500521600504e005141006006216005006012", + "0x514800615e00521600515e00514600615600521600515600501a00604a", + "0x5516215e15604a01200505500521600505500514a006162005216005162", + "0x621600502000520a00600621600502600506d006006216005006012006", + "0x21600517400502d006174005216005006059006057005216005006208006", + "0x13d00617800521600500613b00605900521600517405700c02f006174005", + "0x500600503100617a00521600505b00514100605b00521600505917800c", + "0x614b00521600514b00514600603c00521600503c00501a006006005216", + "0x14b03c00601200517a00521600517a00514a006125005216005125005148", + "0x520d00516800600621600520c00505100600621600500601200617a125", + "0x500606700605d00521600500620800600621600502000520a006006216", + "0x18100521600517f05d00c02f00617f00521600517f00502d00617f005216", + "0x18500514100618500521600518106100c13d00606100521600500613b006", + "0x2300521600502300501a006006005216005006005031006186005216005", + "0x18600514a00612500521600512500514800601d00521600501d005146006", + "0x505100600621600500601200618612501d023006012005186005216005", + "0x17800606500521600500620800600621600501e005168006006216005021", + "0x21600506706500c02f00606700521600506700502d006067005216005006", + "0x14100618a00521600506906b00c13d00606b00521600500613b006069005", + "0x21600501200501a00600600521600500600503100606d00521600518a005", + "0x14a00612500521600512500514800601d00521600501d005146006012005", + "0x600621600500601200606d12501d01200601200506d00521600506d005", + "0xba00521600500605900618d005216005006208006006216005010005168", + "0x613b0060700052160050ba18d00c02f0060ba0052160050ba00502d006", + "0x521600507200514100607200521600507019600c13d006196005216005", + "0x514600601900521600501900501a00600600521600500600503100619a", + "0x521600519a00514a00612500521600512500514800601a00521600501a", + "0xc00c00500c01000600621600500612500619a12501a01900601200519a", + "0x21600501200501a00600621600500601200601a01900c24101d01200c216", + "0x1e901e12521600501001200c1a800601000521600501000501d006012005", + "0x1e900600621600500601200602300524202100521600c02000507e006020", + "0x21600502500502100602500521600520d00502000620d0052160051e9005", + "0x502100602800521600500620d00600621600502600502300620c02600c", + "0x521600520c00502500600621600502a00502300620a02a00c216005028", + "0xc20c00620800521600520800502600620700521600520a005025006208", + "0x62160050210051ab00600621600500601200600624300621600c207208", + "0x21600502f00502d00602f00521600500620700602d005216005006208006", + "0x13d00613d00521600500613b00613b00521600502f02d00c02f00602f005", + "0x500600503100603100521600514100514100614100521600513b13d00c", + "0x601d00521600501d00514600601e00521600501e00501a006006005216", + "0x1d01e00601200503100521600503100514a006125005216005125005148", + "0x5146005039006146005216005006035006006216005006012006031125", + "0x1200603903500c24414a14800c21600c14601d01e125017006146005216", + "0x14b00603a00521600500603c00601700521600500603a006006216005006", + "0x603d00521600500603f00614b00521600500603d00603c005216005006", + "0x14b03c03a01701915000604000521600500614e00603f005216005006040", + "0x514a00514600614800521600514800501a00614e00521600504003f03d", + "0x612500521600512500514800600600521600500600503100614a005216", + "0x15001221600502114e12500614a14801d08d00602100521600502100508c", + "0x21600500601200604a00524515e00521600c156005186006156047152044", + "0x516200505200616200521600500620800600621600515e005065006006", + "0x604e00521600516800505500600621600504c00517200616804c00c216", + "0x515200503100605000521600516d00517400616d00521600504e005057", + "0x604400521600504400514600615000521600515000501a006152005216", + "0x4415015201200505000521600505000514a006047005216005047005148", + "0x15200503100617000521600504a005141006006216005006012006050047", + "0x4400521600504400514600615000521600515000501a006152005216005", + "0x15015201200517000521600517000514a006047005216005047005148006", + "0x50062080060062160050210051ab006006216005006012006170047044", + "0x2f00605200521600505200502d006052005216005006059006051005216", + "0x517205500c13d00605500521600500613b00617200521600505205100c", + "0x6006005216005006005031006174005216005057005141006057005216", + "0x512500514800603900521600503900514600603500521600503500501a", + "0x1200617412503903500601200517400521600517400514a006125005216", + "0x2080060062160051e9005168006006216005023005051006006216005006", + "0x17800521600517800502d006178005216005006178006059005216005006", + "0x17a00c13d00617a00521600500613b00605b00521600517805900c02f006", + "0x521600500600503100617f00521600505d00514100605d00521600505b", + "0x514800601d00521600501d00514600601e00521600501e00501a006006", + "0x17f12501d01e00601200517f00521600517f00514a006125005216005125", + "0x181005216005006208006006216005010005168006006216005006012006", + "0x6118100c02f00606100521600506100502d006061005216005006059006", + "0x6500521600518518600c13d00618600521600500613b006185005216005", + "0x1900501a006006005216005006005031006067005216005065005141006", + "0x12500521600512500514800601a00521600501a005146006019005216005", + "0x21600500612500606712501a01900601200506700521600506700514a006", + "0x21600500601200601a01900c24601d01200c21600c00c00500c010006006", + "0x1a0061e901e00c21600501000501900601000521600501000501d006006", + "0x601200602100524702000521600c1e900501e006012005216005012005", + "0x620d00521600502300502000602300521600501e0051e9006006216005", + "0x21600500620d00600621600502500502300602602500c21600520d005021", + "0x2500600621600502800502300602a02800c21600520c00502100620c005", + "0x21600520a00502600620800521600502a00502500620a005216005026005", + "0x20a00600621600500601200600624800621600c20820a00c20c00620a005", + "0x602d005216005006207006207005216005006208006006216005020005", + "0x500613b00602f00521600502d20700c02f00602d00521600502d00502d", + "0x14100521600513d00514100613d00521600502f13b00c13d00613b005216", + "0x1d00514600601200521600501200501a006006005216005006005031006", + "0x14100521600514100514a00612500521600512500514800601d005216005", + "0x3100521600500603500600621600500601200614112501d012006012005", + "0x24914814600c21600c03101d012125017006031005216005031005039006", + "0x500603c00603900521600500603a00600621600500601200603514a00c", + "0x603f00603c00521600500603d00603a00521600500614b006017005216", + "0x15000603f00521600500614e00603d00521600500604000614b005216005", + "0x14e00c21600504000504400604000521600503f03d14b03c03a017039019", + "0x14800514600614600521600514600501a00600621600514e005152006150", + "0x125005216005125005148006006005216005006005031006148005216005", + "0x1221600502015012500614814601d08e00602000521600502000502d006", + "0x500601200616200524a04a00521600c15e00508f00615e156047152044", + "0x509100616800521600504a00509000604c005216005006208006006216", + "0x521600516d00508c00600621600504e0051ab00616d04e00c216005168", + "0x617005000c21600504c16d00c09200604c00521600504c00504e00616d", + "0x505100517200605205100c216005050005052006006216005170005051", + "0x174006055005216005172005057006172005216005052005055006006216", + "0x21600504400501a006047005216005047005031006057005216005055005", + "0x14a006156005216005156005148006152005216005152005146006044005", + "0x6006216005006012006057156152044047012005057005216005057005", + "0x504400501a006047005216005047005031006174005216005162005141", + "0x6156005216005156005148006152005216005152005146006044005216", + "0x621600500601200617415615204404701200517400521600517400514a", + "0x521600500605900605900521600500620800600621600502000520a006", + "0x13b00605b00521600517805900c02f00617800521600517800502d006178", + "0x21600505d00514100605d00521600505b17a00c13d00617a005216005006", + "0x14600614a00521600514a00501a00600600521600500600503100617f005", + "0x21600517f00514a006125005216005125005148006035005216005035005", + "0x502100505100600621600500601200617f12503514a00601200517f005", + "0x500617800618100521600500620800600621600501e005168006006216", + "0x18500521600506118100c02f00606100521600506100502d006061005216", + "0x6500514100606500521600518518600c13d00618600521600500613b006", + "0x1200521600501200501a006006005216005006005031006067005216005", + "0x6700514a00612500521600512500514800601d00521600501d005146006", + "0x516800600621600500601200606712501d012006012005067005216005", + "0x2d00606b005216005006059006069005216005006208006006216005010", + "0x21600500613b00618a00521600506b06900c02f00606b00521600506b005", + "0x60ba00521600518d00514100618d00521600518a06d00c13d00606d005", + "0x501a00514600601900521600501900501a006006005216005006005031", + "0x50ba0052160050ba00514a00612500521600512500514800601a005216", + "0xc21600c00c00500c0100060062160050061250060ba12501a019006012", + "0x1000521600501000501d00600621600500601200601a01900c24b01d012", + "0x501e00601200521600501200501a0061e901e00c216005010005019006", + "0x21600501200501a00600621600500601200602100524c02000521600c1e9", + "0x20d02312521600501e01200c1a800601e00521600501e00501d006012005", + "0x1e900600621600500601200620c00524d02600521600c02500507e006025", + "0x21600502a00502100602a00521600502800502000602800521600520d005", + "0x502100620700521600500620d00600621600520a00502300620820a00c", + "0x521600520800502500600621600502d00502300602f02d00c216005207", + "0xc20c00613b00521600513b00502600613d00521600502f00502500613b", + "0x62160050260051ab00600621600500601200600624e00621600c13d13b", + "0x521600500620700614100521600500620800600621600502000520a006", + "0x13b00614600521600503114100c02f00603100521600503100502d006031", + "0x21600514a00514100614a00521600514614800c13d006148005216005006", + "0x14600602300521600502300501a006006005216005006005031006035005", + "0x21600503500514a00612500521600512500514800601d00521600501d005", + "0x21600500603500600621600500601200603512501d023006012005035005", + "0x1700c21600c03901d023125017006039005216005039005039006039005", + "0x3c00603d00521600500603a00600621600500601200614b03c00c24f03a", + "0x614e00521600500603d00604000521600500614b00603f005216005006", + "0x15200521600500614e00604400521600500604000615000521600500603f", + "0x21600501700501a00604700521600515204415014e04003f03d019150006", + "0x14800600600521600500600503100603a00521600503a005146006017005", + "0x21600502600508c00602000521600502000502d006125005216005125005", + "0x4c16204a15e15601221600502602004712500603a017019093006026005", + "0x620800600621600500601200604e00525016800521600c04c005094006", + "0x621600505000515200617005000c21600516800509500616d005216005", + "0x5200518d00600621600505100506d00605205100c216005170005096006", + "0xc21600516d05200c1b500616d00521600516d00504e006052005216005", + "0x17200617405700c216005172005052006006216005055005051006055172", + "0x5216005059005057006059005216005174005055006006216005057005", + "0x501a00604a00521600504a00503100605b005216005178005174006178", + "0x521600516200514800615e00521600515e005146006156005216005156", + "0x500601200605b16215e15604a01200505b00521600505b00514a006162", + "0x1a00604a00521600504a00503100617a00521600504e005141006006216", + "0x21600516200514800615e00521600515e005146006156005216005156005", + "0x601200617a16215e15604a01200517a00521600517a00514a006162005", + "0x620800600621600502000520a0060062160050260051ab006006216005", + "0x617f00521600517f00502d00617f00521600500605900605d005216005", + "0x18106100c13d00606100521600500613b00618100521600517f05d00c02f", + "0x6005216005006005031006186005216005185005141006185005216005", + "0x12500514800614b00521600514b00514600603c00521600503c00501a006", + "0x618612514b03c00601200518600521600518600514a006125005216005", + "0x600621600520d00516800600621600520c005051006006216005006012", + "0x6700521600500606700606500521600500620800600621600502000520a", + "0x613b00606900521600506706500c02f00606700521600506700502d006", + "0x521600518a00514100618a00521600506906b00c13d00606b005216005", + "0x514600602300521600502300501a00600600521600500600503100606d", + "0x521600506d00514a00612500521600512500514800601d00521600501d", + "0x21600502100505100600621600500601200606d12501d02300601200506d", + "0x21600500617800618d00521600500620800600621600501e005168006006", + "0x60700052160050ba18d00c02f0060ba0052160050ba00502d0060ba005", + "0x507200514100607200521600507019600c13d00619600521600500613b", + "0x601200521600501200501a00600600521600500600503100619a005216", + "0x519a00514a00612500521600512500514800601d00521600501d005146", + "0x1000516800600621600500601200619a12501d01200601200519a005216", + "0x502d00607600521600500605900619d005216005006208006006216005", + "0x521600500613b0061a000521600507619d00c02f006076005216005076", + "0x3100607b0052160051a20051410061a20052160051a01a100c13d0061a1", + "0x21600501a00514600601900521600501900501a006006005216005006005", + "0x1200507b00521600507b00514a00612500521600512500514800601a005", + "0x1200c21600c00c00500c01000600621600500612500607b12501a019006", + "0x601200521600501200501a00600621600500601200601a01900c25101d", + "0x18a0060201e901e12521600501001200c06b00601000521600501000501d", + "0x501e00501a00600621600500601200602300525202100521600c020005", + "0x20d1252160051e901e00c1a80061e90052160051e900501d00601e005216", + "0x600621600500601200602800525320c00521600c02600507e006026025", + "0x520a00502100620a00521600502a00502000602a0052160050250051e9", + "0x2100602d00521600500620d00600621600520800502300620720800c216", + "0x21600520700502500600621600502f00502300613b02f00c21600502d005", + "0x20c00613d00521600513d00502600614100521600513b00502500613d005", + "0x21600520c0051ab00600621600500601200600625400621600c14113d00c", + "0x21600500620700603100521600500620800600621600502100506d006006", + "0x614800521600514603100c02f00614600521600514600502d006146005", + "0x503500514100603500521600514814a00c13d00614a00521600500613b", + "0x620d00521600520d00501a006006005216005006005031006039005216", + "0x503900514a00612500521600512500514800601d00521600501d005146", + "0x500603500600621600500601200603912501d20d006012005039005216", + "0xc21600c01701d20d125017006017005216005017005039006017005216", + "0x603f00521600500603a00600621600500601200603d14b00c25503c03a", + "0x15000521600500603d00614e00521600500614b00604000521600500603c", + "0x521600500614e00615200521600500604000604400521600500603f006", + "0x503a00501a00615600521600504715204415014e04003f019150006047", + "0x600600521600500600503100603c00521600503c00514600603a005216", + "0x520c00508c00602100521600502100518d006125005216005125005148", + "0x4c16204a15e01221600520c02115612500603c03a01909b00620c005216", + "0x20800600621600500601200616d00525604e00521600c16800509c006168", + "0x21600517000515200605117000c21600504e00505f006050005216005006", + "0x508c0060062160050520051ab00617205200c216005051005091006006", + "0x21600505017200c09200605000521600505000504e006172005216005172", + "0x605917400c21600505500505200600621600505700505100605705500c", + "0x216005178005057006178005216005059005055006006216005174005172", + "0x1a00616200521600516200503100617a00521600505b00517400605b005", + "0x21600504c00514800604a00521600504a00514600615e00521600515e005", + "0x601200617a04c04a15e16201200517a00521600517a00514a00604c005", + "0x616200521600516200503100605d00521600516d005141006006216005", + "0x504c00514800604a00521600504a00514600615e00521600515e00501a", + "0x1200605d04c04a15e16201200505d00521600505d00514a00604c005216", + "0x20800600621600502100506d00600621600520c0051ab006006216005006", + "0x18100521600518100502d00618100521600500605900617f005216005006", + "0x18500c13d00618500521600500613b00606100521600518117f00c02f006", + "0x5216005006005031006065005216005186005141006186005216005061", + "0x514800603d00521600503d00514600614b00521600514b00501a006006", + "0x6512503d14b00601200506500521600506500514a006125005216005125", + "0x6216005025005168006006216005028005051006006216005006012006", + "0x521600500606700606700521600500620800600621600502100506d006", + "0x13b00606b00521600506906700c02f00606900521600506900502d006069", + "0x21600506d00514100606d00521600506b18a00c13d00618a005216005006", + "0x14600620d00521600520d00501a00600600521600500600503100618d005", + "0x21600518d00514a00612500521600512500514800601d00521600501d005", + "0x502300505100600621600500601200618d12501d20d00601200518d005", + "0x50061780060ba0052160050062080060062160051e9005168006006216", + "0x1960052160050700ba00c02f00607000521600507000502d006070005216", + "0x19a00514100619a00521600519607200c13d00607200521600500613b006", + "0x1e00521600501e00501a00600600521600500600503100619d005216005", + "0x19d00514a00612500521600512500514800601d00521600501d005146006", + "0x516800600621600500601200619d12501d01e00601200519d005216005", + "0x2d0061a0005216005006059006076005216005006208006006216005010", + "0x21600500613b0061a10052160051a007600c02f0061a00052160051a0005", + "0x61a400521600507b00514100607b0052160051a11a200c13d0061a2005", + "0x501a00514600601900521600501900501a006006005216005006005031", + "0x51a40052160051a400514a00612500521600512500514800601a005216", + "0xc21600c00500600c0100060062160050061250061a412501a019006012", + "0x1a0052160051250051e900600621600500601200601901d00c257012010", + "0x50230060201e900c21600501e00502100601e00521600501a005020006", + "0x20d02300c21600502100502100602100521600500620d0060062160051e9", + "0x520d005025006025005216005020005025006006216005023005023006", + "0x601000521600501000501a006025005216005025005026006026005216", + "0x21600500620800600621600500601200600625800621600c02602500c20c", + "0xc02f00602800521600502800502d00602800521600500620700620c005", + "0x21600502a20a00c13d00620a00521600500613b00602a00521600502820c", + "0x14600601000521600501000501a006207005216005208005141006208005", + "0x21600520700514a00600c00521600500c005148006012005216005012005", + "0x521600500603500600621600500601200620700c012010010005207005", + "0x13b02f00c21600c02d01201012501700602d00521600502d00503900602d", + "0x603c00603100521600500603a00600621600500601200614113d00c259", + "0x3f00614a00521600500603d00614800521600500614b006146005216005", + "0x601700521600500614e006039005216005006040006035005216005006", + "0x521600502f00501a00603a00521600501703903514a148146031019150", + "0x1009f00600c00521600500c00514800613b00521600513b00514600602f", + "0x25a04000521600c03f00509700603f03d14b03c01021600503a00c13b02f", + "0x50400050a100615000521600500620800600621600500601200614e005", + "0x15604700c21600515200502100600621600504400515200615204400c216", + "0x515e0051bd00615e005216005156005025006006216005047005023006", + "0x4c00c21600516200505200616200521600504a15000c02f00604a005216", + "0x4e00505700604e00521600516800505500600621600504c005172006168", + "0x3c00521600503c00501a00605000521600516d00517400616d005216005", + "0x5000514a00603d00521600503d00514800614b00521600514b005146006", + "0x14e00514100600621600500601200605003d14b03c010005050005216005", + "0x14b00521600514b00514600603c00521600503c00501a006170005216005", + "0x14b03c01000517000521600517000514a00603d00521600503d005148006", + "0x21600500605900605100521600500620800600621600500601200617003d", + "0x617200521600505205100c02f00605200521600505200502d006052005", + "0x505700514100605700521600517205500c13d00605500521600500613b", + "0x614100521600514100514600613d00521600513d00501a006174005216", + "0xc14113d01000517400521600517400514a00600c00521600500c005148", + "0x5216005006208006006216005125005168006006216005006012006174", + "0x5900c02f00617800521600517800502d006178005216005006059006059", + "0x521600505b17a00c13d00617a00521600500613b00605b005216005178", + "0x514600601d00521600501d00501a00617f00521600505d00514100605d", + "0x521600517f00514a00600c00521600500c005148006019005216005019", + "0x621600500600c0060120052160050061be00617f00c01901d01000517f", + "0x1e01a00c25b01901d00c21600c00500600c010006006216005006125006", + "0x21600501900514600601d00521600501d00501a006006216005006012006", + "0x1e912521600512501901d1251c100612500521600512500501d006019005", + "0x600621600500601200620d00525c02300521600c0210050a6006021020", + "0x620c00525d01000521600c0260051bc00602602500c2160050230050a8", + "0x52160050200051460061e90052160051e900501a006006216005006012", + "0x5d00601000521600501001200c0aa00602500521600502500501d006020", + "0x525e20800521600c20a00517f00620a02a0281252160050250201e9125", + "0x2f00506100602f02d00c216005208005181006006216005006012006207", + "0x521600502d0051e900600621600500601200613d00525f13b00521600c", + "0x2300614814600c216005031005021006031005216005141005020006141", + "0x3500c21600514a00502100614a00521600500620d006006216005146005", + "0x39005025006017005216005148005025006006216005035005023006039", + "0x621600c03a01700c20c00601700521600501700502600603a005216005", + "0x501000517200600621600513b005168006006216005006012006006260", + "0x14b00502d00614b00521600500620700603c005216005006208006006216", + "0x3f00521600500613b00603d00521600514b03c00c02f00614b005216005", + "0x501a00614e00521600504000514100604000521600503d03f00c13d006", + "0x521600500c00514800602a00521600502a005146006028005216005028", + "0x21600500601200614e00c02a02801000514e00521600514e00514a00600c", + "0x28125017006150005216005150005039006150005216005006035006006", + "0x603a00600621600500601200615604700c26115204400c21600c15002a", + "0x3d00616200521600500614b00604a00521600500603c00615e005216005", + "0x604e00521600500604000616800521600500603f00604c005216005006", + "0x5000521600516d04e16804c16204a15e01915000616d00521600500614e", + "0x15200514600600621600517000515200605117000c216005050005044006", + "0x1000521600501000504e00600c00521600500c005148006152005216005", + "0x5212521600513b01005100c1520120ac00613b00521600513b00501d006", + "0x526205700521600c0550050b000604400521600504400501a006055172", + "0x52160050062080060062160050570050a9006006216005006012006174", + "0x505500600621600517800517200605b17800c216005059005052006059", + "0x521600505d00517400605d00521600517a00505700617a00521600505b", + "0x514800605200521600505200514600604400521600504400501a00617f", + "0x617f17205204401000517f00521600517f00514a006172005216005172", + "0x521600504400501a006181005216005174005141006006216005006012", + "0x514a006172005216005172005148006052005216005052005146006044", + "0x5168006006216005006012006181172052044010005181005216005181", + "0x5900606100521600500620800600621600501000517200600621600513b", + "0x21600518506100c02f00618500521600518500502d006185005216005006", + "0x14100606700521600518606500c13d00606500521600500613b006186005", + "0x21600515600514600604700521600504700501a006069005216005067005", + "0x1000506900521600506900514a00600c00521600500c005148006156005", + "0x516800600621600513d00505100600621600500601200606900c156047", + "0x6700606b00521600500620800600621600501000517200600621600502d", + "0x21600518a06b00c02f00618a00521600518a00502d00618a005216005006", + "0x1410060ba00521600506d18d00c13d00618d00521600500613b00606d005", + "0x21600502a00514600602800521600502800501a0060700052160050ba005", + "0x1000507000521600507000514a00600c00521600500c00514800602a005", + "0x514100600621600501000517200600621600500601200607000c02a028", + "0x521600502a00514600602800521600502800501a006196005216005207", + "0x2801000519600521600519600514a00600c00521600500c00514800602a", + "0x2500516800600621600520c00505100600621600500601200619600c02a", + "0x6178006072005216005006208006006216005012005098006006216005", + "0x521600519a07200c02f00619a00521600519a00502d00619a005216005", + "0x51410061a000521600519d07600c13d00607600521600500613b00619d", + "0x52160050200051460061e90052160051e900501a0061a10052160051a0", + "0x1e90100051a10052160051a100514a00600c00521600500c005148006020", + "0x20d0051410060062160050120050980060062160050060120061a100c020", + "0x200052160050200051460061e90052160051e900501a0061a2005216005", + "0x201e90100051a20052160051a200514a00600c00521600500c005148006", + "0x51250051680060062160050120050980060062160050060120061a200c", + "0x1a400502d0061a400521600500605900607b005216005006208006006216", + "0x7e00521600500613b0061a80052160051a407b00c02f0061a4005216005", + "0x501a00608c0052160051ab0051410061ab0052160051a807e00c13d006", + "0x521600500c00514800601e00521600501e00514600601a00521600501a", + "0x2160050060b200608c00c01e01a01000508c00521600508c00514a00600c", + "0x50060b200602100521600500605b0061e90052160050061b900601a005", + "0x1000500c01000600621600500612500600621600500600c00620d005216", + "0x501d00501d00600621600500601200602820c00c26302602500c21600c", + "0x2500521600502500501a00620a02a00c21600501d00501900601d005216", + "0x501a00600621600500601200620800526402000521600c20a00501e006", + "0x521600502a00501d006026005216005026005146006025005216005025", + "0x20712521600502a02602512505d00602000521600502002100c17a00602a", + "0x600621600500601200613d00526513b00521600c02f00517f00602f02d", + "0x614600526601900521600c03100506100603114100c21600513b005181", + "0x60062160050060b40061480052160051410051e9006006216005006012", + "0x3900526703514a00c21600c1480051b600601900521600501901a00c0b6", + "0x21600514a0051b40060170052160050350050b8006006216005006012006", + "0x50060120060062680050061b000603c0052160050170051b200603a005", + "0x51b400603d00521600514b0051ac00614b0052160050060bd006006216", + "0x521600c03c0051ae00603c00521600503d0051b200603a005216005039", + "0xc100614e00521600503f0050bf00600621600500601200604000526903f", + "0x21600515000502d00620700521600520700501a00615000521600514e005", + "0x1e00521600c1520051a700615204400c21600515020700c1aa006150005", + "0x503a0050550060062160050060c400600621600500601200604700526a", + "0x602d00521600502d00514600604400521600504400501a006156005216", + "0x4412505d00601e00521600501e1e900c0c600615600521600515600501d", + "0x616800526b04c00521600c16200517f00616204a15e12521600515602d", + "0x21600c16d00506100616d04e00c21600504c005181006006216005006012", + "0x615e00521600515e00501a00600621600500601200605000526c023005", + "0x2320d00c0b600604e00521600504e00501d00604a00521600504a005146", + "0x520050c800605205117012521600504e04a15e1251a5006023005216005", + "0xc2160051720051a300600621600500601200605500526d17200521600c", + "0x600621600500601200617800526e05900521600c17400519c006174057", + "0x517a00502100617a00521600505b00502000605b0052160050570051e9", + "0x2100618100521600500620d00600621600505d00502300617f05d00c216", + "0x21600517f00502500600621600506100502300618506100c216005181005", + "0x20c006186005216005186005026006065005216005185005025006186005", + "0x2160050590050ca00600621600500601200600626f00621600c06518600c", + "0x501900516800600621600501e0050cc006006216005023005168006006", + "0x500620700606700521600500620800600621600502000520a006006216", + "0x6b00521600506906700c02f00606900521600506900502d006069005216", + "0x6d00514100606d00521600506b18a00c13d00618a00521600500613b006", + "0x17000521600517000501a00600600521600500600503100618d005216005", + "0x5100514600612500521600512500519900600c00521600500c00519b006", + "0x18d00521600518d00514a006012005216005012005148006051005216005", + "0x21600500603500600621600500601200618d01205112500c170006019005", + "0x7000c21600c0ba0511701250170060ba0052160050ba0050390060ba005", + "0x3c00619d00521600500603a00600621600500601200619a07200c270196", + "0x61a100521600500603d0061a000521600500614b006076005216005006", + "0x1a400521600500614e00607b0052160050060400061a200521600500603f", + "0x21600507000501a0061a80052160051a407b1a21a11a007619d019150006", + "0x3100600c00521600500c00519b006196005216005196005146006070005", + "0x216005012005148006125005216005125005199006006005216005006005", + "0x19f00601900521600501900501d00602000521600502000502d006012005", + "0x2160050590050cf00602300521600502300501d00601e00521600501e005", + "0x1921600505902301e0190201a801212500600c1960700210d1006059005", + "0x1200609200527109100521600c09000518600609008f08e08d08c1ab07e", + "0x52006093005216005006208006006216005091005065006006216005006", + "0x21600509500505500600621600509400517200609509400c216005093005", + "0x3100609b0052160051b50051740061b5005216005096005057006096005", + "0x21600508c00519b00607e00521600507e00501a00608d00521600508d005", + "0x1480061ab0052160051ab00514600608e00521600508e00519900608c005", + "0x8e08c07e08d01900509b00521600509b00514a00608f00521600508f005", + "0x503100609c00521600509200514100600621600500601200609b08f1ab", + "0x521600508c00519b00607e00521600507e00501a00608d00521600508d", + "0x51480061ab0052160051ab00514600608e00521600508e00519900608c", + "0x1ab08e08c07e08d01900509c00521600509c00514a00608f00521600508f", + "0x50230051680060062160050590050ca00600621600500601200609c08f", + "0x2000520a00600621600501900516800600621600501e0050cc006006216", + "0x502d00609f00521600500605900605f005216005006208006006216005", + "0x521600500613b00609700521600509f05f00c02f00609f00521600509f", + "0x310061be0052160051bd0051410061bd0052160050970a100c13d0060a1", + "0x21600500c00519b00607200521600507200501a006006005216005006005", + "0x14800619a00521600519a00514600612500521600512500519900600c005", + "0x12500c0720060190051be0052160051be00514a006012005216005012005", + "0x570051680060062160051780050510060062160050060120061be01219a", + "0x516800600621600501e0050cc006006216005023005168006006216005", + "0x1950061c100521600500620800600621600502000520a006006216005019", + "0x2160050a61c100c02f0060a60052160050a600502d0060a6005216005006", + "0x1410060aa0052160050a81bc00c13d0061bc00521600500613b0060a8005", + "0x21600517000501a0060060052160050060050310060ac0052160050aa005", + "0x14600612500521600512500519900600c00521600500c00519b006170005", + "0x2160050ac00514a006012005216005012005148006051005216005051005", + "0x520a0060062160050060120060ac01205112500c1700060190050ac005", + "0x16800600621600501e0050cc006006216005023005168006006216005020", + "0x52160050060050310060b0005216005055005141006006216005019005", + "0x519900600c00521600500c00519b00617000521600517000501a006006", + "0x5216005012005148006051005216005051005146006125005216005125", + "0x120060b001205112500c1700060190050b00052160050b000514a006012", + "0xcc006006216005019005168006006216005050005051006006216005006", + "0x600621600504e00516800600621600502000520a00600621600501e005", + "0x980052160050060d20060a900521600500620800600621600520d005193", + "0x613b0060b20052160050980a900c02f00609800521600509800502d006", + "0x52160050b40051410060b40052160050b21b900c13d0061b9005216005", + "0x519b00615e00521600515e00501a0060060052160050060050310060b6", + "0x521600504a00514600612500521600512500519900600c00521600500c", + "0x60190050b60052160050b600514a00601200521600501200514800604a", + "0x60062160050190051680060062160050060120060b601204a12500c15e", + "0x621600520d00519300600621600502000520a00600621600501e0050cc", + "0x15e00501a0060060052160050060050310061b6005216005168005141006", + "0x12500521600512500519900600c00521600500c00519b00615e005216005", + "0x1b600514a00601200521600501200514800604a00521600504a005146006", + "0x60062160050060120061b601204a12500c15e0060190051b6005216005", + "0x621600503a0050d4006006216005019005168006006216005047005051", + "0x2160051e900518e00600621600520d00519300600621600502000520a006", + "0x2160050060120060062720050061b00060b800521600504400501a006006", + "0x503a0050d4006006216005019005168006006216005040005051006006", + "0x1e900518e00600621600520d00519300600621600502000520a006006216", + "0x62080060062160050060c40060b800521600520700501a006006216005", + "0x61b20052160051b200502d0061b20052160050060d50061b4005216005", + "0x1b00bd00c13d0060bd00521600500613b0061b00052160051b21b400c02f", + "0x60052160050060050310061ae0052160051ac0051410061ac005216005", + "0x12500519900600c00521600500c00519b0060b80052160050b800501a006", + "0x1200521600501200514800602d00521600502d005146006125005216005", + "0x60120061ae01202d12500c0b80060190051ae0052160051ae00514a006", + "0x520a006006216005141005168006006216005146005051006006216005", + "0x1930060062160051e900518e00600621600520d005193006006216005020", + "0x60c10052160050060670060bf00521600500620800600621600501a005", + "0x500613b0061aa0052160050c10bf00c02f0060c10052160050c100502d", + "0xc60052160050c40051410060c40052160051aa1a700c13d0061a7005216", + "0xc00519b00620700521600520700501a006006005216005006005031006", + "0x2d00521600502d00514600612500521600512500519900600c005216005", + "0x2070060190050c60052160050c600514a006012005216005012005148006", + "0x20a00600621600501a0051930060062160050060120060c601202d12500c", + "0x60062160051e900518e00600621600520d005193006006216005020005", + "0x520700501a0060060052160050060050310061a500521600513d005141", + "0x612500521600512500519900600c00521600500c00519b006207005216", + "0x51a500514a00601200521600501200514800602d00521600502d005146", + "0x510060062160050060120061a501202d12500c2070060190051a5005216", + "0x60062160051e900518e00600621600501a005193006006216005208005", + "0x621600502100506900600621600502a00516800600621600520d005193", + "0x2160051a300502d0061a30052160050061780060c8005216005006208006", + "0x13d0060ca00521600500613b00619c0052160051a30c800c02f0061a3005", + "0x500600503100619b0052160050cc0051410060cc00521600519c0ca00c", + "0x600c00521600500c00519b00602500521600502500501a006006005216", + "0x5012005148006026005216005026005146006125005216005125005199", + "0x19b01202612500c02500601900519b00521600519b00514a006012005216", + "0x62160051e900518e00600621600501a005193006006216005006012006", + "0x21600502100506900600621600520d00519300600621600501d005168006", + "0x519f00502d00619f005216005006059006199005216005006208006006", + "0x60d100521600500613b0060cf00521600519f19900c02f00619f005216", + "0x60050310061930052160051950051410061950052160050cf0d100c13d", + "0xc00521600500c00519b00620c00521600520c00501a006006005216005", + "0x12005148006028005216005028005146006125005216005125005199006", + "0x1202812500c20c00601900519300521600519300514a006012005216005", + "0x21600500600c0061e900521600500605b00601a0052160050060b2006193", + "0x2300c27302102000c21600c01000500c010006006216005006125006006", + "0x501d00501900601d00521600501d00501d00600621600500601200620d", + "0x1e00521600c02600501e00602000521600502000501a00602602500c216", + "0x514600602000521600502000501a00600621600500601200620c005274", + "0x21600501e1e900c17a00602500521600502500501d006021005216005021", + "0x21600c20a00517f00620a02a02812521600502502102012505d00601e005", + "0x2f02d00c216005208005181006006216005006012006207005275208005", + "0x501a00600621600500601200613b00527601900521600c02f005061006", + "0x521600502d00501d00602a00521600502a005146006028005216005028", + "0x13d12521600502d02a0281251a500601900521600501901a00c0b600602d", + "0x600621600500601200614800527714600521600c0310050c8006031141", + "0x601700527803900521600c03500519c00603514a00c2160051460051a3", + "0x521600503a00502000603a00521600514a0051e9006006216005006012", + "0x620d00600621600514b00502300603d14b00c21600503c00502100603c", + "0x621600504000502300614e04000c21600503f00502100603f005216005", + "0x15000502600604400521600514e00502500615000521600503d005025006", + "0x621600500601200600627900621600c04415000c20c006150005216005", + "0x21600501e00520a0060062160050190051680060062160050390050ca006", + "0x504700502d006047005216005006207006152005216005006208006006", + "0x615e00521600500613b00615600521600504715200c02f006047005216", + "0x600503100616200521600504a00514100604a00521600515615e00c13d", + "0xc00521600500c00519b00613d00521600513d00501a006006005216005", + "0x12005148006141005216005141005146006125005216005125005199006", + "0x1214112500c13d00601900516200521600516200514a006012005216005", + "0x21600504c00503900604c005216005006035006006216005006012006162", + "0x601200605016d00c27a04e16800c21600c04c14113d12501700604c005", + "0x614b00605100521600500603c00617000521600500603a006006216005", + "0x4000605500521600500603f00617200521600500603d006052005216005", + "0x5517205205117001915000617400521600500614e006057005216005006", + "0x21600504e00514600616800521600516800501a006059005216005174057", + "0x19900600600521600500600503100600c00521600500c00519b00604e005", + "0x21600501e00502d006012005216005012005148006125005216005125005", + "0x1970060390052160050390050cf00601900521600501900501d00601e005", + "0x18117f05d17a05b17801921600503901901e05901212500600c04e1681e9", + "0x6500600621600500601200618600527b18500521600c061005186006061", + "0x6700c216005065005052006065005216005006208006006216005185005", + "0x6b00505700606b005216005069005055006006216005067005172006069", + "0x5d00521600505d00503100606d00521600518a00517400618a005216005", + "0x17f00519900617a00521600517a00519b00617800521600517800501a006", + "0x18100521600518100514800605b00521600505b00514600617f005216005", + "0x601200606d18105b17f17a17805d01900506d00521600506d00514a006", + "0x605d00521600505d00503100618d005216005186005141006006216005", + "0x517f00519900617a00521600517a00519b00617800521600517800501a", + "0x618100521600518100514800605b00521600505b00514600617f005216", + "0x500601200618d18105b17f17a17805d01900518d00521600518d00514a", + "0x1e00520a0060062160050190051680060062160050390050ca006006216", + "0x502d0060700052160050060590060ba005216005006208006006216005", + "0x521600500613b0061960052160050700ba00c02f006070005216005070", + "0x3100619d00521600519a00514100619a00521600519607200c13d006072", + "0x21600500c00519b00616d00521600516d00501a006006005216005006005", + "0x14800605000521600505000514600612500521600512500519900600c005", + "0x12500c16d00601900519d00521600519d00514a006012005216005012005", + "0x14a00516800600621600501700505100600621600500601200619d012050", + "0x620800600621600501e00520a006006216005019005168006006216005", + "0x61a00052160051a000502d0061a00052160050060d5006076005216005", + "0x1a11a200c13d0061a200521600500613b0061a10052160051a007600c02f", + "0x60052160050060050310061a400521600507b00514100607b005216005", + "0x12500519900600c00521600500c00519b00613d00521600513d00501a006", + "0x12005216005012005148006141005216005141005146006125005216005", + "0x60120061a401214112500c13d0060190051a40052160051a400514a006", + "0x514100600621600501900516800600621600501e00520a006006216005", + "0x521600513d00501a0060060052160050060050310061a8005216005148", + "0x514600612500521600512500519900600c00521600500c00519b00613d", + "0x52160051a800514a006012005216005012005148006141005216005141", + "0x13b0050510060062160050060120061a801214112500c13d0060190051a8", + "0x519300600621600502d00516800600621600501e00520a006006216005", + "0x2d0061ab00521600500606700607e00521600500620800600621600501a", + "0x21600500613b00608c0052160051ab07e00c02f0061ab0052160051ab005", + "0x608f00521600508e00514100608e00521600508c08d00c13d00608d005", + "0x500c00519b00602800521600502800501a006006005216005006005031", + "0x602a00521600502a00514600612500521600512500519900600c005216", + "0xc02800601900508f00521600508f00514a006012005216005012005148", + "0x519300600621600501e00520a00600621600500601200608f01202a125", + "0x600521600500600503100609000521600520700514100600621600501a", + "0x12500519900600c00521600500c00519b00602800521600502800501a006", + "0x1200521600501200514800602a00521600502a005146006125005216005", + "0x601200609001202a12500c02800601900509000521600509000514a006", + "0x516800600621600501a00519300600621600520c005051006006216005", + "0x1780060910052160050062080060062160051e9005069006006216005025", + "0x21600509209100c02f00609200521600509200502d006092005216005006", + "0x14100609500521600509309400c13d00609400521600500613b006093005", + "0x21600502000501a006006005216005006005031006096005216005095005", + "0x14600612500521600512500519900600c00521600500c00519b006020005", + "0x21600509600514a006012005216005012005148006021005216005021005", + "0x519300600621600500601200609601202112500c020006019005096005", + "0x2080060062160051e900506900600621600501d00516800600621600501a", + "0x9b00521600509b00502d00609b0052160050060590061b5005216005006", + "0x5f00c13d00605f00521600500613b00609c00521600509b1b500c02f006", + "0x521600500600503100609700521600509f00514100609f00521600509c", + "0x519900600c00521600500c00519b00602300521600502300501a006006", + "0x521600501200514800620d00521600520d005146006125005216005125", + "0xd700609701220d12500c02300601900509700521600509700514a006012", + "0x60200052160050060d900601e005216005006005006019005216005006", + "0x20c00521600500605b0060250052160050060b20060230052160050061b9", + "0x600621600500612500600621600500600c00602a005216005006189006", + "0x600621600500601200602d20700c27c20820a00c21600c12500600c010", + "0x20a00501a00613b02f00c21600501200501900601200521600501200501d", + "0x21600500601200613d00527d02600521600c13b00501e00620a005216005", + "0x501d00620800521600520800514600620a00521600520a00501a006006", + "0x2f20820a12505d00602600521600502620c00c17a00602f00521600502f", + "0x601200614a00527e14800521600c14600517f006146031141125216005", + "0x20d00521600c03900506100603903500c216005148005181006006216005", + "0x60b400603a0052160050350051e900600621600500601200601700527f", + "0x3c00c21600c03a0051b600620d00521600520d02500c0b6006006216005", + "0x1b400603f00521600514b0050b800600621600500601200603d00528014b", + "0x62810050061b000614e00521600503f0051b200604000521600503c005", + "0x52160051500051ac0061500052160050060bd006006216005006012006", + "0x51ae00614e0052160050440051b200604000521600503d0051b4006044", + "0x2160051520050bf00600621600500601200604700528215200521600c14e", + "0x2d00614100521600514100501a00615e0052160051560050c1006156005", + "0x1620051a700616204a00c21600515e14100c1aa00615e00521600515e005", + "0x521600504000505500600621600500601200604c00528302100521600c", + "0xc0c600604e00521600504e0051b400604e0052160051680051e9006168", + "0x1200617000528405016d00c21600c04e0051b6006021005216005021023", + "0x5200521600516d0051b40060510052160050500050b8006006216005006", + "0x62160050060120060062850050061b00061720052160050510051b2006", + "0x51700051b40060570052160050550051ac0060550052160050060bd006", + "0x28617400521600c1720051ae0061720052160050570051b2006052005216", + "0x1780050c10061780052160051740050bf006006216005006012006059005", + "0x5b00521600505b00502d00604a00521600504a00501a00605b005216005", + "0x528702800521600c05d0050dd00605d17a00c21600505b04a00c0db006", + "0x52160050520050550060062160050060c400600621600500601200617f", + "0x501d00603100521600503100514600617a00521600517a00501a006181", + "0x18103117a1251a500602800521600502802a00c187006181005216005181", + "0x601200606700528806500521600c1860050c8006186185061125216005", + "0x18a00521600c06b00519c00606b06900c2160050650051a3006006216005", + "0x502000618d0052160050690051e900600621600500601200606d005289", + "0x21600507000502300619607000c2160050ba0050210060ba00521600518d", + "0x502300619d19a00c21600507200502100607200521600500620d006006", + "0x1a000521600519d00502500607600521600519600502500600621600519a", + "0x1200600628a00621600c1a007600c20c006076005216005076005026006", + "0x2a0060062160050190050df006006216005020005184006006216005006", + "0x600621600502800502300600621600518a0050ca00600621600501e005", + "0x621600502600520a00600621600520d0051680060062160050210050cc", + "0x2160051a200502d0061a20052160050062070061a1005216005006208006", + "0x13d0061a400521600500613b00607b0052160051a21a100c02f0061a2005", + "0x506100501a00607e0052160051a80051410061a800521600507b1a400c", + "0x600c00521600500c00519900600500521600500500519b006061005216", + "0x507e00514a006010005216005010005148006185005216005185005146", + "0x603500600621600500601200607e01018500c00506101d00507e005216", + "0x21600c1ab1850611250170061ab0052160051ab0050390061ab005216005", + "0x9000521600500603a00600621600500601200608f08e00c28b08d08c00c", + "0x521600500603d00609200521600500614b00609100521600500603c006", + "0x21600500614e00609500521600500604000609400521600500603f006093", + "0x1b50050440061b5005216005096095094093092091090019150006096005", + "0x8c00521600508c00501a00600621600509b00515200609c09b00c216005", + "0xc00519900600500521600500500519b00608d00521600508d005146006", + "0x2600521600502600502d00601000521600501000514800600c005216005", + "0x2800502600602100521600502100519f00620d00521600520d00501d006", + "0x1000c00508d08c0200e100618a00521600518a0050cf006028005216005", + "0x1e902000c18200609701a01d1e909f05f01d21600518a02802120d02609c", + "0x21600501a01e00c15e00601d00521600501d01900c0e30061e9005216005", + "0x60062160050060120061bd00528c0a100521600c09700504a00601a005", + "0x2160051c100504c0061c10052160050a10051620061be005216005006208", + "0x14600605f00521600505f00501a0060062160050a60051680060a80a600c", + "0x2160051be00504e0060a80052160050a800501d00609f00521600509f005", + "0xc0ac0050500060ac0aa1bc1252160051be0a809f05f01016d0061be005", + "0x9800c2160050b00051700060062160050060120060a900528d0b0005216", + "0x51720060b41b900c2160050980050520060062160050b20050510060b2", + "0x1b60052160050b60050570060b60052160050b40050550060062160051b9", + "0x1e900519b0061bc0052160051bc00501a0060b80052160051b6005174006", + "0xaa0052160050aa00514600601d00521600501d0051990061e9005216005", + "0x1e91bc01d0050b80052160050b800514a00601a00521600501a005148006", + "0x1a0061b40052160050a90051410060062160050060120060b801a0aa01d", + "0x21600501d0051990061e90052160051e900519b0061bc0052160051bc005", + "0x14a00601a00521600501a0051480060aa0052160050aa00514600601d005", + "0x62160050060120061b401a0aa01d1e91bc01d0051b40052160051b4005", + "0x1e900519b00605f00521600505f00501a0061b20052160051bd005141006", + "0x9f00521600509f00514600601d00521600501d0051990061e9005216005", + "0x1e905f01d0051b20052160051b200514a00601a00521600501a005148006", + "0x50df0060062160050200051840060062160050060120061b201a09f01d", + "0x2300600621600518a0050ca00600621600501e00502a006006216005019", + "0x600621600520d0051680060062160050210050cc006006216005028005", + "0xbd0052160050060590061b000521600500620800600621600502600520a", + "0x613b0061ac0052160050bd1b000c02f0060bd0052160050bd00502d006", + "0x52160050bf0051410060bf0052160051ac1ae00c13d0061ae005216005", + "0x519900600500521600500500519b00608e00521600508e00501a0060c1", + "0x521600501000514800608f00521600508f00514600600c00521600500c", + "0x60120060c101008f00c00508e01d0050c10052160050c100514a006010", + "0x50df00600621600502000518400600621600506d005051006006216005", + "0x2300600621600506900516800600621600501e00502a006006216005019", + "0x600621600520d0051680060062160050210050cc006006216005028005", + "0x1a70052160050061950061aa00521600500620800600621600502600520a", + "0x613b0060c40052160051a71aa00c02f0061a70052160051a700502d006", + "0x52160051a50051410061a50052160050c40c600c13d0060c6005216005", + "0x519900600500521600500500519b00606100521600506100501a0060c8", + "0x521600501000514800618500521600518500514600600c00521600500c", + "0x60120060c801018500c00506101d0050c80052160050c800514a006010", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0xcc00600621600502800502300600621600502600520a00600621600501e", + "0x1a300521600506700514100600621600520d005168006006216005021005", + "0xc00519900600500521600500500519b00606100521600506100501a006", + "0x1000521600501000514800618500521600518500514600600c005216005", + "0x50060120061a301018500c00506101d0051a30052160051a300514a006", + "0x20d0051680060062160050210050cc00600621600517f005051006006216", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0x1800060062160050520050d400600621600502600520a00600621600501e", + "0x600628e0050061b000619c00521600517a00501a00600621600502a005", + "0x60062160050210050cc006006216005059005051006006216005006012", + "0x62160050190050df00600621600502000518400600621600520d005168", + "0x2160050520050d400600621600502600520a00600621600501e00502a006", + "0x50060c400619c00521600504a00501a00600621600502a005180006006", + "0xcc00502d0060cc0052160050060d20060ca005216005006208006006216", + "0x19900521600500613b00619b0052160050cc0ca00c02f0060cc005216005", + "0x501a0060cf00521600519f00514100619f00521600519b19900c13d006", + "0x521600500c00519900600500521600500500519b00619c00521600519c", + "0x514a00601000521600501000514800603100521600503100514600600c", + "0x60062160050060120060cf01003100c00519c01d0050cf0052160050cf", + "0x621600520d0051680060062160050400050d400600621600504c005051", + "0x21600501e00502a0060062160050190050df006006216005020005184006", + "0x502300518e00600621600502a00518000600621600502600520a006006", + "0x500601200600628f0050061b00060d100521600504a00501a006006216", + "0x20d0051680060062160050400050d4006006216005047005051006006216", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0x18000600621600502300518e00600621600502600520a00600621600501e", + "0x60062160050060c40060d100521600514100501a00600621600502a005", + "0x521600519300502d0061930052160050060d5006195005216005006208", + "0xc13d0060d400521600500613b0060d200521600519319500c02f006193", + "0x2160050d100501a0060d500521600518e00514100618e0052160050d20d4", + "0x14600600c00521600500c00519900600500521600500500519b0060d1005", + "0x2160050d500514a006010005216005010005148006031005216005031005", + "0x170050510060062160050060120060d501003100c0050d101d0050d5005", + "0x50df006006216005020005184006006216005035005168006006216005", + "0x18e00600621600502600520a00600621600501e00502a006006216005019", + "0x600621600502500519300600621600502a005180006006216005023005", + "0x52160050d700502d0060d7005216005006067006197005216005006208", + "0xc13d00618900521600500613b0060d90052160050d719700c02f0060d7", + "0x21600514100501a0060dd0052160050db0051410060db0052160050d9189", + "0x14600600c00521600500c00519900600500521600500500519b006141005", + "0x2160050dd00514a006010005216005010005148006031005216005031005", + "0x250051930060062160050060120060dd01003100c00514101d0050dd005", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0x18000600621600502300518e00600621600502600520a00600621600501e", + "0x521600514100501a00618700521600514a00514100600621600502a005", + "0x514600600c00521600500c00519900600500521600500500519b006141", + "0x521600518700514a006010005216005010005148006031005216005031", + "0x513d00505100600621600500601200618701003100c00514101d005187", + "0x2000518400600621600502500519300600621600502a005180006006216", + "0x518e00600621600501e00502a0060062160050190050df006006216005", + "0x20800600621600520c00506900600621600502f005168006006216005023", + "0xdf0052160050df00502d0060df005216005006178006184005216005006", + "0x18200c13d00618200521600500613b0060e10052160050df18400c02f006", + "0x521600520a00501a0061800052160050e30051410060e30052160050e1", + "0x514600600c00521600500c00519900600500521600500500519b00620a", + "0x521600518000514a006010005216005010005148006208005216005208", + "0x502a00518000600621600500601200618001020800c00520a01d005180", + "0x190050df006006216005020005184006006216005025005193006006216", + "0x518e00600621600501200516800600621600501e00502a006006216005", + "0x590060e500521600500620800600621600520c005069006006216005023", + "0x2160051790e500c02f00617900521600517900502d006179005216005006", + "0x1410061730052160050e717600c13d00617600521600500613b0060e7005", + "0x21600500500519b00620700521600520700501a0060e9005216005173005", + "0x14800602d00521600502d00514600600c00521600500c005199006005005", + "0x2d00c00520701d0050e90052160050e900514a006010005216005010005", + "0x50060d900601e0052160050060050060190052160050060d70060e9010", + "0x605b0060250052160050060e50060230052160050060b2006020005216", + "0x612500600621600500600c00602a00521600500618900620c005216005", + "0x601200602d20700c29020820a00c21600c12500600c010006006216005", + "0x13b02f00c21600501200501900601200521600501200501d006006216005", + "0x613d00529102600521600c13b00501e00620a00521600520a00501a006", + "0xc21600502f00517900602f00521600502f00501d006006216005006012", + "0x29220d00521600c0310050e700602600521600502620c00c17a006031141", + "0x20800514600620a00521600520a00501a006006216005006012006146005", + "0x521600520d02500c17600614100521600514100501d006208005216005", + "0x521600c03500517f00603514a14812521600514120820a12505d00620d", + "0x603c03a00c216005039005181006006216005006012006017005293039", + "0x3a0051e900600621600500601200614b00529402100521600c03c005061", + "0x602100521600502102300c0b60060062160050060b400603d005216005", + "0x50b800600621600500601200614e00529504003f00c21600c03d0051b6", + "0x52160051500051b200604400521600503f0051b4006150005216005040", + "0x470052160050060bd0060062160050060120060062960050061b0006152", + "0x1560051b200604400521600514e0051b40061560052160050470051ac006", + "0x21600500601200604a00529715e00521600c1520051ae006152005216005", + "0x501a00604c0052160051620050c100616200521600515e0050bf006006", + "0x21600504c14800c0db00604c00521600504c00502d006148005216005148", + "0x621600500601200616d00529802800521600c04e0050dd00604e16800c", + "0x21600516800501a0060500052160050440050550060062160050060c4006", + "0x18700605000521600505000501d00614a00521600514a005146006168005", + "0x605205117012521600505014a1681251a500602800521600502802a00c", + "0x1720051a300600621600500601200605500529917200521600c0520050c8", + "0x500601200617800529a05900521600c17400519c00617405700c216005", + "0x2100617a00521600505b00502000605b0052160050570051e9006006216", + "0x521600500620d00600621600505d00502300617f05d00c21600517a005", + "0x502500600621600506100502300618506100c216005181005021006181", + "0x521600518600502600606500521600518500502500618600521600517f", + "0x518400600621600500601200600629b00621600c06518600c20c006186", + "0xca00600621600501e00502a0060062160050190050df006006216005020", + "0x6006216005021005168006006216005028005023006006216005059005", + "0x6700521600500620800600621600502600520a00600621600520d005173", + "0x6906700c02f00606900521600506900502d006069005216005006207006", + "0x6d00521600506b18a00c13d00618a00521600500613b00606b005216005", + "0x500519b00617000521600517000501a00618d00521600506d005141006", + "0x5100521600505100514600600c00521600500c005199006005005216005", + "0x517001d00518d00521600518d00514a006010005216005010005148006", + "0x50390060ba00521600500603500600621600500601200618d01005100c", + "0x19a07200c29c19607000c21600c0ba0511701250170060ba0052160050ba", + "0x7600521600500603c00619d00521600500603a006006216005006012006", + "0x521600500603f0061a100521600500603d0061a000521600500614b006", + "0x7619d0191500061a400521600500614e00607b0052160050060400061a2", + "0x1520061ab07e00c2160051a80050440061a80052160051a407b1a21a11a0", + "0x521600519600514600607000521600507000501a00600621600507e005", + "0x514800600c00521600500c00519900600500521600500500519b006196", + "0x521600520d0050e900602600521600502600502d006010005216005010", + "0x50cf00602800521600502800502600602100521600502100501d00620d", + "0x505902802120d0261ab01000c0051960700200eb006059005216005059", + "0xc0e30061e90052160051e902000c18200608e01a01d1e908d08c01d216", + "0xc08e00516f00601a00521600501a01e00c15e00601d00521600501d019", + "0x609100521600500620800600621600500601200609000529d08f005216", + "0x9300516900609409300c21600509200517100609200521600508f00516e", + "0x608d00521600508d00514600608c00521600508c00501a006006216005", + "0x8d08c0100ef00609100521600509100504e00609400521600509400516c", + "0x609c00529e09b00521600c1b50050500061b5096095125216005091094", + "0x21600509f00505100609f05f00c21600509b005170006006216005006012", + "0x50550060062160050970051720060a109700c21600505f005052006006", + "0x52160051be0051740061be0052160051bd0050570061bd0052160050a1", + "0x51990061e90052160051e900519b00609500521600509500501a0061c1", + "0x521600501a00514800609600521600509600514600601d00521600501d", + "0x60120061c101a09601d1e909501d0051c10052160051c100514a00601a", + "0x609500521600509500501a0060a600521600509c005141006006216005", + "0x509600514600601d00521600501d0051990061e90052160051e900519b", + "0x50a60052160050a600514a00601a00521600501a005148006096005216", + "0x52160050900051410060062160050060120060a601a09601d1e909501d", + "0x51990061e90052160051e900519b00608c00521600508c00501a0060a8", + "0x521600501a00514800608d00521600508d00514600601d00521600501d", + "0x60120060a801a08d01d1e908c01d0050a80052160050a800514a00601a", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0x1680060062160050280050230060062160050590050ca00600621600501e", + "0x600621600502600520a00600621600520d005173006006216005021005", + "0x52160050aa00502d0060aa0052160050060590061bc005216005006208", + "0xc13d0060b000521600500613b0060ac0052160050aa1bc00c02f0060aa", + "0x21600507200501a0060980052160050a90051410060a90052160050ac0b0", + "0x14600600c00521600500c00519900600500521600500500519b006072005", + "0x21600509800514a00601000521600501000514800619a00521600519a005", + "0x17800505100600621600500601200609801019a00c00507201d005098005", + "0x502a0060062160050190050df006006216005020005184006006216005", + "0x16800600621600502800502300600621600505700516800600621600501e", + "0x600621600502600520a00600621600520d005173006006216005021005", + "0x52160051b900502d0061b90052160050061950060b2005216005006208", + "0xc13d0060b600521600500613b0060b40052160051b90b200c02f0061b9", + "0x21600517000501a0060b80052160051b60051410061b60052160050b40b6", + "0x14600600c00521600500c00519900600500521600500500519b006170005", + "0x2160050b800514a006010005216005010005148006051005216005051005", + "0x200051840060062160050060120060b801005100c00517001d0050b8005", + "0x520a00600621600501e00502a0060062160050190050df006006216005", + "0x173006006216005021005168006006216005028005023006006216005026", + "0x521600517000501a0061b400521600505500514100600621600520d005", + "0x514600600c00521600500c00519900600500521600500500519b006170", + "0x52160051b400514a006010005216005010005148006051005216005051", + "0x516d0050510060062160050060120061b401005100c00517001d0051b4", + "0x2000518400600621600520d005173006006216005021005168006006216", + "0x520a00600621600501e00502a0060062160050190050df006006216005", + "0x1a00600621600502a0051800060062160050440050d4006006216005026", + "0x5100600621600500601200600629f0050061b00061b2005216005168005", + "0x600621600520d00517300600621600502100516800600621600504a005", + "0x621600501e00502a0060062160050190050df006006216005020005184", + "0x21600502a0051800060062160050440050d400600621600502600520a006", + "0x2160050062080060062160050060c40061b200521600514800501a006006", + "0xc02f0060bd0052160050bd00502d0060bd0052160050060d20061b0005", + "0x2160051ac1ae00c13d0061ae00521600500613b0061ac0052160050bd1b0", + "0x19b0061b20052160051b200501a0060c10052160050bf0051410060bf005", + "0x21600514a00514600600c00521600500c005199006005005216005005005", + "0x1d0050c10052160050c100514a00601000521600501000514800614a005", + "0x600621600514b0050510060062160050060120060c101014a00c0051b2", + "0x621600502000518400600621600520d00517300600621600503a005168", + "0x21600502600520a00600621600501e00502a0060062160050190050df006", + "0x21600500620800600621600502300519300600621600502a005180006006", + "0xc02f0061a70052160051a700502d0061a70052160050060d50061aa005", + "0x2160050c40c600c13d0060c600521600500613b0060c40052160051a71aa", + "0x19b00614800521600514800501a0060c80052160051a50051410061a5005", + "0x21600514a00514600600c00521600500c005199006005005216005005005", + "0x1d0050c80052160050c800514a00601000521600501000514800614a005", + "0x60062160050230051930060062160050060120060c801014a00c005148", + "0x62160050190050df00600621600502000518400600621600520d005173", + "0x21600502a00518000600621600502600520a00600621600501e00502a006", + "0x519b00614800521600514800501a0061a3005216005017005141006006", + "0x521600514a00514600600c00521600500c005199006005005216005005", + "0x14801d0051a30052160051a300514a00601000521600501000514800614a", + "0x1930060062160051460050510060062160050060120061a301014a00c005", + "0x60062160050190050df006006216005020005184006006216005023005", + "0x621600502a00518000600621600502600520a00600621600501e00502a", + "0x521600500620800600621600502500516a006006216005141005168006", + "0x19c00c02f0060ca0052160050ca00502d0060ca00521600500606700619c", + "0x52160050cc19b00c13d00619b00521600500613b0060cc0052160050ca", + "0x519b00620a00521600520a00501a00619f005216005199005141006199", + "0x521600520800514600600c00521600500c005199006005005216005005", + "0x20a01d00519f00521600519f00514a006010005216005010005148006208", + "0x19300600621600513d00505100600621600500601200619f01020800c005", + "0x60062160050190050df006006216005020005184006006216005023005", + "0x621600502a00518000600621600502f00516800600621600501e00502a", + "0x521600500620800600621600520c00506900600621600502500516a006", + "0xcf00c02f0060d10052160050d100502d0060d10052160050061780060cf", + "0x521600519519300c13d00619300521600500613b0061950052160050d1", + "0x519b00620a00521600520a00501a0060d40052160050d20051410060d2", + "0x521600520800514600600c00521600500c005199006005005216005005", + "0x20a01d0050d40052160050d400514a006010005216005010005148006208", + "0x1680060062160050230051930060062160050060120060d401020800c005", + "0x60062160050190050df006006216005020005184006006216005012005", + "0x621600502a00518000600621600520c00506900600621600501e00502a", + "0x521600500605900618e00521600500620800600621600502500516a006", + "0x13b0061970052160050d518e00c02f0060d50052160050d500502d0060d5", + "0x2160050d90051410060d90052160051970d700c13d0060d7005216005006", + "0x19900600500521600500500519b00620700521600520700501a006189005", + "0x21600501000514800602d00521600502d00514600600c00521600500c005", + "0xd700618901002d00c00520701d00518900521600518900514a006010005", + "0x12500600621600500600c00601a00521600500600500601d005216005006", + "0x1200602102000c2a01e901e00c21600c00c00600c010006006216005006", + "0x2300c21600501000501900601000521600501000501d006006216005006", + "0x260052a102500521600c20d00501e00601e00521600501e00501a00620d", + "0x21600520c00502000620c0052160050230051e9006006216005006012006", + "0x20d00600621600502a00502300620a02a00c216005028005021006028005", + "0x21600520700502300602d20700c216005208005021006208005216005006", + "0x502600613b00521600502d00502500602f00521600520a005025006006", + "0x2160050060120060062a200621600c13b02f00c20c00602f00521600502f", + "0x502500520a00600621600501a00502a00600621600501d0050df006006", + "0x14100502d00614100521600500620700613d005216005006208006006216", + "0x14600521600500613b00603100521600514113d00c02f006141005216005", + "0x501a00614a00521600514800514100614800521600503114600c13d006", + "0x52160051e900514600600500521600500500519900601e00521600501e", + "0x1e01200514a00521600514a00514a0061250052160051250051480061e9", + "0x503900603500521600500603500600621600500601200614a1251e9005", + "0x3c03a00c2a301703900c21600c0351e901e125017006035005216005035", + "0x3d00521600500603c00614b00521600500603a006006216005006012006", + "0x521600500603f00604000521600500603d00603f00521600500614b006", + "0x3d14b01915000604400521600500614e00615000521600500604000614e", + "0x15200615604700c21600515200504400615200521600504415014e04003f", + "0x521600501700514600603900521600503900501a006006216005047005", + "0x502d006125005216005125005148006005005216005005005199006017", + "0x1204a15e01221600502515612500501703901d0f1006025005216005025", + "0x1900521600501901a00c15e00601200521600501201d00c0e3006162019", + "0x62080060062160050060120061680052a404c00521600c16200504a006", + "0x5000c21600516d00504c00616d00521600504c00516200604e005216005", + "0x4a00514600615e00521600515e00501a006006216005050005168006170", + "0x4e00521600504e00504e00617000521600517000501d00604a005216005", + "0x521600c17200505000617205205112521600504e17004a15e01016d006", + "0x605917400c2160050550051700060062160050060120060570052a5055", + "0x517800517200605b17800c216005174005052006006216005059005051", + "0x17400605d00521600517a00505700617a00521600505b005055006006216", + "0x21600501200519900605100521600505100501a00617f00521600505d005", + "0x14a006019005216005019005148006052005216005052005146006012005", + "0x600621600500601200617f01905201205101200517f00521600517f005", + "0x501200519900605100521600505100501a006181005216005057005141", + "0x6019005216005019005148006052005216005052005146006012005216", + "0x621600500601200618101905201205101200518100521600518100514a", + "0x1200519900615e00521600515e00501a006061005216005168005141006", + "0x1900521600501900514800604a00521600504a005146006012005216005", + "0x21600500601200606101904a01215e01200506100521600506100514a006", + "0x502500520a00600621600501a00502a00600621600501d0050df006006", + "0x18600502d006186005216005006059006185005216005006208006006216", + "0x6700521600500613b00606500521600518618500c02f006186005216005", + "0x501a00606b00521600506900514100606900521600506506700c13d006", + "0x521600503c00514600600500521600500500519900603a00521600503a", + "0x3a01200506b00521600506b00514a00612500521600512500514800603c", + "0x50df00600621600502600505100600621600500601200606b12503c005", + "0x20800600621600502300516800600621600501a00502a00600621600501d", + "0x6d00521600506d00502d00606d00521600500617800618a005216005006", + "0xba00c13d0060ba00521600500613b00618d00521600506d18a00c02f006", + "0x521600501e00501a00619600521600507000514100607000521600518d", + "0x51480061e90052160051e900514600600500521600500500519900601e", + "0x1961251e900501e01200519600521600519600514a006125005216005125", + "0x621600501a00502a006006216005010005168006006216005006012006", + "0x521600500605900607200521600500620800600621600501d0050df006", + "0x13b00619d00521600519a07200c02f00619a00521600519a00502d00619a", + "0x2160051a00051410061a000521600519d07600c13d006076005216005006", + "0x14600600500521600500500519900602000521600502000501a0061a1005", + "0x2160051a100514a006125005216005125005148006021005216005021005", + "0xc00500c0100060062160050061250061a11250210050200120051a1005", + "0x501200501a00600621600500601200601a01900c2a601d01200c21600c", + "0x1e12521600501001200c06b00601000521600501000501d006012005216", + "0x60062160050060120060230052a702100521600c02000518a0060201e9", + "0x502500502100602500521600520d00502000620d0052160051e90051e9", + "0x2100602800521600500620d00600621600502600502300620c02600c216", + "0x21600520c00502500600621600502a00502300620a02a00c216005028005", + "0x20c00620800521600520800502600620700521600520a005025006208005", + "0x21600502100506d0060062160050060120060062a800621600c20720800c", + "0x502f00502d00602f00521600500620700602d005216005006208006006", + "0x613d00521600500613b00613b00521600502f02d00c02f00602f005216", + "0x600503100603100521600514100514100614100521600513b13d00c13d", + "0x1d00521600501d00514600601e00521600501e00501a006006005216005", + "0x1e00601200503100521600503100514a006125005216005125005148006", + "0x14600503900614600521600500603500600621600500601200603112501d", + "0x603903500c2a914a14800c21600c14601d01e125017006146005216005", + "0x603a00521600500603c00601700521600500603a006006216005006012", + "0x3d00521600500603f00614b00521600500603d00603c00521600500614b", + "0x3c03a01701915000604000521600500614e00603f005216005006040006", + "0x14a00514600614800521600514800501a00614e00521600504003f03d14b", + "0x12500521600512500514800600600521600500600503100614a005216005", + "0x1221600502114e12500614a14801d16b00602100521600502100518d006", + "0x500601200604a0052aa15e00521600c156005186006156047152044150", + "0x16200505200616200521600500620800600621600515e005065006006216", + "0x4e00521600516800505500600621600504c00517200616804c00c216005", + "0x15200503100605000521600516d00517400616d00521600504e005057006", + "0x4400521600504400514600615000521600515000501a006152005216005", + "0x15015201200505000521600505000514a006047005216005047005148006", + "0x503100617000521600504a005141006006216005006012006050047044", + "0x521600504400514600615000521600515000501a006152005216005152", + "0x15201200517000521600517000514a006047005216005047005148006044", + "0x620800600621600502100506d006006216005006012006170047044150", + "0x605200521600505200502d006052005216005006059006051005216005", + "0x17205500c13d00605500521600500613b00617200521600505205100c02f", + "0x6005216005006005031006174005216005057005141006057005216005", + "0x12500514800603900521600503900514600603500521600503500501a006", + "0x617412503903500601200517400521600517400514a006125005216005", + "0x60062160051e9005168006006216005023005051006006216005006012", + "0x521600517800502d006178005216005006178006059005216005006208", + "0xc13d00617a00521600500613b00605b00521600517805900c02f006178", + "0x21600500600503100617f00521600505d00514100605d00521600505b17a", + "0x14800601d00521600501d00514600601e00521600501e00501a006006005", + "0x12501d01e00601200517f00521600517f00514a006125005216005125005", + "0x521600500620800600621600501000516800600621600500601200617f", + "0x18100c02f00606100521600506100502d006061005216005006059006181", + "0x521600518518600c13d00618600521600500613b006185005216005061", + "0x501a006006005216005006005031006067005216005065005141006065", + "0x521600512500514800601a00521600501a005146006019005216005019", + "0x500612500606712501a01900601200506700521600506700514a006125", + "0x500601200601901d00c2ab01201000c21600c00500600c010006006216", + "0x2100601e00521600501a00502000601a0052160051250051e9006006216", + "0x521600500620d0060062160051e90050230060201e900c21600501e005", + "0x502500600621600502300502300620d02300c216005021005021006021", + "0x521600502500502600602600521600520d005025006025005216005020", + "0x60062ac00621600c02602500c20c00601000521600501000501a006025", + "0x602800521600500620700620c005216005006208006006216005006012", + "0x500613b00602a00521600502820c00c02f00602800521600502800502d", + "0x20700521600520800514100620800521600502a20a00c13d00620a005216", + "0xc00514800601200521600501200514600601000521600501000501a006", + "0x1200620700c01201001000520700521600520700514a00600c005216005", + "0x602d00521600502d00503900602d005216005006035006006216005006", + "0x621600500601200614113d00c2ad13b02f00c21600c02d012010125017", + "0x521600500614b00614600521600500603c00603100521600500603a006", + "0x21600500604000603500521600500603f00614a00521600500603d006148", + "0x501703903514a14814603101915000601700521600500614e006039005", + "0x600621600503c00515200614b03c00c21600503a00504400603a005216", + "0x500c00514800613b00521600513b00514600602f00521600502f00501a", + "0x50f500614e04003f03d01021600514b00c13b02f0100f300600c005216", + "0x52160050062080060062160050060120060440052ae15000521600c14e", + "0x6d00615e15600c216005047005096006047005216005150005161006152", + "0x521600515200504e00615e00521600515e00518d006006216005156005", + "0x5200600621600516200505100616204a00c21600515215e00c1b5006152", + "0x21600516800505500600621600504c00517200616804c00c21600504a005", + "0x1a00605000521600516d00517400616d00521600504e00505700604e005", + "0x21600504000514800603f00521600503f00514600603d00521600503d005", + "0x500601200605004003f03d01000505000521600505000514a006040005", + "0x14600603d00521600503d00501a006170005216005044005141006006216", + "0x21600517000514a00604000521600504000514800603f00521600503f005", + "0x521600500620800600621600500601200617004003f03d010005170005", + "0x5100c02f00605200521600505200502d006052005216005006059006051", + "0x521600517205500c13d00605500521600500613b006172005216005052", + "0x514600613d00521600513d00501a006174005216005057005141006057", + "0x521600517400514a00600c00521600500c005148006141005216005141", + "0x621600512500516800600621600500601200617400c14113d010005174", + "0x21600517800502d006178005216005006059006059005216005006208006", + "0x13d00617a00521600500613b00605b00521600517805900c02f006178005", + "0x501d00501a00617f00521600505d00514100605d00521600505b17a00c", + "0x600c00521600500c00514800601900521600501900514600601d005216", + "0x600621600500612500617f00c01901d01000517f00521600517f00514a", + "0x600621600500601200601901d00c2af01201000c21600c00500600c010", + "0x501e00502100601e00521600501a00502000601a0052160051250051e9", + "0x2100602100521600500620d0060062160051e90050230060201e900c216", + "0x21600502000502500600621600502300502300620d02300c216005021005", + "0x1a00602500521600502500502600602600521600520d005025006025005", + "0x50060120060062b000621600c02602500c20c006010005216005010005", + "0x2800502d00602800521600500620700620c005216005006208006006216", + "0x20a00521600500613b00602a00521600502820c00c02f006028005216005", + "0x501a00620700521600520800514100620800521600502a20a00c13d006", + "0x521600500c005148006012005216005012005146006010005216005010", + "0x21600500601200620700c01201001000520700521600520700514a00600c", + "0x1012501700602d00521600502d00503900602d005216005006035006006", + "0x603a00600621600500601200614113d00c2b113b02f00c21600c02d012", + "0x3d00614800521600500614b00614600521600500603c006031005216005", + "0x603900521600500604000603500521600500603f00614a005216005006", + "0x3a00521600501703903514a14814603101915000601700521600500614e", + "0x2f00501a00600621600503c00515200614b03c00c21600503a005044006", + "0xc00521600500c00514800613b00521600513b00514600602f005216005", + "0x21600c14e00508f00614e04003f03d01021600514b00c13b02f01015f006", + "0x900061520052160050062080060062160050060120060440052b2150005", + "0x51560051ab00615e15600c216005047005091006047005216005150005", + "0x9200615200521600515200504e00615e00521600515e00508c006006216", + "0x504a00505200600621600516200505100616204a00c21600515215e00c", + "0x604e00521600516800505500600621600504c00517200616804c00c216", + "0x503d00501a00605000521600516d00517400616d00521600504e005057", + "0x604000521600504000514800603f00521600503f00514600603d005216", + "0x600621600500601200605004003f03d01000505000521600505000514a", + "0x503f00514600603d00521600503d00501a006170005216005044005141", + "0x517000521600517000514a00604000521600504000514800603f005216", + "0x5900605100521600500620800600621600500601200617004003f03d010", + "0x21600505205100c02f00605200521600505200502d006052005216005006", + "0x14100605700521600517205500c13d00605500521600500613b006172005", + "0x21600514100514600613d00521600513d00501a006174005216005057005", + "0x1000517400521600517400514a00600c00521600500c005148006141005", + "0x620800600621600512500516800600621600500601200617400c14113d", + "0x617800521600517800502d006178005216005006059006059005216005", + "0x5b17a00c13d00617a00521600500613b00605b00521600517805900c02f", + "0x1d00521600501d00501a00617f00521600505d00514100605d005216005", + "0x17f00514a00600c00521600500c005148006019005216005019005146006", + "0x500c01000600621600500612500617f00c01901d01000517f005216005", + "0x1200501a00600621600500601200601a01900c2b301d01200c21600c00c", + "0x12521600501001200c1a800601000521600501000501d006012005216005", + "0x62160050060120060230052b402100521600c02000507e0060201e901e", + "0x2500502100602500521600520d00502000620d0052160051e90051e9006", + "0x602800521600500620d00600621600502600502300620c02600c216005", + "0x520c00502500600621600502a00502300620a02a00c216005028005021", + "0x620800521600520800502600620700521600520a005025006208005216", + "0x50210051ab0060062160050060120060062b500621600c20720800c20c", + "0x2f00502d00602f00521600500620700602d005216005006208006006216", + "0x13d00521600500613b00613b00521600502f02d00c02f00602f005216005", + "0x503100603100521600514100514100614100521600513b13d00c13d006", + "0x521600501d00514600601e00521600501e00501a006006005216005006", + "0x601200503100521600503100514a00612500521600512500514800601d", + "0x503900614600521600500603500600621600500601200603112501d01e", + "0x3903500c2b614a14800c21600c14601d01e125017006146005216005146", + "0x3a00521600500603c00601700521600500603a006006216005006012006", + "0x521600500603f00614b00521600500603d00603c00521600500614b006", + "0x3a01701915000604000521600500614e00603f00521600500604000603d", + "0x514600614800521600514800501a00614e00521600504003f03d14b03c", + "0x521600512500514800600600521600500600503100614a00521600514a", + "0x21600502114e12500614a14801d15b00602100521600502100508c006125", + "0x601200604a0052b715e00521600c156005186006156047152044150012", + "0x505200616200521600500620800600621600515e005065006006216005", + "0x521600516800505500600621600504c00517200616804c00c216005162", + "0x503100605000521600516d00517400616d00521600504e00505700604e", + "0x521600504400514600615000521600515000501a006152005216005152", + "0x15201200505000521600505000514a006047005216005047005148006044", + "0x3100617000521600504a005141006006216005006012006050047044150", + "0x21600504400514600615000521600515000501a006152005216005152005", + "0x1200517000521600517000514a006047005216005047005148006044005", + "0x2080060062160050210051ab006006216005006012006170047044150152", + "0x5200521600505200502d006052005216005006059006051005216005006", + "0x5500c13d00605500521600500613b00617200521600505205100c02f006", + "0x5216005006005031006174005216005057005141006057005216005172", + "0x514800603900521600503900514600603500521600503500501a006006", + "0x17412503903500601200517400521600517400514a006125005216005125", + "0x62160051e9005168006006216005023005051006006216005006012006", + "0x21600517800502d006178005216005006178006059005216005006208006", + "0x13d00617a00521600500613b00605b00521600517805900c02f006178005", + "0x500600503100617f00521600505d00514100605d00521600505b17a00c", + "0x601d00521600501d00514600601e00521600501e00501a006006005216", + "0x1d01e00601200517f00521600517f00514a006125005216005125005148", + "0x21600500620800600621600501000516800600621600500601200617f125", + "0xc02f00606100521600506100502d006061005216005006059006181005", + "0x21600518518600c13d00618600521600500613b006185005216005061181", + "0x1a006006005216005006005031006067005216005065005141006065005", + "0x21600512500514800601a00521600501a005146006019005216005019005", + "0x612500606712501a01900601200506700521600506700514a006125005", + "0x601200601a01900c2b801d01200c21600c00c00500c010006006216005", + "0x601000521600501000501d00601200521600501200501a006006216005", + "0x52b902100521600c02000518a0060201e901e12521600501001200c06b", + "0x51e900501d00601e00521600501e00501a006006216005006012006023", + "0x21600c02600507e00602602520d1252160051e901e00c1a80061e9005216", + "0x602a0052160050250051e90060062160050060120060280052ba20c005", + "0x20800502300620720800c21600520a00502100620a00521600502a005020", + "0x613b02f00c21600502d00502100602d00521600500620d006006216005", + "0x21600513b00502500613d00521600520700502500600621600502f005023", + "0x62bb00621600c14113d00c20c00613d00521600513d005026006141005", + "0x621600502100506d00600621600520c0051ab006006216005006012006", + "0x21600514600502d006146005216005006207006031005216005006208006", + "0x13d00614a00521600500613b00614800521600514603100c02f006146005", + "0x500600503100603900521600503500514100603500521600514814a00c", + "0x601d00521600501d00514600620d00521600520d00501a006006005216", + "0x1d20d00601200503900521600503900514a006125005216005125005148", + "0x5017005039006017005216005006035006006216005006012006039125", + "0x1200603d14b00c2bc03c03a00c21600c01701d20d125017006017005216", + "0x14b00604000521600500603c00603f00521600500603a006006216005006", + "0x604400521600500603f00615000521600500603d00614e005216005006", + "0x15014e04003f01915000604700521600500614e006152005216005006040", + "0x503c00514600603a00521600503a00501a006156005216005047152044", + "0x612500521600512500514800600600521600500600503100603c005216", + "0x3c03a01916600620c00521600520c00508c00602100521600502100518d", + "0x521600c16800518600616804c16204a15e01221600520c021156125006", + "0x620800600621600504e00506500600621600500601200616d0052bd04e", + "0x621600517000517200605117000c216005050005052006050005216005", + "0x172005174006172005216005052005057006052005216005051005055006", + "0x15e00521600515e00501a006162005216005162005031006055005216005", + "0x5500514a00604c00521600504c00514800604a00521600504a005146006", + "0x514100600621600500601200605504c04a15e162012005055005216005", + "0x521600515e00501a00616200521600516200503100605700521600516d", + "0x514a00604c00521600504c00514800604a00521600504a00514600615e", + "0x1ab00600621600500601200605704c04a15e162012005057005216005057", + "0x617400521600500620800600621600502100506d00600621600520c005", + "0x505917400c02f00605900521600505900502d006059005216005006059", + "0x617a00521600517805b00c13d00605b00521600500613b006178005216", + "0x514b00501a00600600521600500600503100605d00521600517a005141", + "0x612500521600512500514800603d00521600503d00514600614b005216", + "0x621600500601200605d12503d14b00601200505d00521600505d00514a", + "0x21600502100506d006006216005025005168006006216005028005051006", + "0x518100502d00618100521600500606700617f005216005006208006006", + "0x618500521600500613b00606100521600518117f00c02f006181005216", + "0x600503100606500521600518600514100618600521600506118500c13d", + "0x1d00521600501d00514600620d00521600520d00501a006006005216005", + "0x20d00601200506500521600506500514a006125005216005125005148006", + "0x1e900516800600621600502300505100600621600500601200606512501d", + "0x502d006069005216005006178006067005216005006208006006216005", + "0x521600500613b00606b00521600506906700c02f006069005216005069", + "0x3100618d00521600506d00514100606d00521600506b18a00c13d00618a", + "0x21600501d00514600601e00521600501e00501a006006005216005006005", + "0x1200518d00521600518d00514a00612500521600512500514800601d005", + "0x20800600621600501000516800600621600500601200618d12501d01e006", + "0x7000521600507000502d0060700052160050060590060ba005216005006", + "0x7200c13d00607200521600500613b0061960052160050700ba00c02f006", + "0x521600500600503100619d00521600519a00514100619a005216005196", + "0x514800601a00521600501a00514600601900521600501900501a006006", + "0x19d12501a01900601200519d00521600519d00514a006125005216005125", + "0x100052be12500c00c21600c0050051b60060050052160050060051e9006", + "0x21600500c0051b40060120052160051250050b8006006216005006012006", + "0x50060120060062bf0050061b00060190052160050120051b200601d005", + "0x51b400601e00521600501a0051ac00601a0052160050060bd006006216", + "0x521600501d00505500601900521600501e0051b200601d005216005010", + "0x210052c002000521600c0190051ae0061e90052160051e900501d0061e9", + "0x2160050230050c10060230052160050200050bf006006216005006012006", + "0xe90061e90052160051e900501d00602500521600520d00516400620d005", + "0x210050510060062160050060120060251e900c005025005216005025005", + "0x1d00620c0052160050260050fa0060260052160050060bd006006216005", + "0xc400620c1e900c00520c00521600520c0050e90061e90052160051e9005", + "0x1901000c21600501000515700601d005216005006208006006216005006", + "0x21600501a0050fe00620d0230210201e901e01a0192160050190050fc006", + "0x50210051010060062160051e900515800600621600501e005155006006", + "0x600501a00600621600520d005153006006216005023005103006006216", + "0xc00521600500c005031006005005216005005005146006006005216005", + "0x502d00602501200c216005012005105006125005216005125005148006", + "0x2820c02601221600502502012500c00500601d151006025005216005025", + "0x60062160050060120062070052c120800521600c20a00510700620a02a", + "0x502f02d00c14d00602f00521600500610900602d00521600520800514f", + "0x614100521600513d01200c14d00613d00521600500610900613b005216", + "0x502800503100620c00521600520c00514600602600521600502600501a", + "0x613b00521600513b00502d00602a00521600502a005148006028005216", + "0x20c02601a14c00601d00521600501d00504e00614100521600514100502d", + "0x21600c03500510c00603514a14814603101221600501001d14113b02a028", + "0x3c03a1252160050390051490060062160050060120060170052c2039005", + "0x503a00505200600621600514b00505100600621600503c00520a00614b", + "0x604000521600503f00505500600621600503d00517200603f03d00c216", + "0x503100501a00615000521600514e00517400614e005216005040005057", + "0x6148005216005148005031006146005216005146005146006031005216", + "0x14814603101200515000521600515000514a00614a00521600514a005148", + "0x3100501a00604400521600501700514100600621600500601200615014a", + "0x148005216005148005031006146005216005146005146006031005216005", + "0x14603101200504400521600504400514a00614a00521600514a005148006", + "0x1d00517200600621600501000515200600621600500601200604414a148", + "0x1a00615200521600520700514100600621600501200520a006006216005", + "0x21600502800503100620c00521600520c005146006026005216005026005", + "0x1200515200521600515200514a00602a00521600502a005148006028005", + "0x1000c00c21600500c0051470060062160050060c400615202a02820c026", + "0x1d00502000601d0052160050120051e900601200521600501000510f006", + "0x621600501a00502300601e01a00c216005019005021006019005216005", + "0x2000502d0060200052160051e90051bd0061e900521600501e005025006", + "0x521600500c00510f00602100521600502012500c02f006020005216005", + "0x501d00600500521600500500514600600600521600500600501a006023", + "0x2102300500601014300602100521600502100504e006023005216005023", + "0x60120060280052c320c00521600c02600505000602602520d125216005", + "0x521600520a02a00c11100620a02a00c21600520c005170006006216005", + "0x514600620d00521600520d00501a006207005216005208005113006208", + "0x1200620702520d125005207005216005207005142006025005216005025", + "0x20d00521600520d00501a00602d005216005028005145006006216005006", + "0x2520d12500502d00521600502d005142006025005216005025005146006", + "0xc1250051b600612500521600500c0051e90060062160050060c400602d", + "0x52160050120050b800600621600500601200601d0052c401201000c216", + "0x61b000601e0052160050190051b200601a0052160050100051b4006019", + "0x1e90051ac0061e90052160050060bd0060062160050060120060062c5005", + "0x1e0052160050200051b200601a00521600501d0051b4006020005216005", + "0x1e0051ae00602100521600502100501d00602100521600501a005055006", + "0x52160050230050bf00600621600500601200620d0052c602300521600c", + "0x600501a00620c0052160050250050c1006026005216005006208006025", + "0x2100521600502100501d006005005216005005005146006006005216005", + "0x601211500620c00521600520c00502d00602600521600502600504e006", + "0x52c720800521600c20a0050a600620a02a02812521600520c026021005", + "0x2f0051bc00602f02d00c2160052080050a8006006216005006012006207", + "0xc21600513b00505200600621600500601200613d0052c813b00521600c", + "0x513e006146005216005031005055006006216005141005172006031141", + "0x21600514a00513900614a00521600514802d00c119006148005216005146", + "0x11c00602a00521600502a00514600602800521600502800501a006035005", + "0x513600600621600500601200603502a028125005035005216005035005", + "0x21600501700513900601700521600503902d00c11900603900521600513d", + "0x11c00602a00521600502a00514600602800521600502800501a00603a005", + "0x513700600621600500601200603a02a02812500503a00521600503a005", + "0x521600502a00514600602800521600502800501a00603c005216005207", + "0x621600500601200603c02a02812500503c00521600503c00511c00602a", + "0x21600514b00513600614b0052160050060bd00600621600520d005051006", + "0x604000521600503f00513900603f00521600503d02100c11900603d005", + "0x504000511c00600500521600500500514600600600521600500600501a", + "0x21600500600c00601a005216005006006006040005006125005040005216", + "0x50051460061e901e00c2160050100050440060062160050060c4006006", + "0x12521600512500500c126006125005216005125005148006005005216005", + "0x62160050060120060250052c920d00521600c0230050f5006023021020", + "0x2000514600600600521600500600501a00602600521600520d005161006", + "0x2100521600502100514800600c00521600500c005031006020005216005", + "0x502d00620c01200c21600501200510500602600521600502600518d006", + "0x2a02801221600520c0261e902100c0200060190ba00620c00521600520c", + "0x621600500601200602f0052ca02d00521600c20700507000620720820a", + "0x61410052cb13d00521600c13b00512f00613b00521600502d005196006", + "0x600621600501d00516800600621600513d005051006006216005006012", + "0x621600501a00502800600621600501e00515200600621600501200520a", + "0x21600514600502d006146005216005006000006031005216005006208006", + "0x13d00614a00521600500613b00614800521600514603100c02f006146005", + "0x502800501a0060390052160050350052cc00603500521600514814a00c", + "0x620a00521600520a00503100602a00521600502a005146006028005216", + "0x20a02a0280120050390052160050390052cd006208005216005208005148", + "0x501d005147006006216005141005051006006216005006012006039208", + "0x3c00521600503a00502000603a0052160050170051e900601701d00c216", + "0x503c0052cf00603c00521600503c00502600614b0052160050062ce006", + "0x21600c14b03d0281252d000614b00521600514b00502600603d03c00c216", + "0x621600504000502300600621600500601200615014e00c2d104003f00c", + "0x21600503c00502300600621600501200520a00600621600501d005168006", + "0x21600500620800600621600501a00502800600621600501e005152006006", + "0xc02f00615200521600515200502d0061520052160050062d2006044005", + "0x21600504715600c13d00615600521600500613b006047005216005152044", + "0x14600603f00521600503f00501a00604a00521600515e0052cc00615e005", + "0x21600520800514800620a00521600520a00503100602a00521600502a005", + "0x601200604a20820a02a03f01200504a00521600504a0052cd006208005", + "0xfc00616200521600503c0051bd006006216005150005023006006216005", + "0x14e00521600514e00501a00605117005016d04e16804c01921600501e005", + "0x20800514800620a00521600520a00503100602a00521600502a005146006", + "0x521600505200502d00605201200c216005012005105006208005216005", + "0x516205216d20820a02a14e0192d300616200521600516200502d006052", + "0x1740052d400601900521600501901a00c156006174057019055172012216", + "0xc2160050590052d60060062160050060120061780052d505900521600c", + "0x14700605d01200c21600501200510500600621600517a00505100617a05b", + "0x4c01915000618100521600517f05d00c21c00617f01d00c21600501d005", + "0x14600617200521600517200501a00606100521600505117005005b04e168", + "0x2160051810052d7006057005216005057005148006055005216005055005", + "0x51860060670651861850102160051810610570551720122d8006181005", + "0x521600500610900600621600500601200606b0052d906900521600c067", + "0x60ba18d00c2160050690052da00606d00521600518a01200c14d00618a", + "0x1a007619d19a07219607001921600518d0050fc0060062160050ba005051", + "0x1900503100618600521600518600514600618500521600518500501a006", + "0x1d00521600501d00501d006065005216005065005148006019005216005", + "0x21600506d19a01d0650191861850192db00606d00521600506d00502d006", + "0x60120061ab0052dd07e00521600c1a80052dc0061a81a407b1a21a1012", + "0x21600508c00516800608f08e08d08c01021600507e0052de006006216005", + "0x2160050060bd00600621600508f00505100600621600508e00520a006006", + "0x9100c2df0060910052160051a007619d08d072196070019150006090005", + "0x52160051a100501a00609300521600509200521b006092005216005090", + "0x514800607b00521600507b0050310061a20052160051a20051460061a1", + "0x931a407b1a21a10120050930052160050930052cd0061a40052160051a4", + "0x62160051a00051530060062160050700050fe006006216005006012006", + "0x21600519600515500600621600519d005101006006216005076005103006", + "0x1a100501a0060940052160051ab0052cc006006216005072005158006006", + "0x7b00521600507b0050310061a20052160051a20051460061a1005216005", + "0x1a21a10120050940052160050940052cd0061a40052160051a4005148006", + "0x1200520a00600621600501d0051680060062160050060120060941a407b", + "0x618500521600518500501a00609500521600506b0052cc006006216005", + "0x5065005148006019005216005019005031006186005216005186005146", + "0x120060950650191861850120050950052160050950052cd006065005216", + "0xfe00600621600501200520a00600621600501d005168006006216005006", + "0x600621600517000510300600621600505100515300600621600504c005", + "0x621600504e005158006006216005168005155006006216005050005101", + "0x5500514600617200521600517200501a0060960052160051780052cc006", + "0x57005216005057005148006019005216005019005031006055005216005", + "0x2160050060120060960570190551720120050960052160050960052cd006", + "0x501e00515200600621600501200520a00600621600501d005168006006", + "0x501a0061b500521600502f0052cc00600621600501a005028006006216", + "0x521600520a00503100602a00521600502a005146006028005216005028", + "0x280120051b50052160051b50052cd00620800521600520800514800620a", + "0x520a00600621600501d0051680060062160050060120061b520820a02a", + "0x15200600621600501a00502800600621600501e005152006006216005012", + "0x521600500600501a00609b0052160050250052cc0060062160051e9005", + "0x514800600c00521600500c005031006020005216005020005146006006", + "0x9b02100c02000601200509b00521600509b0052cd006021005216005021", + "0x501e00612500c00c21600500500501900600500521600500500501d006", + "0xc01000600c2e10060062160050060120060120052e001000521600c125", + "0x52160050190052e300600621600500601200601a0052e201901d00c216", + "0x52e400600c00521600500c00501d00601d00521600501d00501a00601e", + "0x50060bd00600621600500601200601e00c01d12500501e00521600501e", + "0x601a00521600501a00501a0060200052160051e90052e50061e9005216", + "0x2000c01a1250050200052160050200052e400600c00521600500c00501d", + "0x21600500600501a0060210052160050120052e5006006216005006012006", + "0x1250050210052160050210052e400600c00521600500c00501d006006005", + "0x50fe0060230210201e901e01a0190192160050100050fc00602100c006", + "0x1f200600621600501e00515800600621600501a005155006006216005019", + "0x60062160050230051530060062160050200051010060062160051e9005", + "0x500514600600600521600500600501a00620d00521600501201d00c2e6", + "0x12500521600512500514800600c00521600500c005031006005005216005", + "0x1221600520d02112500c00500601d2e800620d00521600520d0052e7006", + "0x50060120062080052e920a00521600c02a00507000602a02820c026025", + "0x2ea00602d00521600520700517c00620700521600520a005196006006216", + "0x21600502600514600602500521600502500501a00602f00521600502d005", + "0x2eb00602800521600502800514800620c00521600520c005031006026005", + "0x600621600500601200602f02820c02602501200502f00521600502f005", + "0x502600514600602500521600502500501a00613b0052160052080052ec", + "0x602800521600502800514800620c00521600520c005031006026005216", + "0x2160050060052ed00613b02820c02602501200513b00521600513b0052eb", + "0x60062160050060120060100052ee12500521600c00c00512f00600c005", + "0x521600501200502d0060120052160050062ef006006216005125005051", + "0x62160050100050510060062160050060120060062f00050061b000601d", + "0x501d0052f100601d00521600501900502d006019005216005006109006", + "0x600500521600500500504e00600621600501a00520a00601e01a00c216", + "0x50060bd0060062160050200050510060201e900c21600500501e00c21a", + "0x621600500600c00601a0052160050060060060211e900c005021005216", + "0x2160051250051480060050052160050050051460060062160050060c4006", + "0x521600c0200050f50060201e901e12521600512500500c126006125005", + "0x602520d00c2160050100050440060062160050060120060230052f2021", + "0x501e00514600600600521600500600501a006026005216005021005161", + "0x61e90052160051e900514800600c00521600500c00503100601e005216", + "0x1d00510500620c00521600520c00518d00620c02600c2160050260052f3", + "0x1e900c01e0060190ba00602800521600502800502d00602801d00c216005", + "0x2f402f00521600c02d00507000602d20720820a02a01221600502820c025", + "0x13d00512f00613d00521600502f00519600600621600500601200613b005", + "0x62160051410050510060062160050060120060310052f514100521600c", + "0x2a00501a00614a0052160050062ef00614814600c21600520d005044006", + "0x20800521600520800503100620a00521600520a00514600602a005216005", + "0x14a00502d00602600521600502600518d006207005216005207005148006", + "0x1703903501221600514a02614820720820a02a0190ba00614a005216005", + "0x600621600500601200603d0052f614b00521600c03c00507000603c03a", + "0x503900514600604000521600503500501a00603f00521600514b005196", + "0x604400521600503a00514800615000521600501700503100614e005216", + "0x2f80050061b000604700521600503f00519d0061520052160051460052f7", + "0x21600501a005028006006216005146005152006006216005006012006006", + "0x503d0052cc00600621600501d00520a00600621600501200506d006006", + "0x603900521600503900514600603500521600503500501a006156005216", + "0x51560052cd00603a00521600503a005148006017005216005017005031", + "0x3100505100600621600500601200615603a017039035012005156005216", + "0x52f900615e0052160050060bd00600621600502600506d006006216005", + "0x521600520a00514600604000521600502a00501a00604a00521600515e", + "0x52f700604400521600520700514800615000521600520800503100614e", + "0x521600c04700512f00604700521600504a00519d00615200521600520d", + "0x502800600621600516200505100600621600500601200604c0052fa162", + "0x15200600621600501d00520a00600621600501200506d00600621600501a", + "0x604e005216005006000006168005216005006208006006216005152005", + "0x500613b00616d00521600504e16800c02f00604e00521600504e00502d", + "0x510052160051700052cc00617000521600516d05000c13d006050005216", + "0x15000503100614e00521600514e00514600604000521600504000501a006", + "0x510052160050510052cd006044005216005044005148006150005216005", + "0x621600504c00505100600621600500601200605104415014e040012005", + "0x21600501d0051050061780591740570551720520192160051520050fc006", + "0x521600517a05b00c2e600617a01200c2160050120052f300605b01d00c", + "0x4000501a00618100521600517f0052f900617f0052160050060bd00605d", + "0x15000521600515000503100614e00521600514e005146006040005216005", + "0x18100519d00605d00521600505d0052e7006044005216005044005148006", + "0x1918506101221600518105d05904415014e0400192fb006181005216005", + "0x2fd06700521600c0650052fc00601900521600501901a00c156006065186", + "0x505100618a06b00c2160050670052fe006006216005006012006069005", + "0x618d00521600506d0052f900606d0052160050060bd00600621600518a", + "0x517806b1740570551720520191500060ba00521600518d01d0121252ff", + "0x618500521600518500514600606100521600506100501a006070005216", + "0x1850610123010060ba0052160050ba005300006186005216005186005148", + "0x530207600521600c19d00518600619d19a0721960102160050ba070186", + "0x1a20050510061a21a100c2160050760052da0060062160050060120061a0", + "0x61a400521600507b1a100c2df00607b0052160050060bd006006216005", + "0x507200514600619600521600519600501a0061a80052160051a400521b", + "0x619a00521600519a005148006019005216005019005031006072005216", + "0x62160050060120061a819a0190721960120051a80052160051a80052cd", + "0x7200514600619600521600519600501a00607e0052160051a00052cc006", + "0x19a00521600519a005148006019005216005019005031006072005216005", + "0x21600500601200607e19a01907219601200507e00521600507e0052cd006", + "0x501d00520a0060062160051780051530060062160050520050fe006006", + "0x550051580060062160050570051f2006006216005174005101006006216", + "0x52cc00600621600501200506d006006216005172005155006006216005", + "0x521600518500514600606100521600506100501a0061ab005216005069", + "0x52cd006186005216005186005148006019005216005019005031006185", + "0x280060062160050060120061ab1860191850610120051ab0052160051ab", + "0x600621600501d00520a00600621600501200506d00600621600501a005", + "0x521600513b0052cc00600621600520d00515200600621600502600506d", + "0x503100620a00521600520a00514600602a00521600502a00501a00608c", + "0x521600508c0052cd006207005216005207005148006208005216005208", + "0x21600501a00502800600621600500601200608c20720820a02a01200508c", + "0x501d00520a00600621600501200506d006006216005010005152006006", + "0x14600600600521600500600501a00608d0052160050230052cc006006216", + "0x2160051e900514800600c00521600500c00503100601e00521600501e005", + "0x600600608d1e900c01e00601200508d00521600508d0052cd0061e9005", + "0x50051460060062160050060c400600621600500600c00601a005216005", + "0x12521600512500500c126006125005216005125005148006005005216005", + "0x621600500601200602300530302100521600c0200050f50060201e901e", + "0x501a00602600521600502100516100602520d00c216005010005044006", + "0x521600500c00503100601e00521600501e005146006006005216005006", + "0x18d00620c02600c2160050260052f30061e90052160051e900514800600c", + "0x502800502d00602801d00c21600501d00510500620c00521600520c005", + "0x20720820a02a01221600502820c0251e900c01e0060190ba006028005216", + "0x19600600621600500601200613b00530402f00521600c02d00507000602d", + "0x601200603100530514100521600c13d00512f00613d00521600502f005", + "0x614814600c21600520d005044006006216005141005051006006216005", + "0x21600520a00514600602a00521600502a00501a00614a0052160050062ef", + "0x18d00620700521600520700514800620800521600520800503100620a005", + "0x20820a02a0190ba00614a00521600514a00502d006026005216005026005", + "0x14b00521600c03c00507000603c03a01703903501221600514a026148207", + "0x501a00603f00521600514b00519600600621600500601200603d005306", + "0x521600501700503100614e005216005039005146006040005216005035", + "0x519d0061520052160051460052f700604400521600503a005148006150", + "0x51520060062160050060120060063070050061b000604700521600503f", + "0x20a00600621600501200506d00600621600501a005028006006216005146", + "0x521600503500501a00615600521600503d0052cc00600621600501d005", + "0x5148006017005216005017005031006039005216005039005146006035", + "0x15603a0170390350120051560052160051560052cd00603a00521600503a", + "0x621600502600506d006006216005031005051006006216005006012006", + "0x502a00501a00604a00521600515e0052f900615e0052160050060bd006", + "0x615000521600520800503100614e00521600520a005146006040005216", + "0x504a00519d00615200521600520d0052f7006044005216005207005148", + "0x621600500601200604c00530816200521600c04700512f006047005216", + "0x21600501200506d00600621600501a005028006006216005162005051006", + "0x21600500620800600621600515200515200600621600501d00520a006006", + "0xc02f00604e00521600504e00502d00604e005216005006000006168005", + "0x21600516d05000c13d00605000521600500613b00616d00521600504e168", + "0x14600604000521600504000501a0060510052160051700052cc006170005", + "0x21600504400514800615000521600515000503100614e00521600514e005", + "0x601200605104415014e0400120050510052160050510052cd006044005", + "0x551720520192160051520050fc00600621600504c005051006006216005", + "0xc2160050120052f300605b01d00c21600501d005105006178059174057", + "0x30900617f0052160050060bd00605d00521600517a05b00c2e600617a012", + "0x21600514e00514600604000521600504000501a00618100521600517f005", + "0x2e700604400521600504400514800615000521600515000503100614e005", + "0x15014e0400192fb00618100521600518100519d00605d00521600505d005", + "0x521600501901a00c15600606518601918506101221600518105d059044", + "0x2fe00600621600500601200606900530a06700521600c0650052fc006019", + "0x52160050060bd00600621600518a00505100618a06b00c216005067005", + "0x60ba00521600518d01d0121252ff00618d00521600506d00530900606d", + "0x521600506100501a00607000521600517806b174057055172052019150", + "0x5300006186005216005186005148006185005216005185005146006061", + "0x19d19a0721960102160050ba0701861850610123010060ba0052160050ba", + "0x52da0060062160050060120061a000530b07600521600c19d005186006", + "0x7b0052160050060bd0060062160051a20050510061a21a100c216005076", + "0x501a0061a80052160051a400521b0061a400521600507b1a100c2df006", + "0x5216005019005031006072005216005072005146006196005216005196", + "0x1960120051a80052160051a80052cd00619a00521600519a005148006019", + "0x1a00607e0052160051a00052cc0060062160050060120061a819a019072", + "0x216005019005031006072005216005072005146006196005216005196005", + "0x1200507e00521600507e0052cd00619a00521600519a005148006019005", + "0x1530060062160050520050fe00600621600500601200607e19a019072196", + "0x600621600517400510100600621600501d00520a006006216005178005", + "0x62160051720051550060062160050550051580060062160050570051f2", + "0x506100501a0061ab0052160050690052cc00600621600501200506d006", + "0x6019005216005019005031006185005216005185005146006061005216", + "0x191850610120051ab0052160051ab0052cd006186005216005186005148", + "0x501200506d00600621600501a0050280060062160050060120061ab186", + "0x20d00515200600621600502600506d00600621600501d00520a006006216", + "0x602a00521600502a00501a00608c00521600513b0052cc006006216005", + "0x520700514800620800521600520800503100620a00521600520a005146", + "0x1200608c20720820a02a01200508c00521600508c0052cd006207005216", + "0x6d00600621600501000515200600621600501a005028006006216005006", + "0x8d0052160050230052cc00600621600501d00520a006006216005012005", + "0xc00503100601e00521600501e00514600600600521600500600501a006", + "0x8d00521600508d0052cd0061e90052160051e900514800600c005216005", + "0x210201e901e01a0190192160050100050fc00608d1e900c01e006012005", + "0x501e00515800600621600501a0051550060062160050190050fe006023", + "0x210051030060062160050200051010060062160051e90051f2006006216", + "0x600521600500600501a00620d00521600501d01200c2e6006006216005", + "0x12500514800600c00521600500c005031006005005216005005005146006", + "0x2312500c00500601d30c00620d00521600520d0052e7006125005216005", + "0x20800530d20a00521600c02a00507000602a02820c02602501221600520d", + "0x21600520700517c00620700521600520a005196006006216005006012006", + "0x14600602500521600502500501a00602f00521600502d0052ea00602d005", + "0x21600502800514800620c00521600520c005031006026005216005026005", + "0x601200602f02820c02602501200502f00521600502f0052eb006028005", + "0x602500521600502500501a00613b0052160052080052ec006006216005", + "0x502800514800620c00521600520c005031006026005216005026005146", + "0x600613b02820c02602501200513b00521600513b0052eb006028005216", + "0x51460060062160050060c400600621600500600c00601a005216005006", + "0x21600512500500c126006125005216005125005148006005005216005005", + "0x21600500601200602300530e02100521600c0200050f50060201e901e125", + "0x1a00602600521600502100516100602520d00c216005010005044006006", + "0x21600500c00503100601e00521600501e005146006006005216005006005", + "0x620c02600c2160050260052f30061e90052160051e900514800600c005", + "0x2800502d00602801200c21600501200510500620c00521600520c00518d", + "0x20820a02a01221600502820c0251e900c01e0060190ba006028005216005", + "0x600621600500601200613b00530f02f00521600c02d00507000602d207", + "0x1200603100531014100521600c13d00512f00613d00521600502f005196", + "0x14814600c21600520d005044006006216005141005051006006216005006", + "0x520a00514600602a00521600502a00501a00614a0052160050062ef006", + "0x620700521600520700514800620800521600520800503100620a005216", + "0x20a02a0190ba00614a00521600514a00502d00602600521600502600518d", + "0x521600c03c00507000603c03a01703903501221600514a026148207208", + "0x1a00603f00521600514b00519600600621600500601200603d00531114b", + "0x21600501700503100614e005216005039005146006040005216005035005", + "0x19d0061520052160051460052f700604400521600503a005148006150005", + "0x1520060062160050060120060063120050061b000604700521600503f005", + "0x600621600501d00506d00600621600501200520a006006216005146005", + "0x21600503500501a00615600521600503d0052cc00600621600501a005028", + "0x148006017005216005017005031006039005216005039005146006035005", + "0x3a0170390350120051560052160051560052cd00603a00521600503a005", + "0x21600502600506d006006216005031005051006006216005006012006156", + "0x2a00501a00604a00521600515e0052f900615e0052160050060bd006006", + "0x15000521600520800503100614e00521600520a005146006040005216005", + "0x4a00519d00615200521600520d0052f7006044005216005207005148006", + "0x21600500601200604c00531316200521600c04700512f006047005216005", + "0x501d00506d00600621600501200520a006006216005162005051006006", + "0x500620800600621600515200515200600621600501a005028006006216", + "0x2f00604e00521600504e00502d00604e005216005006314006168005216", + "0x516d05000c13d00605000521600500613b00616d00521600504e16800c", + "0x604000521600504000501a0060510052160051700052cc006170005216", + "0x504400514800615000521600515000503100614e00521600514e005146", + "0x1200605104415014e0400120050510052160050510052cd006044005216", + "0x1720520192160051520050fc00600621600504c005051006006216005006", + "0x21600501d0052f300605b01200c216005012005105006178059174057055", + "0x617f0052160050060bd00605d00521600517a05b00c2e600617a01d00c", + "0x514e00514600604000521600504000501a00618100521600517f0052f9", + "0x604400521600504400514800615000521600515000503100614e005216", + "0x14e04001931500618100521600518100519d00605d00521600505d0052e7", + "0x21600501901a00c15600606518601918506101221600518105d178044150", + "0x600621600500601200606900531706700521600c065005316006019005", + "0x2160050060bd00600621600518a00505100618a06b00c216005067005318", + "0xba00521600518d01d01212531900618d00521600506d0052f900606d005", + "0x21600506100501a00607000521600506b059174057055172052019150006", + "0x219006186005216005186005148006185005216005185005146006061005", + "0x19a0721960102160050ba07018618506101231a0060ba0052160050ba005", + "0x2da0060062160050060120061a000531b07600521600c19d00518600619d", + "0x52160050060bd0060062160051a20050510061a21a100c216005076005", + "0x1a0061a80052160051a400521b0061a400521600507b1a100c2df00607b", + "0x216005019005031006072005216005072005146006196005216005196005", + "0x120051a80052160051a80052cd00619a00521600519a005148006019005", + "0x607e0052160051a00052cc0060062160050060120061a819a019072196", + "0x501900503100607200521600507200514600619600521600519600501a", + "0x507e00521600507e0052cd00619a00521600519a005148006019005216", + "0x60062160050520050fe00600621600500601200607e19a019072196012", + "0x621600517400510100600621600505900510300600621600501d00506d", + "0x2160051720051550060062160050550051580060062160050570051f2006", + "0x6100501a0061ab0052160050690052cc00600621600501200520a006006", + "0x19005216005019005031006185005216005185005146006061005216005", + "0x1850610120051ab0052160051ab0052cd006186005216005186005148006", + "0x1d00506d00600621600501200520a0060062160050060120061ab186019", + "0x515200600621600502600506d00600621600501a005028006006216005", + "0x2a00521600502a00501a00608c00521600513b0052cc00600621600520d", + "0x20700514800620800521600520800503100620a00521600520a005146006", + "0x608c20720820a02a01200508c00521600508c0052cd006207005216005", + "0x600621600501000515200600621600501200520a006006216005006012", + "0x52160050230052cc00600621600501a00502800600621600501d00506d", + "0x503100601e00521600501e00514600600600521600500600501a00608d", + "0x521600508d0052cd0061e90052160051e900514800600c00521600500c", + "0x21600500600c00601a00521600500600600608d1e900c01e00601200508d", + "0x51250051480060050052160050050051460060062160050060c4006006", + "0x21600c0200050f50060201e901e12521600512500500c126006125005216", + "0x2520d00c21600501000504400600621600500601200602300531c021005", + "0x1e00514600600600521600500600501a006026005216005021005161006", + "0x1e90052160051e900514800600c00521600500c00503100601e005216005", + "0x52f300620c00521600520c00502d00620c01200c216005012005105006", + "0xc01e0060191a200602800521600502800518d00602802600c216005026", + "0x2f00521600c02d00507000602d20720820a02a01221600502820c0251e9", + "0x60b400613d00521600502f00519600600621600500601200613b00531d", + "0x621600500601200603100531e14100521600c13d00512f006006216005", + "0x2a00501a00614814600c21600520d005044006006216005141005051006", + "0x20800521600520800503100620a00521600520a00514600602a005216005", + "0x518d00614a02600c2160050260052f3006207005216005207005148006", + "0x21600503500502d00603501200c21600501200510500614a00521600514a", + "0x14b03c03a01703901221600503514a14820720820a02a0190ba006035005", + "0x519600600621600500601200603f00531f03d00521600c14b005070006", + "0x521600501700514600614e00521600503900501a00604000521600503d", + "0x52f700615200521600503c00514800604400521600503a005031006150", + "0x60063200050061b000615600521600504000519d006047005216005146", + "0x6d00600621600501200520a0060062160050060c4006006216005006012", + "0x600621600502600506d00600621600501a00502800600621600501d005", + "0x21600503900501a00615e00521600503f0052cc006006216005146005152", + "0x14800603a00521600503a005031006017005216005017005146006039005", + "0x3c03a01703901200515e00521600515e0052cd00603c00521600503c005", + "0x52160050060bd00600621600503100505100600621600500601200615e", + "0x514600614e00521600502a00501a00616200521600504a0052f900604a", + "0x521600520700514800604400521600520800503100615000521600520a", + "0x512f00615600521600516200519d00604700521600520d0052f7006152", + "0x62160050060c400600621600500601200616800532104c00521600c156", + "0x50062ef00616d04e00c21600504700504400600621600504c005051006", + "0x615000521600515000514600614e00521600514e00501a006050005216", + "0x502600518d006152005216005152005148006044005216005044005031", + "0x2616d15204415014e0190ba00605000521600505000502d006026005216", + "0x17400532205700521600c055005070006055172052051170012216005050", + "0x21600517000501a006059005216005057005196006006216005006012006", + "0x14800617a00521600505200503100605b005216005051005146006178005", + "0x21600505900519d00617f00521600504e0052f700605d005216005172005", + "0x21600504e0051520060062160050060120060063230050061b0006181005", + "0x501a00502800600621600501d00506d00600621600501200520a006006", + "0x14600617000521600517000501a0060610052160051740052cc006006216", + "0x216005172005148006052005216005052005031006051005216005051005", + "0x60120060611720520511700120050610052160050610052cd006172005", + "0x2600506d0060062160051680050510060062160050060c4006006216005", + "0x1a0061860052160051850052f90061850052160050060bd006006216005", + "0x21600504400503100605b00521600515000514600617800521600514e005", + "0x19d00617f0052160050470052f700605d00521600515200514800617a005", + "0x601200606700532406500521600c18100512f006181005216005186005", + "0x506d00600621600501200520a006006216005065005051006006216005", + "0x20800600621600517f00515200600621600501a00502800600621600501d", + "0x6b00521600506b00502d00606b005216005006314006069005216005006", + "0x6d00c13d00606d00521600500613b00618a00521600506b06900c02f006", + "0x521600517800501a0060ba00521600518d0052cc00618d00521600518a", + "0x514800617a00521600517a00503100605b00521600505b005146006178", + "0xba05d17a05b1780120050ba0052160050ba0052cd00605d00521600505d", + "0x1921600517f0050fc006006216005067005051006006216005006012006", + "0x1d0052f30061a101200c2160050120051050061a007619d19a072196070", + "0x52160050060bd00607b0052160051a21a100c2e60061a201d00c216005", + "0x514600617800521600517800501a0061a80052160051a40053090061a4", + "0x521600505d00514800617a00521600517a00503100605b00521600505b", + "0x193150061a80052160051a800519d00607b00521600507b0052e700605d", + "0x1901a00c15600608d08c0191ab07e0122160051a807b1a005d17a05b178", + "0x21600500601200608f00532508e00521600c08d005316006019005216005", + "0x60bd00600621600509100505100609109000c21600508e005318006006", + "0x21600509301d012125319006093005216005092005309006092005216005", + "0x7e00501a00609500521600509007619d19a072196070019150006094005", + "0x8c00521600508c0051480061ab0052160051ab00514600607e005216005", + "0x9601021600509409508c1ab07e01231a006094005216005094005219006", + "0x621600500601200609f00532605f00521600c09c00518600609c09b1b5", + "0x50060bd0060062160050a10050510060a109700c21600505f0052da006", + "0x1c10052160051be00521b0061be0052160051bd09700c2df0061bd005216", + "0x190050310061b50052160051b500514600609600521600509600501a006", + "0x1c10052160051c10052cd00609b00521600509b005148006019005216005", + "0x521600509f0052cc0060062160050060120061c109b0191b5096012005", + "0x50310061b50052160051b500514600609600521600509600501a0060a6", + "0x52160050a60052cd00609b00521600509b005148006019005216005019", + "0x2160050700050fe0060062160050060120060a609b0191b50960120050a6", + "0x519d00510100600621600507600510300600621600501d00506d006006", + "0x19600515500600621600507200515800600621600519a0051f2006006216", + "0x1a0060a800521600508f0052cc00600621600501200520a006006216005", + "0x2160050190050310061ab0052160051ab00514600607e00521600507e005", + "0x120050a80052160050a80052cd00608c00521600508c005148006019005", + "0x6d00600621600501200520a0060062160050060120060a808c0191ab07e", + "0x600621600502600506d00600621600501a00502800600621600501d005", + "0x21600502a00501a0061bc00521600513b0052cc00600621600520d005152", + "0x14800620800521600520800503100620a00521600520a00514600602a005", + "0x20720820a02a0120051bc0052160051bc0052cd006207005216005207005", + "0x21600501000515200600621600501200520a0060062160050060120061bc", + "0x50230052cc00600621600501a00502800600621600501d00506d006006", + "0x601e00521600501e00514600600600521600500600501a0060aa005216", + "0x50aa0052cd0061e90052160051e900514800600c00521600500c005031", + "0x1900600500521600500500501d0060aa1e900c01e0060120050aa005216", + "0x1200601200532701000521600c12500501e00612500c00c216005005005", + "0x601200601a00532901901d00c21600c01000600c328006006216005006", + "0x601d00521600501d00501a00601e00521600501900532a006006216005", + "0x1e00c01d12500501e00521600501e00532b00600c00521600500c00501d", + "0x52160051e900532c0061e90052160050060bd006006216005006012006", + "0x532b00600c00521600500c00501d00601a00521600501a00501a006020", + "0x1200532c00600621600500601200602000c01a125005020005216005020", + "0xc00521600500c00501d00600600521600500600501a006021005216005", + "0x1900521600500600600602100c00612500502100521600502100532b006", + "0x60062160050060c400600621600500600c00601e00521600500632d006", + "0x12500500c126006125005216005125005148006005005216005005005146", + "0x601200602300532e01a00521600c0210050f50060210201e9125216005", + "0x602602500c21600501000504400620d005216005006208006006216005", + "0x502800517200602a02800c21600520d00505200620c00521600500632f", + "0x14600600600521600500600501a00620a00521600502a005055006006216", + "0x2160050120053300060200052160050200051480061e90052160051e9005", + "0x620c00521600520c00502d00620800521600520800508c00620801200c", + "0x601933100601a00521600501a01e00c21800620a00521600520a00501d", + "0x521600c13b00504a00613b02f02d20701021600520a20c2080260201e9", + "0x4c00603100521600513d00516200600621600500601200614100533213d", + "0x521600500620d00600621600514600516800614814600c216005031005", + "0x501a0060390052160050350053330060350052160051480051e900614a", + "0x521600514a0050260060390052160050390051b4006207005216005207", + "0x3c00521600c03a00533500603a01700c21600514a03920712533400614a", + "0x50bf00603d00521600503c00533700600621600500601200614b005336", + "0x14e0192160050250050fc00604000521600500633800603f00521600503d", + "0x503f0050c100616204a00c21600504700533900615e156047152044150", + "0x602d00521600502d00514600601700521600501700501a00604c005216", + "0x504c00502d00602f00521600502f00514800600c00521600500c005031", + "0x16202f00c02d01701d33a00604c16800c216005168005105006168005216", + "0x17200533b05200521600c05100508f00605117005016d04e012216005168", + "0x216005055005330006055005216005052005090006006216005006012006", + "0x60062160051740051ab00605917400c21600505700509100605705500c", + "0x505b0052f100605b00521600517800533d00617800521600505900533c", + "0x617f00521600505d0050c100600621600517a00520a00605d17a00c216", + "0x2160050062ef00600621600518100520a00606118100c21600517f0052f1", + "0xc100600621600518600520a00606518600c2160051850052f1006185005", + "0x506906700c33e0060690052160050650050c1006067005216005061005", + "0x502d0060062160050060b400618a00521600501a00516100606b005216", + "0x21600500601200606d00534000621600c06b00533f00606b00521600506b", + "0xc2e600618d04c00c21600504c0051050060062160050550051ab006006", + "0x52160050700052f90060700052160050060bd0060ba00521600518a18d", + "0x503100616d00521600516d00514600604e00521600504e00501a006196", + "0x52160050ba0052e7006170005216005170005148006050005216005050", + "0x51960ba15617005016d04e0192fb00619600521600519600519d0060ba", + "0x120061a20053411a100521600c1a00052fc0061a007619d19a072012216", + "0x62160051a40050510061a407b00c2160051a10052fe006006216005006", + "0x21600507200501a0061a800521600515e07b04a15204415014e019150006", + "0x14800608c00521600519d0050310061ab00521600519a00514600607e005", + "0x21600504000508c00608e0052160051a80052f700608d005216005076005", + "0x62160050060c40060062160050060120060063420050061b000608f005", + "0x2160050190050280060062160050120051ab0060062160050400051ab006", + "0x514e0050fe00600621600515e00515300600621600504c00520a006006", + "0x440051580060062160051520051f200600621600504a005101006006216", + "0x1a0060900052160051a20052cc006006216005150005155006006216005", + "0x21600519d00503100619a00521600519a005146006072005216005072005", + "0x120050900052160050900052cd00607600521600507600514800619d005", + "0x1ab00600621600506d00534300600621600500601200609007619d19a072", + "0x609100521600515e15604a15204415014e019150006006216005040005", + "0x16d00514600604e00521600504e00501a00609309200c216005091005044", + "0x17000521600517000514800605000521600505000503100616d005216005", + "0x502d00609404c00c21600504c00510500618a00521600518a00518d006", + "0x9609501221600509418a09317005016d04e0190ba006094005216005094", + "0x621600500601200609f00534405f00521600c09c00507000609c09b1b5", + "0x61bd0053450a100521600c09700512f00609700521600505f005196006", + "0x1ab0060062160050a10050510060062160050060c4006006216005006012", + "0x60062160050920051520060062160050120051ab006006216005055005", + "0x1be00521600500620800600621600504c00520a006006216005019005028", + "0x1c11be00c02f0061c10052160051c100502d0061c1005216005006346006", + "0x1bc0052160050a60a800c13d0060a800521600500613b0060a6005216005", + "0x9600514600609500521600509500501a0060aa0052160051bc0052cc006", + "0x9b00521600509b0051480061b50052160051b5005031006096005216005", + "0x2160050060120060aa09b1b50960950120050aa0052160050aa0052cd006", + "0x9600514600607e00521600509500501a0060062160051bd005051006006", + "0x8d00521600509b00514800608c0052160051b50050310061ab005216005", + "0x8e0050fc00608f00521600505500508c00608e0052160050920052f7006", + "0x14600607e00521600507e00501a0060b41b90b20980a90b00ac019216005", + "0x21600508d00514800608c00521600508c0050310061ab0052160051ab005", + "0x60b60052160050b600502d0060b604c00c21600504c00510500608d005", + "0x7e0193470061b60052160051b600508c0061b601200c216005012005330", + "0x501d01900c1560061b01b201d1b40b80122160051b60b60b208d08c1ab", + "0x62160050060120061ac0053490bd00521600c1b000534800601d005216", + "0x50bf0050510060bf1ae00c2160050bd0052170060062160050060c4006", + "0x1ae0980a90b00ac0191500060c100521600508f01204c12534a006006216", + "0x2160051b40051460060b80052160050b800501a0061aa0052160050b41b9", + "0x34c0060c10052160050c100534b0061b20052160051b20051480061b4005", + "0x521600c1a50051860061a50c60c41a70102160050c11aa1b21b40b8012", + "0x60ca19c00c2160050c80052da0060062160050060120061a300534d0c8", + "0x2160050cc19c00c2df0060cc0052160050060bd0060062160050ca005051", + "0x1460061a70052160051a700501a00619900521600519b00521b00619b005", + "0x2160050c600514800601d00521600501d0050310060c40052160050c4005", + "0x60120061990c601d0c41a70120051990052160051990052cd0060c6005", + "0x61a70052160051a700501a00619f0052160051a30052cc006006216005", + "0x50c600514800601d00521600501d0050310060c40052160050c4005146", + "0x1200619f0c601d0c41a701200519f00521600519f0052cd0060c6005216", + "0x51530060062160050ac0050fe0060062160050060c4006006216005006", + "0x1f200600621600504c00520a0060062160051b90051030060062160050b4", + "0x60062160050b00051550060062160050a9005158006006216005098005", + "0x52160051ac0052cc0060062160050120051ab00600621600508f0051ab", + "0x50310061b40052160051b40051460060b80052160050b800501a0060cf", + "0x52160050cf0052cd0061b20052160051b200514800601d00521600501d", + "0x62160050060c40060062160050060120060cf1b201d1b40b80120050cf", + "0x2160050920051520060062160050120051ab0060062160050550051ab006", + "0x509f0052cc00600621600504c00520a006006216005019005028006006", + "0x609600521600509600514600609500521600509500501a0060d1005216", + "0x50d10052cd00609b00521600509b0051480061b50052160051b5005031", + "0x400051ab0060062160050060120060d109b1b50960950120050d1005216", + "0x51580060062160051520051f2006006216005150005155006006216005", + "0x2800600621600504a0051010060062160050120051ab006006216005044", + "0x600621600515e00515300600621600504c00520a006006216005019005", + "0x621600501a00534e00600621600515600510300600621600514e0050fe", + "0x16d00514600604e00521600504e00501a0061950052160051720052cc006", + "0x17000521600517000514800605000521600505000503100616d005216005", + "0x21600500601200619517005016d04e0120051950052160051950052cd006", + "0x501a00534e0060062160050120051ab006006216005025005152006006", + "0x501a00619300521600514b0052cc006006216005019005028006006216", + "0x521600500c00503100602d00521600502d005146006017005216005017", + "0x170120051930052160051930052cd00602f00521600502f00514800600c", + "0x51ab00600621600502500515200600621600500601200619302f00c02d", + "0x2cc00600621600501900502800600621600501a00534e006006216005012", + "0x21600502d00514600620700521600520700501a0060d2005216005141005", + "0x2cd00602f00521600502f00514800600c00521600500c00503100602d005", + "0x60062160050060120060d202f00c02d2070120050d20052160050d2005", + "0x62160050100051520060062160050120051ab006006216005019005028", + "0x500600501a0060d40052160050230052cc00600621600501e00534f006", + "0x600c00521600500c0050310061e90052160051e9005146006006005216", + "0xc1e90060120050d40052160050d40052cd006020005216005020005148", + "0x1d0050fe0060210201e901e01a01901d0192160050100050fc0060d4020", + "0x51f200600621600501a005158006006216005019005155006006216005", + "0x1a00600621600502100515300600621600502000510300600621600501e", + "0x21600500c005031006005005216005005005146006006005216005006005", + "0x33a00601200521600501200502d00612500521600512500514800600c005", + "0xc20c00508f00620c02602520d0230122160050121e912500c00500601d", + "0x20a00521600502800509000600621600500601200602a005350028005216", + "0x2300501a00620700521600520800535200620800521600520a005351006", + "0x2500521600502500503100620d00521600520d005146006023005216005", + "0x20d023012005207005216005207005353006026005216005026005148006", + "0x501a00602d00521600502a005354006006216005006012006207026025", + "0x521600502500503100620d00521600520d005146006023005216005023", + "0x2301200502d00521600502d005353006026005216005026005148006025", + "0x12500521600500c00533d00600c00521600500600533c00602d02602520d", + "0x1200502d00600621600501000520a00601201000c2160051250052f1006", + "0xc21600500501200c21a00600500521600500500504e006012005216005", + "0x1a01d00c00501a0052160050060bd00600621600501900505100601901d", + "0x621600500600c0061e900521600500635500601a005216005006006006", + "0x20c02602520d0230210200192160050100050fc0060062160050060c4006", + "0x514600600600521600500600501a00602a02800c216005021005356006", + "0x2a125005006010357006125005216005125005148006005005216005005", + "0x1200613b00535802f00521600c02d00508f00602d20720820a010216005", + "0x614100521600502f00509000613d005216005006208006006216005006", + "0x514600505500600621600503100517200614603100c21600513d005052", + "0xb400603500521600514a00530900614a0052160050060bd006148005216", + "0x521600514800501d00603901200c216005012005105006006216005006", + "0x21600c03514803914120720801d35900603500521600503500519d006148", + "0x3c00c35b00600621600500601200604003f03d12535a14b03c03a017010", + "0x521600501700514600615000521600514e00535c00614e00521600514b", + "0x61b000604700521600515000535d00615200521600503a005148006044", + "0x514600615600521600504000535f00600621600500601200600635e005", + "0x521600515600535d00615200521600503f00514800604400521600503d", + "0x16200536204a00521600c15e00536100615e005216005047005360006047", + "0x21600504c00536400604c00521600504a005363006006216005006012006", + "0x601e00521600501e1e900c36500600621600516800516800616801e00c", + "0x4400514600616d00521600504e00536600604e01e00c21600501e0052f3", + "0x16d00521600516d005367006152005216005152005148006044005216005", + "0x1036800605000521600505000508c00605001d00c21600501d005330006", + "0x536917200521600c0520050b000605205117012521600505016d152044", + "0x21600501e0052f30060062160051720050a9006006216005006012006055", + "0x617000521600517000514600617400521600505700536a00605701e00c", + "0xf500605b17805912521600505117000c126006051005216005051005148", + "0x2160050060c400600621600500601200605d00536b17a00521600c05b005", + "0xbd00618100521600517f17400c2e600617f00521600517a005161006006", + "0x521600520a00501a0061850052160050610052f9006061005216005006", + "0x514800600c00521600500c00503100605900521600505900514600620a", + "0x521600518500519d0061810052160051810052e7006178005216005178", + "0x606906701906518601221600518518102617800c05920a0192fb006185", + "0x618a00536c06b00521600c0690052fc00601900521600501901a00c156", + "0x21600518d00505100618d06d00c21600506b0052fe006006216005006012", + "0x700052160050ba01d01212536d0060ba01e00c21600501e0052f3006006", + "0x21600518600501a00619600521600520c06d02520d023028020019150006", + "0x36e006067005216005067005148006065005216005065005146006186005", + "0x19d19a07201021600507019606706518601236f006070005216005070005", + "0x2da0060062160050060120061a10053701a000521600c076005186006076", + "0x501e1a200c37100600621600507b00505100607b1a200c2160051a0005", + "0x607200521600507200501a0061a80052160051a40053720061a4005216", + "0x519d00514800601900521600501900503100619a00521600519a005146", + "0x120061a819d01919a0720120051a80052160051a800537300619d005216", + "0x607e0052160051a100537400600621600501e00506d006006216005006", + "0x501900503100619a00521600519a00514600607200521600507200501a", + "0x507e00521600507e00537300619d00521600519d005148006019005216", + "0x600621600501e00506d00600621600500601200607e19d01919a072012", + "0x621600501200520a00600621600520c0051530060062160050200050fe", + "0x21600502300515800600621600520d0051f2006006216005025005101006", + "0x518a00537400600621600501d0051ab006006216005028005155006006", + "0x606500521600506500514600618600521600518600501a0061ab005216", + "0x51ab005373006067005216005067005148006019005216005019005031", + "0x50060c40060062160050060120061ab0670190651860120051ab005216", + "0x200050fe00600621600501e00506d006006216005028005155006006216", + "0x51ab00600621600502300515800600621600520d0051f2006006216005", + "0x10100600621600501200520a00600621600520c00515300600621600501d", + "0x600621600502600510300600621600501a005028006006216005025005", + "0x21600520a00501a00608c00521600505d00537400600621600517400520a", + "0x14800600c00521600500c00503100605900521600505900514600620a005", + "0x17800c05920a01200508c00521600508c005373006178005216005178005", + "0x62160050280051550060062160050060c400600621600500601200608c", + "0x21600520d0051f20060062160050200050fe00600621600501e00506d006", + "0x520c00515300600621600501d0051ab006006216005023005158006006", + "0x1a00502800600621600502500510100600621600501200520a006006216", + "0x1a00608d005216005055005374006006216005026005103006006216005", + "0x21600500c00503100617000521600517000514600620a00521600520a005", + "0x1200508d00521600508d00537300605100521600505100514800600c005", + "0x51550060062160050060c400600621600500601200608d05100c17020a", + "0x15800600621600520d0051f20060062160050200050fe006006216005028", + "0x600621600520c00515300600621600501d0051ab006006216005023005", + "0x621600501a00502800600621600502500510100600621600501200520a", + "0x2160051620053740060062160051e9005375006006216005026005103006", + "0x3100604400521600504400514600620a00521600520a00501a00608e005", + "0x21600508e00537300615200521600515200514800600c00521600500c005", + "0x502800515500600621600500601200608e15200c04420a01200508e005", + "0x20d0051f20060062160050200050fe0060062160051e9005375006006216", + "0x515300600621600501d0051ab006006216005023005158006006216005", + "0x2800600621600502500510100600621600501200520a00600621600520c", + "0x8f00521600513b00537400600621600502600510300600621600501a005", + "0xc00503100620800521600520800514600620a00521600520a00501a006", + "0x8f00521600508f00537300620700521600520700514800600c005216005", + "0x500c00536a00600c00521600500600537600608f20700c20820a012005", + "0x600621600501000520a00601201000c2160051250052f1006125005216", + "0x501200c21a00600500521600500500504e00601200521600501200502d", + "0x501a0052160050060bd00600621600501900505100601901d00c216005", + "0x50060c400600621600500600c00601a00521600500600600601a01d00c", + "0x2001200c2160050120052f30061e901e00c216005010005044006006216", + "0x12500514800600500521600500500514600602100521600502000536a006", + "0xc0250050f500602520d02312521600512500500c126006125005216005", + "0x2800521600502600516100600621600500601200620c005377026005216", + "0xc00503100602300521600502300514600600600521600500600501a006", + "0x2100521600502100502d00620d00521600520d00514800600c005216005", + "0x2160050280211e920d00c02300601937800602800521600502800518d006", + "0xc2070050b000601900521600501901a00c15600620720801920a02a012", + "0x600621600502d0050a900600621600500601200602f00537902d005216", + "0x20a00514600613d00521600513b00536600613b01200c2160050120052f3", + "0x13d00521600513d00536700620800521600520800514800620a005216005", + "0x1036800614100521600514100508c00614101d00c21600501d005330006", + "0x537a14a00521600c1480050b000614814603112521600514113d20820a", + "0x21600501d00533000600621600514a0050a9006006216005006012006035", + "0x2a00521600502a00501a00601700521600501203900c37b00603901d00c", + "0x1700537c006146005216005146005148006031005216005031005146006", + "0x603d14b03c03a01021600501701e14603102a01237d006017005216005", + "0x3f0052da00600621600500601200604000537e03f00521600c03d005186", + "0x521600501d14e00c37f00600621600515000505100615014e00c216005", + "0x514600603a00521600503a00501a006152005216005044005380006044", + "0x521600514b00514800601900521600501900503100603c00521600503c", + "0x500601200615214b01903c03a01200515200521600515200538100614b", + "0x501a00604700521600504000538200600621600501d0051ab006006216", + "0x521600501900503100603c00521600503c00514600603a00521600503a", + "0x3a01200504700521600504700538100614b00521600514b005148006019", + "0x515200600621600501d0051ab00600621600500601200604714b01903c", + "0x615600521600503500538200600621600501200506d00600621600501e", + "0x501900503100603100521600503100514600602a00521600502a00501a", + "0x5156005216005156005381006146005216005146005148006019005216", + "0x600621600501d0051ab00600621600500601200615614601903102a012", + "0x521600502f00538200600621600501200506d00600621600501e005152", + "0x503100620a00521600520a00514600602a00521600502a00501a00615e", + "0x521600515e005381006208005216005208005148006019005216005019", + "0x21600501200506d00600621600500601200615e20801920a02a01200515e", + "0x501a00502800600621600501e00515200600621600501d0051ab006006", + "0x20c0053820060062160051e900515200600621600502100520a006006216", + "0x2300521600502300514600600600521600500600501a00604a005216005", + "0x4a00538100620d00521600520d00514800600c00521600500c005031006", + "0x120100192160051250050fc00604a20d00c02300601200504a005216005", + "0x21600500600501a00602102000c21600501d0053830061e901e01a01901d", + "0x38400600c00521600500c005148006005005216005005005146006006005", + "0x20c00521600c02600538500602602520d02301021600502100c005006010", + "0x638800602a00521600520c005387006006216005006012006028005386", + "0x2a00c21600502a0052cf00602300521600502300501a00620a005216005", + "0x12538900620a00521600520a005026006208005216005208005026006208", + "0x613b00538a02f00521600c02d00538500602d20700c21600520a208023", + "0x521600520d00514600613d00521600502f005387006006216005006012", + "0x1038b00613d00521600513d00502600602500521600502500514800620d", + "0x538d14800521600c14600538c00614603114112521600513d02002520d", + "0x3900505100603903500c21600514800538e00600621600500601200614a", + "0xc38f0060170052160051e901e01a019035012010019150006006216005", + "0x21600520700501a00603c00521600503a00539000603a00521600502a017", + "0x220006031005216005031005148006141005216005141005146006207005", + "0x2300600621600500601200603c03114120701000503c00521600503c005", + "0x600621600501e0051030060062160051e900515300600621600502a005", + "0x62160050100050fe0060062160050190051f200600621600501a005101", + "0x520700501a00614b00521600514a005391006006216005012005155006", + "0x6031005216005031005148006141005216005141005146006207005216", + "0x600621600500601200614b03114120701000514b00521600514b005220", + "0x621600502a0050230060062160050100050fe006006216005012005155", + "0x21600501a00510100600621600501e0051030060062160051e9005153006", + "0x513b0053910060062160050200051580060062160050190051f2006006", + "0x620d00521600520d00514600620700521600520700501a00603d005216", + "0x2520d20701000503d00521600503d005220006025005216005025005148", + "0x2160050100050fe00600621600501200515500600621600500601200603d", + "0x501a00510100600621600501e0051030060062160051e9005153006006", + "0x280053910060062160050200051580060062160050190051f2006006216", + "0x20d00521600520d00514600602300521600502300501a00603f005216005", + "0x20d02301000503f00521600503f005220006025005216005025005148006", + "0x1250051b600612500521600500c0051e90060062160050060c400603f025", + "0x2160050120050b800600621600500601200601d00539201201000c21600c", + "0x1b000601e0052160050190051b200601a0052160050100051b4006019005", + "0x51ac0061e90052160050060bd006006216005006012006006393005006", + "0x52160050200051b200601a00521600501d0051b40060200052160051e9", + "0x51ae00602100521600502100501d00602100521600501a00505500601e", + "0x2160050230050bf00600621600500601200620d00539402300521600c01e", + "0x501a00620c0052160050250050c1006026005216005006208006025005", + "0x521600502100501d006005005216005005005146006006005216005006", + "0x1211500620c00521600520c00502d00602600521600502600504e006021", + "0x39520800521600c20a0050a600620a02a02812521600520c026021005006", + "0xc39600602f02d00c2160052080050a8006006216005006012006207005", + "0x21600502800501a00613d00521600513b00539700613b00521600502f02d", + "0x12500513d00521600513d00539800602a00521600502a005146006028005", + "0x501a00614100521600520700539900600621600500601200613d02a028", + "0x521600514100539800602a00521600502a005146006028005216005028", + "0x600621600520d00505100600621600500601200614102a028125005141", + "0x514602100c39600614600521600503100539a0060310052160050060bd", + "0x600600521600500600501a00614a005216005148005397006148005216", + "0x14a00500612500514a00521600514a005398006005005216005005005146", + "0x500500514800600600521600500600514600600621600500c005152006", + "0x21600c0190050f500601901d01212521600500500600c126006005005216", + "0x61e900521600501a00516100600621600500601200601e00539b01a005", + "0x502100518d00600621600502000506d00602102000c2160051e9005096", + "0x2300c21600512502100c1b500612500521600512500504e006021005216", + "0x517200602602500c21600502300505200600621600520d00505100620d", + "0x20c00521600520c00501d00620c005216005026005055006006216005025", + "0x601200620720820a12539d02a02800c21600c01020c01d01201039c006", + "0x14600602f00521600502d00539e00602d0052160050060bd006006216005", + "0x21600502f00539f00613d00521600502a00514800613b005216005028005", + "0x2160052070053a10060062160050060120060063a00050061b0006141005", + "0x39f00613d00521600520800514800613b00521600520a005146006031005", + "0x21600c1460050b00061460052160051410053a2006141005216005031005", + "0xbd0060062160051480050a900600621600500601200614a0053a3148005", + "0x52160050390053a50060390052160050350053a4006035005216005006", + "0x53a600613d00521600513d00514800613b00521600513b005146006017", + "0x14a0053a700600621600500601200601713d13b125005017005216005017", + "0x13d00521600513d00514800613b00521600513b00514600603a005216005", + "0x600621600500601200603a13d13b12500503a00521600503a0053a6006", + "0x521600501e0053a7006006216005125005172006006216005010005168", + "0x53a600601d00521600501d00514800601200521600501200514600603c", + "0x12500c00c21600c00500600c3a800603c01d01212500503c00521600503c", + "0x501a0060120052160051250053aa0060062160050060120060100053a9", + "0x60063ac0050061b00060190052160050120053ab00601d00521600500c", + "0x1e00521600501a0053ad00601a0052160050060bd006006216005006012", + "0x1901d00c00501900521600501e0053ab00601d00521600501000501a006", + "0x21600c1250051b600612500521600500c0051e90060062160050060c4006", + "0x190052160050120050b800600621600500601200601d0053ae01201000c", + "0x50061b000601e0052160050190051b200601a0052160050100051b4006", + "0x51e90051ac0061e90052160050060bd0060062160050060120060063af", + "0x601e0052160050200051b200601a00521600501d0051b4006020005216", + "0xc01e0051ae00602100521600502100501d00602100521600501a005055", + "0x250052160050230050bf00600621600500601200620d0053b0023005216", + "0x500600501a00620c0052160050250050c10060260052160050063b1006", + "0x602100521600502100501d006005005216005005005146006006005216", + "0x50060123b300620c00521600520c00502d0060260052160050260053b2", + "0x2070053b520800521600c20a0053b400620a02a02812521600520c026021", + "0xc02f0053b700602f02d00c2160052080053b6006006216005006012006", + "0x14100c21600513b0053b900600621600500601200613d0053b813b005216", + "0x1460053bc0061460052160050310053bb0060062160051410053ba006031", + "0x521600514a0053be00614a00521600514802d00c3bd006148005216005", + "0x53bf00602a00521600502a00514600602800521600502800501a006035", + "0x13d0053c000600621600500601200603502a028125005035005216005035", + "0x52160050170053be00601700521600503902d00c3bd006039005216005", + "0x53bf00602a00521600502a00514600602800521600502800501a00603a", + "0x2070053c100600621600500601200603a02a02812500503a00521600503a", + "0x2a00521600502a00514600602800521600502800501a00603c005216005", + "0x600621600500601200603c02a02812500503c00521600503c0053bf006", + "0x521600514b0053c000614b0052160050060bd00600621600520d005051", + "0x1a00604000521600503f0053be00603f00521600503d02100c3bd00603d", + "0x2160050400053bf006005005216005005005146006006005216005006005", + "0x52160050060d9006023005216005006006006040005006125005040005", + "0x21600500600c00602a00521600500600500620c0052160050060d7006025", + "0x500514600620820a00c21600501d0050440060062160050060c4006006", + "0x12521600501200500c126006012005216005012005148006005005216005", + "0x621600500601200613d0053c213b00521600c02f0050f500602f02d207", + "0x20700514600600600521600500600501a00614100521600513b005161006", + "0x2d00521600502d005148006125005216005125005031006207005216005", + "0x518d00603100521600503100502d00603101900c216005019005105006", + "0x14814601221600514103120802d125207006019378006141005216005141", + "0x521600502802a00c15e00602100521600502102300c15600614a028021", + "0xa90060062160050060120060390053c303500521600c14a0050b0006028", + "0x521600514800514600614600521600514600501a006006216005035005", + "0x1d00601701a00c21600501a005147006010005216005010005199006148", + "0x3d14b03c03a0102160050170101481460103c4006017005216005017005", + "0x514f0060062160050060120060400053c503f00521600c03d005107006", + "0x521600503c00514600603a00521600503a00501a00614e00521600503f", + "0x514800614b00521600514b00519900600c00521600500c00519b00603c", + "0x21600515000502d00615001900c216005019005105006028005216005028", + "0x604401e00c21600501e0053c600614e00521600514e00502d006150005", + "0x15200501d0061521e900c2160051e900514700604400521600504400519f", + "0x2814b00c03c03a1e93c70060200052160050200050cf006152005216005", + "0x520d02500c18200604a15e02620d15604701d21600502015204414e150", + "0x16200521600c04a0050b000602600521600502620c00c0e300620d005216", + "0x190103c90060062160051620050a900600621600500601200604c0053c8", + "0x515600514600604700521600504700501a0061680052160051e901e01a", + "0x61680052160051680053ca00615e00521600515e005148006156005216", + "0x21600c17000518600617005016d04e01021600516820a15e1560470123cb", + "0x5517200c2160050510052da0060062160050060120060520053cc051005", + "0x505717200c2df0060570052160050060bd006006216005055005051006", + "0x604e00521600504e00501a00605900521600517400521b006174005216", + "0x502100503100620d00521600520d00519b00616d00521600516d005146", + "0x6050005216005050005148006026005216005026005199006021005216", + "0x500601200605905002602120d16d04e0190050590052160050590052cd", + "0x14600604e00521600504e00501a0061780052160050520052cc006006216", + "0x21600502100503100620d00521600520d00519b00616d00521600516d005", + "0x2cd006050005216005050005148006026005216005026005199006021005", + "0x21600500601200617805002602120d16d04e019005178005216005178005", + "0x501e0050cc0060062160051e900516800600621600520a005152006006", + "0x4c0052cc00600621600501900520a00600621600501a005168006006216", + "0x15600521600515600514600604700521600504700501a00605b005216005", + "0x2600519900602100521600502100503100620d00521600520d00519b006", + "0x5b00521600505b0052cd00615e00521600515e005148006026005216005", + "0x501a00516800600621600500601200605b15e02602120d156047019005", + "0x20a00515200600621600501900520a00600621600501e0050cc006006216", + "0x50df0060062160050250051840060062160051e9005168006006216005", + "0x617a0052160050400052cc0060062160050200050ca00600621600520c", + "0x500c00519b00603c00521600503c00514600603a00521600503a00501a", + "0x614b00521600514b00519900602100521600502100503100600c005216", + "0xc03c03a01900517a00521600517a0052cd006028005216005028005148", + "0x50cc00600621600501a00516800600621600500601200617a02814b021", + "0x16800600621600520a00515200600621600501900520a00600621600501e", + "0x600621600520c0050df0060062160050250051840060062160051e9005", + "0x21600514600501a00605d0052160050390052cc0060062160050200050ca", + "0x3100600c00521600500c00519b006148005216005148005146006146005", + "0x216005028005148006010005216005010005199006021005216005021005", + "0x605d02801002100c14814601900505d00521600505d0052cd006028005", + "0x600621600501e0050cc00600621600501a005168006006216005006012", + "0x62160051e900516800600621600520a00515200600621600501900520a", + "0x2160050200050ca00600621600520c0050df006006216005025005184006", + "0x520800515200600621600502a00502a006006216005023005028006006", + "0x14600600600521600500600501a00617f00521600513d0052cc006006216", + "0x21600512500503100600c00521600500c00519b006207005216005207005", + "0x2cd00602d00521600502d005148006010005216005010005199006125005", + "0x21600500600600617f02d01012500c20700601900517f00521600517f005", + "0x500605b0060250052160050060d70060230052160050060d9006020005", + "0x600c0062080052160050063cd00602a00521600500600500620c005216", + "0x14600602d20700c21600501d0050440060062160050060c4006006216005", + "0x501200500c126006012005216005012005148006005005216005005005", + "0x50060120060310053ce14100521600c13d0050f500613d13b02f125216", + "0x14600600600521600500600501a006146005216005141005161006006216", + "0x21600513b00514800612500521600512500503100602f00521600502f005", + "0x614800521600514800502d00614801900c21600501900510500613b005", + "0x1221600514614802d13b12502f00601937800614600521600514600518d", + "0x500601200614b0053cf03c00521600c03a0050b000603a01703903514a", + "0x14e04003f03d0192160052070050fc00600621600503c0050a9006006216", + "0x14a00521600514a00501a00615604700c216005150005339006152044150", + "0x17005148006039005216005039005031006035005216005035005146006", + "0x521600515e00502d00615e01900c216005019005105006017005216005", + "0x15600604c0281e916204a01221600515e15601703903514a01d33a00615e", + "0x4c00508f00602800521600502802a00c15e0061e90052160051e902000c", + "0x62160051680053d100600621600500601200604e0053d016800521600c", + "0x21600504a00501a00605000521600500620d00616d005216005006208006", + "0x22200605000521600505000502600616200521600516200514600604a005", + "0x516d00504e0061700052160051700050cf00617001e00c21600501e005", + "0x53d300617205205112521600516d17005016204a0123d200616d005216", + "0x21600505100501a0060062160050060120060550053d420a00521600c172", + "0x147006010005216005010005199006052005216005052005146006051005", + "0x20a20800c3d500605700521600505700501d00605701a00c21600501a005", + "0x10700605b1780591740102160050570100520510103c400620a005216005", + "0x520a0053d700600621600500601200605d0053d617a00521600c05b005", + "0x600621600506100505100600621600518100502300606118117f125216", + "0x21600517f0050520061850052160050063d800602600521600517a00514f", + "0x1a00606700521600506500505500600621600518600517200606518600c", + "0x21600500c00519b006059005216005059005146006174005216005174005", + "0x10500602800521600502800514800617800521600517800519900600c005", + "0x502600510500606900521600506900502d00606901900c216005019005", + "0x18500521600518500519f00606b00521600506b00502d00606b02600c216", + "0x20c00c17a00601e00521600501e0050cf00606700521600506700501d006", + "0x21600501e06718506b06902817800c0591741e93c7006026005216005026", + "0x700050b000602100521600502102300c1820060700ba18d02106d18a01d", + "0x62160051960050a90060062160050060120060720053d919600521600c", + "0x18d00519900606d00521600506d00514600618a00521600518a00501a006", + "0x1900c2160050190051050060ba0052160050ba00514800618d005216005", + "0x1d3da00602600521600502600502d00619a00521600519a00502d00619a", + "0x520d02500c0e30061a11a020d07619d01221600502619a0ba18d06d18a", + "0x621600500601200607b0053db1a200521600c1a10050b000620d005216", + "0x3d0191500061a400521600501a01900c3dc0060062160051a20050a9006", + "0x14600619d00521600519d00501a0061a800521600515204404714e04003f", + "0x2160051a40052210061a00052160051a0005148006076005216005076005", + "0x518600608d08c1ab07e0102160051a41a81a007619d0123dd0061a4005", + "0x21600508e0052da00600621600500601200608f0053de08e00521600c08d", + "0xc2df0060920052160050060bd00600621600509100505100609109000c", + "0x21600507e00501a00609400521600509300521b006093005216005092090", + "0x3100602100521600502100519b0061ab0052160051ab00514600607e005", + "0x21600508c00514800620d00521600520d0051990061e90052160051e9005", + "0x609408c20d1e90211ab07e0190050940052160050940052cd00608c005", + "0x521600507e00501a00609500521600508f0052cc006006216005006012", + "0x503100602100521600502100519b0061ab0052160051ab00514600607e", + "0x521600508c00514800620d00521600520d0051990061e90052160051e9", + "0x1200609508c20d1e90211ab07e0190050950052160050950052cd00608c", + "0x10300600621600515200515300600621600503d0050fe006006216005006", + "0x600621600514e0051f2006006216005047005101006006216005044005", + "0x621600501a00516800600621600503f005155006006216005040005158", + "0x519d00501a00609600521600507b0052cc00600621600501900520a006", + "0x602100521600502100519b00607600521600507600514600619d005216", + "0x51a000514800620d00521600520d0051990061e90052160051e9005031", + "0x961a020d1e902107619d0190050960052160050960052cd0061a0005216", + "0x621600503d0050fe00600621600501a005168006006216005006012006", + "0x21600501900520a00600621600503f005155006006216005040005158006", + "0x5047005101006006216005044005103006006216005152005153006006", + "0x2600520a0060062160050250050df00600621600514e0051f2006006216", + "0x618a00521600518a00501a0061b50052160050720052cc006006216005", + "0x51e900503100602100521600502100519b00606d00521600506d005146", + "0x60ba0052160050ba00514800618d00521600518d0051990061e9005216", + "0x50060120061b50ba18d1e902106d18a0190051b50052160051b50052cd", + "0x4000515800600621600503d0050fe00600621600501a005168006006216", + "0x515300600621600501900520a00600621600503f005155006006216005", + "0x1f2006006216005047005101006006216005044005103006006216005152", + "0x60062160050230051840060062160050250050df00600621600514e005", + "0x621600520c00506900600621600520a0053df00600621600501e0050ca", + "0x5900514600617400521600517400501a00609b00521600505d0052cc006", + "0x1e90052160051e900503100600c00521600500c00519b006059005216005", + "0x9b0052cd006028005216005028005148006178005216005178005199006", + "0x600621600500601200609b0281781e900c05917401900509b005216005", + "0x621600503d0050fe00600621600501a00516800600621600520c005069", + "0x21600501900520a00600621600503f005155006006216005040005158006", + "0x5047005101006006216005044005103006006216005152005153006006", + "0x230051840060062160050250050df00600621600514e0051f2006006216", + "0x52cc0060062160052080053e000600621600501e0050ca006006216005", + "0x521600505200514600605100521600505100501a00609c005216005055", + "0x51990061e90052160051e900503100600c00521600500c00519b006052", + "0x521600509c0052cd006028005216005028005148006010005216005010", + "0x20c00506900600621600500601200609c0280101e900c05205101900509c", + "0x515800600621600503d0050fe00600621600501a005168006006216005", + "0x15300600621600501900520a00600621600503f005155006006216005040", + "0x6006216005047005101006006216005044005103006006216005152005", + "0x62160050230051840060062160050250050df00600621600514e0051f2", + "0x21600504e0052cc0060062160052080053e000600621600501e0050ca006", + "0x19b00616200521600516200514600604a00521600504a00501a00605f005", + "0x2160050100051990061e90052160051e900503100600c00521600500c005", + "0x1900505f00521600505f0052cd006028005216005028005148006010005", + "0x621600520c00506900600621600500601200605f0280101e900c16204a", + "0x21600501900520a00600621600502a00502a00600621600501a005168006", + "0x50250050df0060062160050200050280060062160052080053e0006006", + "0x20700515200600621600501e0050ca006006216005023005184006006216", + "0x614a00521600514a00501a00609f00521600514b0052cc006006216005", + "0x503900503100600c00521600500c00519b006035005216005035005146", + "0x6017005216005017005148006010005216005010005199006039005216", + "0x500601200609f01701003900c03514a01900509f00521600509f0052cd", + "0x2a00502a00600621600501a00516800600621600520c005069006006216", + "0x53e000600621600501900520a00600621600501e0050ca006006216005", + "0x152006006216005023005184006006216005020005028006006216005208", + "0x600621600502d0051520060062160050250050df006006216005207005", + "0x502f00514600600600521600500600501a0060970052160050310052cc", + "0x612500521600512500503100600c00521600500c00519b00602f005216", + "0x50970052cd00613b00521600513b005148006010005216005010005199", + "0xc21600c00500600c3e100609713b01012500c02f006019005097005216", + "0x60120052160051250053e30060062160050060120060100053e212500c", + "0x3e50050061b00060190052160050120053e400601d00521600500c00501a", + "0x21600501a0053e600601a0052160050060bd006006216005006012006006", + "0xc00501900521600501e0053e400601d00521600501000501a00601e005", + "0x500600501a0060062160050120051520060062160050060c400601901d", + "0x6125005216005125005199006005005216005005005146006006005216", + "0x230210200102160050191250050060103c400601900521600501900501d", + "0x14f0060062160050060120060260053e702500521600c20d00510700620d", + "0x21600502100514600602000521600502000501a00620c005216005025005", + "0x14800602300521600502300519900600c00521600500c00519b006021005", + "0x21600520c00502d00601d00521600501d00502d006010005216005010005", + "0xcf00601e00521600501e00502600601a00521600501a00519f00620c005", + "0x51e901e01a20c01d01002300c0210201e93e80061e90052160051e9005", + "0x613b0053e902f00521600c02d00504a00602d20720820a02a02801d216", + "0x521600513d00505700613d00521600502f005162006006216005006012", + "0x514600602800521600502800501a006031005216005141005174006141", + "0x521600520800519900620a00521600520a00519b00602a00521600502a", + "0x2801d00503100521600503100514a006207005216005207005148006208", + "0x614600521600513b00514100600621600500601200603120720820a02a", + "0x520a00519b00602a00521600502a00514600602800521600502800501a", + "0x620700521600520700514800620800521600520800519900620a005216", + "0x21600500601200614620720820a02a02801d00514600521600514600514a", + "0x501a0050cc00600621600501e0050230060062160051e90050ca006006", + "0x501a00614800521600502600514100600621600501d00520a006006216", + "0x521600500c00519b006021005216005021005146006020005216005020", + "0x514a00601000521600501000514800602300521600502300519900600c", + "0x521600500600501d00614801002300c02102001d005148005216005148", + "0x53ea12500521600c00c00501e00600c00500c216005006005019006006", + "0x1200533f00601212500c216005125005105006006216005006012006010", + "0x600621600512500520a00600621600500601200601d0053eb00621600c", + "0x1a00501e00601a01900c21600500500501900600500521600500500501d", + "0x521600501e0051640060062160050060120061e90053ec01e00521600c", + "0x61b00060230052160050200050e900602100521600501900501d006020", + "0x501d00620d0052160051e90053ee0060062160050060120060063ed005", + "0x601200620d01900c00520d00521600520d0053ef006019005216005019", + "0xc33e00602500521600500610900600621600501d005343006006216005", + "0x21600c02600533f00602600521600502600502d006026005216005025125", + "0x3f100600500521600500500501d00600621600500601200620c0053f0006", + "0x120062080053f320a00521600c02a0053f200602a02800c216005005005", + "0x2100521600502800501d00620700521600520a0050fa006006216005006", + "0x2100501d00602d0052160050230053f40060230052160052070050e9006", + "0x500601200602d02100c00502d00521600502d0053ef006021005216005", + "0x3ef00602800521600502800501d00602f0052160052080053ee006006216", + "0x20c00534300600621600500601200602f02800c00502f00521600502f005", + "0x1d00613d00521600513b0053ee00613b0052160050060bd006006216005", + "0x1200613d00500c00513d00521600513d0053ef006005005216005005005", + "0x500521600500500501d0061410052160050100053ee006006216005006", + "0x1520060062160050060c400614100500c0051410052160051410053ef006", + "0x2000521600501a0051e9006006216005019005173006006216005012005", + "0x502300620d02300c216005021005021006021005216005020005020006", + "0x20c02600c21600502500502100602500521600500620d006006216005023", + "0x520c00502500602800521600520d005025006006216005026005023006", + "0x3f500621600c02a02800c20c00602800521600502800502600602a005216", + "0x21600501e0050230060062160051e90050ca006006216005006012006006", + "0x2160050063f600620a00521600500620800600621600501d00520a006006", + "0x620700521600520820a00c02f00620800521600520800502d006208005", + "0x502f0053f700602f00521600520702d00c13d00602d00521600500613b", + "0x600500521600500500514600600600521600500600501a00613b005216", + "0x501000514800612500521600512500519900600c00521600500c00519b", + "0x613b01012500c00500601d00513b00521600513b00521f006010005216", + "0x14100521600513d0053f800613d0052160050060bd006006216005006012", + "0xc00519b00600500521600500500514600600600521600500600501a006", + "0x1000521600501000514800612500521600512500519900600c005216005", + "0x1e0050260061410052160051410053f900601d00521600501d00502d006", + "0x1012500c00500601e3fa0061e90052160051e90050cf00601e005216005", + "0x521600c03900516f00603903514a14814603101d2160051e901e14101d", + "0x3fc00603c00521600501700516e00600621600500601200603a0053fb017", + "0x21600503100501a00603d00521600514b0053fd00614b00521600503c005", + "0x19900614800521600514800519b006146005216005146005146006031005", + "0x21600503d00521f00603500521600503500514800614a00521600514a005", + "0x3a0053f700600621600500601200603d03514a14814603101d00503d005", + "0x14600521600514600514600603100521600503100501a00603f005216005", + "0x3500514800614a00521600514a00519900614800521600514800519b006", + "0x3f03514a14814603101d00503f00521600503f00521f006035005216005", + "0x500600501a00601201000c21600500c0053fe0060062160050060c4006", + "0x601000521600501000501d006005005216005005005146006006005216", + "0x1a01901d12521600512501000500601016d00612500521600512500504e", + "0x51700060062160050060120061e90053ff01e00521600c01a005050006", + "0x521600501d00501a00600621600502100505100602102000c21600501e", + "0x504e00601200521600501200540000601900521600501900514600601d", + "0x5000602520d02312521600502001201901d010401006020005216005020", + "0x502600517000600621600500601200620c00540202600521600c025005", + "0x521600520a00511300620a00521600502a02800c11100602a02800c216", + "0x514200620d00521600520d00514600602300521600502300501a006208", + "0x20c00514500600621600500601200620820d023125005208005216005208", + "0x20d00521600520d00514600602300521600502300501a006207005216005", + "0x600621600500601200620720d023125005207005216005207005142006", + "0x21600501d00501a00602d0052160051e9005145006006216005012005403", + "0x12500502d00521600502d00514200601900521600501900514600601d005", + "0x50060bd0060062160050100051520060062160050060c400602d01901d", + "0x600600521600500600501a00601900521600501d0053f800601d005216", + "0x512500514800600c00521600500c005199006005005216005005005146", + "0x60190052160050190053f900601200521600501200502d006125005216", + "0x2100504a0060210201e901e01a01221600501901212500c00500601d404", + "0x521600502300516200600621600500601200620d00540502300521600c", + "0x501a00620c005216005026005174006026005216005025005057006025", + "0x52160051e900519900601e00521600501e00514600601a00521600501a", + "0x1a01200520c00521600520c00514a0060200052160050200051480061e9", + "0x1a00602800521600520d00514100600621600500601200620c0201e901e", + "0x2160051e900519900601e00521600501e00514600601a00521600501a005", + "0x1200502800521600502800514a0060200052160050200051480061e9005", + "0xc400600621600500600c0060190052160050060060060280201e901e01a", + "0x521600500500514600601e01a00c216005010005044006006216005006", + "0x210201e912521600512500500c126006125005216005125005148006005", + "0x516100600621600500601200620d00540602300521600c0210050f5006", + "0x600521600500600501a0060260052160050062ef006025005216005023", + "0x2000514800600c00521600500c0050310061e90052160051e9005146006", + "0x2600521600502600502d00602500521600502500518d006020005216005", + "0x15600620a02a01d02820c01221600502602501e02000c1e90060190ba006", + "0x1200620700540720800521600c20a00507000601d00521600501d01900c", + "0x2f00521600c02d00512f00602d005216005208005196006006216005006", + "0x1200506d00600621600502f00505100600621600500601200613b005408", + "0x640900613d00521600500620800600621600501a005152006006216005", + "0x521600514113d00c02f00614100521600514100502d006141005216005", + "0x52cc00614800521600503114600c13d00614600521600500613b006031", + "0x521600502800514600620c00521600520c00501a00614a005216005148", + "0x52cd00602a00521600502a00514800601d00521600501d005031006028", + "0x5100600621600500601200614a02a01d02820c01200514a00521600514a", + "0x603d14b03c03a01703903501921600501a0050fc00600621600513b005", + "0x2800514600620c00521600520c00501a00604003f00c21600503500540a", + "0x504002a02820c01040b00602a00521600502a005148006028005216005", + "0x601200615600540d04700521600c15200540c00615204415014e010216", + "0x4a00521600515e00540e00615e01200c2160050120052f3006006216005", + "0x4a00540f006044005216005044005148006150005216005150005146006", + "0x541000616804c16212521600504a03f04415001021e00604a005216005", + "0x21600504700541200600621600500601200616d00541104e00521600c168", + "0x605205100c21600504e005414006170005216005050005413006050005", + "0x3905101915000617200521600517001200c415006006216005052005051", + "0x514600614e00521600514e00501a00605500521600503d14b03c03a017", + "0x521600517200541600604c00521600504c005148006162005216005162", + "0x17800518600617805917405701021600517205504c16214e012417006172", + "0xc21600505b0052da00600621600500601200617a00541805b00521600c", + "0x5d00c2df0061810052160050060bd00600621600517f00505100617f05d", + "0x521600505700501a00618500521600506100521b006061005216005181", + "0x514800601d00521600501d005031006174005216005174005146006057", + "0x18505901d1740570120051850052160051850052cd006059005216005059", + "0x21600505700501a00618600521600517a0052cc006006216005006012006", + "0x14800601d00521600501d005031006174005216005174005146006057005", + "0x5901d1740570120051860052160051860052cd006059005216005059005", + "0x21600503d00515300600621600501200506d006006216005006012006186", + "0x503a0051f200600621600503c00510100600621600514b005103006006", + "0x47005419006006216005039005155006006216005017005158006006216", + "0x614e00521600514e00501a00606500521600516d0052cc006006216005", + "0x504c00514800601d00521600501d005031006162005216005162005146", + "0x1200606504c01d16214e0120050650052160050650052cd00604c005216", + "0x15300600621600503900515500600621600501200506d006006216005006", + "0x600621600503c00510100600621600514b00510300600621600503d005", + "0x621600503f0050fe00600621600501700515800600621600503a0051f2", + "0x15000514600614e00521600514e00501a0060670052160051560052cc006", + "0x4400521600504400514800601d00521600501d005031006150005216005", + "0x21600500601200606704401d15014e0120050670052160050670052cd006", + "0x52070052cc00600621600501a00515200600621600501200506d006006", + "0x602800521600502800514600620c00521600520c00501a006069005216", + "0x50690052cd00602a00521600502a00514800601d00521600501d005031", + "0x1200506d00600621600500601200606902a01d02820c012005069005216", + "0x515200600621600501900502800600621600501a005152006006216005", + "0x600521600500600501a00606b00521600520d0052cc00600621600501e", + "0x2000514800600c00521600500c0050310061e90052160051e9005146006", + "0x606b02000c1e900601200506b00521600506b0052cd006020005216005", + "0x62160050120051550061e901e01a01901d0120100192160051250050fc", + "0x21600501a0051010060062160050190051f200600621600501d005158006", + "0x500600501a0060062160051e900515300600621600501e005103006006", + "0x600c00521600500c005148006005005216005005005146006006005216", + "0x521600c20d00540c00620d02302102001021600501000c00500601040b", + "0x41300620c00521600502500541200600621600500601200602600541a025", + "0x21600502a00541c00602a00521600502800541b00602800521600520c005", + "0x14800602100521600502100514600602000521600502000501a00620a005", + "0x20a02302102001000520a00521600520a00541d006023005216005023005", + "0x21600502000501a00620800521600502600541e006006216005006012006", + "0x41d006023005216005023005148006021005216005021005146006020005", + "0x100192160051250050fc006208023021020010005208005216005208005", + "0x621600501d0051580060062160050100050fe0061e901e01a01901d012", + "0x21600501e00510300600621600501a0051010060062160050190051f2006", + "0x500514600600600521600500600501a0060062160051e9005153006006", + "0x501200c00500601035700600c00521600500c005148006005005216005", + "0x601200602600541f02500521600c20d00508f00620d023021020010216", + "0x602800521600520c00535100620c005216005025005090006006216005", + "0x502100514600602000521600502000501a00602a005216005028005352", + "0x502a00521600502a005353006023005216005023005148006021005216", + "0x620a00521600502600535400600621600500601200602a023021020010", + "0x502300514800602100521600502100514600602000521600502000501a", + "0x600600620a02302102001000520a00521600520a005353006023005216", + "0x120053300060062160050060c400600621600500600c006019005216005", + "0x21600501e0051ab0061e901e00c21600501a00509100601a01200c216005", + "0x52f100602100521600502000533d0060200052160051e900533c006006", + "0x521600520d0050c100600621600502300520a00620d02300c216005021", + "0x62ef00600621600502600520a00620c02600c2160050250052f1006025", + "0x621600502a00520a00620a02a00c2160050280052f1006028005216005", + "0x20800c33e00620700521600520a0050c100620800521600520c0050c1006", + "0x621600c02d00533f00602d00521600502d00502d00602d005216005207", + "0x13b0052f900613b0052160050060bd00600621600500601200602f005420", + "0x120060064210050061b000614100521600513d00519d00613d005216005", + "0x3090060310052160050060bd00600621600502f005343006006216005006", + "0x21600514100542200614100521600514600519d006146005216005031005", + "0x542314a00521600c14800512f00614800521600514800519d006148005", + "0x2160050120051ab00600621600514a005051006006216005006012006035", + "0x216005006208006006216005010005152006006216005019005028006006", + "0xc02f00601700521600501700502d006017005216005006424006039005", + "0x21600503a03c00c13d00603c00521600500613b00603a005216005017039", + "0x14600600600521600500600501a00603d00521600514b0052cc00614b005", + "0x21600512500514800600c00521600500c005031006005005216005005005", + "0x601200603d12500c00500601200503d00521600503d0052cd006125005", + "0x604003f00c216005010005044006006216005035005051006006216005", + "0x12500500c425006125005216005125005148006005005216005005005146", + "0x601200604700542615200521600c04400521d00604415014e125216005", + "0x615e005216005156005428006156005216005152005427006006216005", + "0x605705517205205117005016d04e16804c16204a02321600515e005429", + "0x621600516800516800600621600504c00542a00600621600504a00520a", + "0x21600505000520a00600621600516d00520a00600621600504e00520a006", + "0x505200516800600621600505100542a00600621600517000542b006006", + "0x57005168006006216005055005023006006216005172005023006006216", + "0x14600600600521600500600501a0061740052160050062ef006006216005", + "0x21600515000514800600c00521600500c00503100614e00521600514e005", + "0xba00617400521600517400502d00616200521600516200518d006150005", + "0x1900c15600617a05b01d17805901221600517416204015000c14e006019", + "0x500601200617f00542c05d00521600c17a00507000601d00521600501d", + "0x542d06100521600c18100512f00618100521600505d005196006006216", + "0x21600503f005152006006216005061005051006006216005006012006185", + "0x21600500642e0061860052160050062080060062160050120051ab006006", + "0x606700521600506518600c02f00606500521600506500502d006065005", + "0x506b0052cc00606b00521600506706900c13d00606900521600500613b", + "0x617800521600517800514600605900521600505900501a00618a005216", + "0x518a0052cd00605b00521600505b00514800601d00521600501d005031", + "0x18500505100600621600500601200618a05b01d17805901200518a005216", + "0x42f00606d01200c2160050120053300060062160050060b4006006216005", + "0x62160050060120060721960701254300ba18d00c21600c06d05b178125", + "0x518d00514600619d00521600519a00539e00619a0052160050060bd006", + "0x61a100521600519d00539f0061a00052160050ba005148006076005216", + "0x61a20052160050720053a10060062160050060120060064310050061b0", + "0x51a200539f0061a0005216005196005148006076005216005070005146", + "0x4331a400521600c07b0050b000607b0052160051a10054320061a1005216", + "0x2160051a40050a90060062160050060c40060062160050060120061a8005", + "0x514600605900521600505900501a00607e005216005012005434006006", + "0x521600507e0054350061a00052160051a0005148006076005216005076", + "0x8e00518600608e08d08c1ab01021600507e03f1a007605901243600607e", + "0xc21600508f0052da00600621600500601200609000543708f00521600c", + "0x9100c2df0060930052160050060bd006006216005092005051006092091", + "0x52160051ab00501a00609500521600509400521b006094005216005093", + "0x514800601d00521600501d00503100608c00521600508c0051460061ab", + "0x9508d01d08c1ab0120050950052160050950052cd00608d00521600508d", + "0x2160051ab00501a0060960052160050900052cc006006216005006012006", + "0x14800601d00521600501d00503100608c00521600508c0051460061ab005", + "0x8d01d08c1ab0120050960052160050960052cd00608d00521600508d005", + "0x621600503f0051520060062160050060c4006006216005006012006096", + "0x505900501a0061b50052160051a80052cc0060062160050120051ab006", + "0x601d00521600501d005031006076005216005076005146006059005216", + "0x1d0760590120051b50052160051b50052cd0061a00052160051a0005148", + "0x50120051ab00600621600503f0051520060062160050060120061b51a0", + "0x14600605900521600505900501a00609b00521600517f0052cc006006216", + "0x21600505b00514800601d00521600501d005031006178005216005178005", + "0x601200609b05b01d17805901200509b00521600509b0052cd00605b005", + "0x50280060062160050120051ab00600621600503f005152006006216005", + "0x609c0052160050470052cc006006216005040005152006006216005019", + "0x500c00503100614e00521600514e00514600600600521600500600501a", + "0x509c00521600509c0052cd00615000521600515000514800600c005216", + "0x600621600500600c00601a00521600500600600609c15000c14e006012", + "0x52160051250051480060050052160050050051460060062160050060c4", + "0x2100521600c02000521d0060201e901e12521600512500500c425006125", + "0x542800620d005216005021005427006006216005006012006023005438", + "0x620720820a02a02820c0260192160050100050fc00602500521600520d", + "0x51e900514800601e00521600501e00514600602d00521600501200540e", + "0x21600502d0261e901e01021e00602d00521600502d00540f0061e9005216", + "0x14100521600c13d00541000602500521600502500543900613d13b02f125", + "0x514800602f00521600502f00514600600621600500601200603100543a", + "0x1d20c13b02f01043b00601d00521600501d00508c00613b00521600513b", + "0x601200603900543d03500521600c14a00543c00614a148146125216005", + "0x4415014e04003f03d14b03c03a017023216005025005429006006216005", + "0x516800600621600503c00542a00600621600501700520a006156047152", + "0x20a00600621600503f00520a00600621600503d00520a00600621600514b", + "0x600621600515000542a00600621600514e00542b006006216005040005", + "0x6216005047005023006006216005152005023006006216005044005168", + "0x21600503a0052f300615e0052160050062ef006006216005156005168006", + "0x604c0052160050060bd00616200521600504a15e00c2e600604a03a00c", + "0x514600514600600600521600500600501a00616800521600504c0052f9", + "0x614800521600514800514800600c00521600500c005031006146005216", + "0x1460060192fb00616800521600516800519d0061620052160051620052e7", + "0x21600501901a00c15600617005001916d04e01221600516816220814800c", + "0x600621600500601200605200543e05100521600c1700052fc006019005", + "0x5016d00c43f00605000521600505000514800616d00521600516d005146", + "0x601200605900544017400521600c0570050f5006057055172125216005", + "0x600621600505b00505100605b17800c216005141005414006006216005", + "0x50510052fe00600621600505d00505100605d17a00c216005035005441", + "0x606100521600517400516100600621600518100505100618117f00c216", + "0x21600520717f20a02a02817a17801915000618500521600503a06100c442", + "0x14800617200521600517200514600604e00521600504e00501a006186005", + "0x5517204e012444006185005216005185005443006055005216005055005", + "0x6d00544518a00521600c06b00518600606b069067065010216005185186", + "0x50ba0050510060ba18d00c21600518a0052da006006216005006012006", + "0x21b00619600521600507018d00c2df0060700052160050060bd006006216", + "0x21600506700514600606500521600506500501a006072005216005196005", + "0x2cd006069005216005069005148006019005216005019005031006067005", + "0x6006216005006012006072069019067065012005072005216005072005", + "0x506700514600606500521600506500501a00619a00521600506d0052cc", + "0x6069005216005069005148006019005216005019005031006067005216", + "0x621600500601200619a06901906706501200519a00521600519a0052cd", + "0x216005141005447006006216005207005153006006216005035005446006", + "0x502800515800600621600502a0051f200600621600520a005101006006", + "0x590052cc00600621600503a00506d006006216005051005448006006216", + "0x17200521600517200514600604e00521600504e00501a00619d005216005", + "0x19d0052cd006055005216005055005148006019005216005019005031006", + "0x544600600621600500601200619d05501917204e01200519d005216005", + "0x44700600621600520700515300600621600503a00506d006006216005035", + "0x600621600502a0051f200600621600520a005101006006216005141005", + "0x21600504e00501a0060760052160050520052cc006006216005028005158", + "0x14800601900521600501900503100616d00521600516d00514600604e005", + "0x5001916d04e0120050760052160050760052cd006050005216005050005", + "0x216005025005449006006216005028005158006006216005006012006076", + "0x520700515300600621600502a0051f200600621600520a005101006006", + "0x20800510300600621600501a005028006006216005141005447006006216", + "0x600600521600500600501a0061a00052160050390052cc006006216005", + "0x514800514800600c00521600500c005031006146005216005146005146", + "0x120061a014800c1460060120051a00052160051a00052cd006148005216", + "0x101006006216005025005449006006216005028005158006006216005006", + "0x600621600520700515300600621600502a0051f200600621600520a005", + "0x621600501d0051ab00600621600520800510300600621600501a005028", + "0x500600501a0061a10052160050310052cc00600621600520c005155006", + "0x600c00521600500c00503100602f00521600502f005146006006005216", + "0xc02f0060120051a10052160051a10052cd00613b00521600513b005148", + "0x501000515200600621600501200506d0060062160050060120061a113b", + "0x230052cc00600621600501a00502800600621600501d0051ab006006216", + "0x1e00521600501e00514600600600521600500600501a0061a2005216005", + "0x1a20052cd0061e90052160051e900514800600c00521600500c005031006", + "0x600600521600500600501a0061a21e900c01e0060120051a2005216005", + "0xc00601044a00601200521600501200502d00600c00521600500c005031", + "0x500620d00601e00521600501a00544b00601a01901d125216005012010", + "0x21600c01e1e912500501044c0061e90052160051e90050260061e9005216", + "0x502300544e00600621600500601200602602520d12544d023021020125", + "0x602a00521600502100514800602800521600502000514600620c005216", + "0x60062160050060120060064500050061b000620a00521600520c00544f", + "0x502500514800602800521600520d005146006208005216005026005451", + "0x620700521600520a00545200620a00521600520800544f00602a005216", + "0x2d00514f00600621600500601200602f00545302d00521600c207005107", + "0x14100521600513d00545500613d00521600513b00545400613b005216005", + "0x1900503100602800521600502800514600601d00521600501d00501a006", + "0x14100521600514100545600602a00521600502a005148006019005216005", + "0x521600502f00545700600621600500601200614102a01902801d012005", + "0x503100602800521600502800514600601d00521600501d00501a006031", + "0x521600503100545600602a00521600502a005148006019005216005019", + "0x52160050060350060062160050060c400603102a01902801d012005031", + "0x1e901e00c21600c01a00500612501700601a00521600501a00503900601a", + "0x602301000c21600501000510500600621600500601200602102000c458", + "0x502d00602500521600520d02300c33e00620d01200c216005012005105", + "0x621600c02500533f00601e00521600501e00501a006025005216005025", + "0x1000520a006006216005019005152006006216005006012006026005459", + "0x2800521600520c01201d12545a00620c0052160050060bd006006216005", + "0x1e900514600601e00521600501e00501a00602a00521600502800545b006", + "0x12500521600512500514800600c00521600500c0050310061e9005216005", + "0x21600500601200602a12500c1e901e01200502a00521600502a00545c006", + "0x50fc00620a01900c216005019005157006006216005026005343006006", + "0x1550060062160052080050fe00614113d13b02f02d20720801921600520a", + "0x600621600513b00510100600621600502d005158006006216005207005", + "0x521600501e00501a00600621600514100515300600621600513d005103", + "0x514800600c00521600500c0050310061e90052160051e900514600601e", + "0x21600503100502d00603101200c216005012005105006125005216005125", + "0x603903514a14814601221600503102f12500c1e901e01d151006031005", + "0x1700514f00600621600500601200603a00545d01700521600c039005107", + "0x3d00521600500610900614b00521600503c01d00c02f00603c005216005", + "0x514600614600521600514600501a00603f00521600503d01200c14d006", + "0x521600503500514800614a00521600514a005031006148005216005148", + "0x504e00603f00521600503f00502d00601000521600501000502d006035", + "0x4001221600501914b03f01003514a14814601a14c00614b00521600514b", + "0x21600500601200615600545e04700521600c15200510c00615204415014e", + "0x521600516204a15e12545a00616204a15e125216005047005149006006", + "0x514600604000521600504000501a00616800521600504c00545b00604c", + "0x521600504400514800615000521600515000503100614e00521600514e", + "0x500601200616804415014e04001200516800521600516800545c006044", + "0x14600604000521600504000501a00604e00521600515600545f006006216", + "0x21600504400514800615000521600515000503100614e00521600514e005", + "0x601200604e04415014e04001200504e00521600504e00545c006044005", + "0x520a00600621600501200520a006006216005019005152006006216005", + "0x616d00521600503a00545f00600621600501d005172006006216005010", + "0x514a00503100614800521600514800514600614600521600514600501a", + "0x516d00521600516d00545c00603500521600503500514800614a005216", + "0x600621600501000520a00600621600500601200616d03514a148146012", + "0x621600501200520a00600621600501d005172006006216005019005152", + "0x21600517000502d006170005216005006059006050005216005006208006", + "0x13d00605200521600500613b00605100521600517005000c02f006170005", + "0x502000501a00605500521600517200545f00617200521600505105200c", + "0x600c00521600500c005031006021005216005021005146006020005216", + "0xc02102001200505500521600505500545c006125005216005125005148", + "0x50100050390060100052160050060350060062160050060c4006055125", + "0x1200601a01900c46001d01200c21600c010005006125017006010005216", + "0x1a0060062160050060b400601e00521600500c0051e9006006216005006", + "0x120060210054610201e900c21600c01e0051b6006012005216005012005", + "0x20d0052160051e90051b40060230052160050200050b8006006216005006", + "0x62160050060120060064620050061b00060250052160050230051b2006", + "0x50210051b400620c0052160050260051ac0060260052160050060bd006", + "0x46302800521600c0250051ae00602500521600520c0051b200620d005216", + "0x2160050280050bf0060062160050060c400600621600500601200602a005", + "0x21a00612500521600512500504e00620a00521600520a00502d00620a005", + "0x520d00505500600621600520700505100620720800c21600512520a00c", + "0x601d00521600501d00514600601200521600501200501a00602d005216", + "0x1d01201014300620800521600520800504e00602d00521600502d00501d", + "0x603100546414100521600c13d00505000613d13b02f12521600520802d", + "0x21600514800505100614814600c216005141005170006006216005006012", + "0x504e00603500521600513b00514600614a00521600502f00501a006006", + "0x51450060062160050060120060064650050061b0006039005216005146", + "0x521600513b00514600602f00521600502f00501a006017005216005031", + "0x621600500601200601713b02f12500501700521600501700514200613b", + "0x621600520d0050d400600621600502a0050510060062160050060c4006", + "0x12500504e00603500521600501d00514600614a00521600501200501a006", + "0x3c00521600503a03900c11100603a0052160050060bd006039005216005", + "0x3500514600614a00521600514a00501a00614b00521600503c005113006", + "0x601200614b03514a12500514b00521600514b005142006035005216005", + "0x620800600621600500c005168006006216005125005172006006216005", + "0x603f00521600503f00502d00603f00521600500605900603d005216005", + "0x4014e00c13d00614e00521600500613b00604000521600503f03d00c02f", + "0x1900521600501900501a006044005216005150005145006150005216005", + "0x1a01912500504400521600504400514200601a00521600501a005146006", + "0x2160050120050390060120052160050060350060062160050060c4006044", + "0x601200601e01a00c46601901d00c21600c012005006125017006012005", + "0x1d00521600501d00501a0061e901000c216005010005105006006216005", + "0x1000520a00600621600500601200602000546700621600c1e900533f006", + "0x2300521600502100c00c396006021005216005125005468006006216005", + "0x1900514600601d00521600501d00501a00620d005216005023005397006", + "0x601200620d01901d12500520d00521600520d005398006019005216005", + "0x1900600c00521600500c00501d006006216005020005343006006216005", + "0x1200602800546920c00521600c02600501e00602602500c21600500c005", + "0x20a00521600500610900602a00521600520c12500c02f006006216005006", + "0x514600601d00521600501d00501a00620800521600520a01000c33e006", + "0x521600502a00504e00602500521600502500501d006019005216005019", + "0x12521600520802a02501901d01211500620800521600520800502d00602a", + "0x621600500601200613d00546a13b00521600c02f0050a600602f02d207", + "0x39700614600521600503114100c39600603114100c21600513b0050a8006", + "0x21600502d00514600620700521600520700501a006148005216005146005", + "0x21600500601200614802d20712500514800521600514800539800602d005", + "0x514600620700521600520700501a00614a00521600513d005399006006", + "0x1200614a02d20712500514a00521600514a00539800602d00521600502d", + "0x39a00600621600512500517200600621600501000520a006006216005006", + "0x503900539700603900521600503502500c396006035005216005028005", + "0x601900521600501900514600601d00521600501d00501a006017005216", + "0x20a00600621600500601200601701901d125005017005216005017005398", + "0x600621600500c005168006006216005125005172006006216005010005", + "0x521600503c00502d00603c00521600500605900603a005216005006208", + "0xc13d00603d00521600500613b00614b00521600503c03a00c02f00603c", + "0x21600501a00501a00604000521600503f00539900603f00521600514b03d", + "0x12500504000521600504000539800601e00521600501e00514600601a005", + "0x600500521600500500514800600600521600500600514600604001e01a", + "0x546d01200521600c01000546c00601012500c12521600500500600c46b", + "0x501900546f00601900521600501200546e00600621600500601200601d", + "0x501e0054710060230210201e901e01221600501a00547000601a005216", + "0x2300520a00600621600502100506d0060062160051e9005472006006216", + "0x602500521600520d00541c00620d00521600502000541b006006216005", + "0x502500541d00612500521600512500514800600c00521600500c005146", + "0x21600501d00541e00600621600500601200602512500c125005025005216", + "0x41d00612500521600512500514800600c00521600500c005146006026005", + "0x1a01900c21600501000547300602612500c125005026005216005026005", + "0x1200502d00600c00521600500c00503100600600521600500600501a006", + "0x544b0060201e901e12521600501201a00c00601044a006012005216005", + "0x2300521600502300502600602300521600500620d006021005216005020", + "0x1200602820c02612547502520d00c21600c01d021023125005012474006", + "0x620a00521600502a00539e00602a0052160050060bd006006216005006", + "0x520a00539f00620700521600502500514800620800521600520d005146", + "0x50280053a10060062160050060120060064760050061b000602d005216", + "0x620700521600520c00514800620800521600502600514600602f005216", + "0xc13b0050b000613b00521600502d0053a200602d00521600502f00539f", + "0x3100521600513d00547800600621600500601200614100547713d005216", + "0x501a00614800521600514600547a00614600521600503101900c479006", + "0x52160051e900503100620800521600520800514600601e00521600501e", + "0x1e01200514800521600514800547b0062070052160052070051480061e9", + "0x547c0060062160050190051f20060062160050060120061482071e9208", + "0x521600520800514600601e00521600501e00501a00614a005216005141", + "0x547b0062070052160052070051480061e90052160051e9005031006208", + "0x2d70060062160050060c400614a2071e920801e01200514a00521600514a", + "0x5216005006208006012005216005010005226006010005216005010005", + "0x547e00601e01a00c21600501200547d00601900521600500620800601d", + "0x500521600500500514600600600521600500600501a00600621600501a", + "0x1900504e00601d00521600501d00504e00601e00521600501e00547f006", + "0x4810060210201e912521600501901d01e005006012480006019005216005", + "0x502300548300600621600500601200620d00548202300521600c021005", + "0x2800c21600502500505200600621600520c00505100620c026025125216", + "0x2600505200620a00521600502a00505500600621600502800517200602a", + "0x2d00521600520700505500600621600520800517200620720800c216005", + "0x21600502d00501d00620a00521600520a00501d0060062160050060b4006", + "0x603114113d12548413b02f00c21600c02d20a00c02001039c00602d005", + "0x14800521600514600539e0061460052160050060bd006006216005006012", + "0x14800539f00603500521600513b00514800614a00521600502f005146006", + "0x310053a10060062160050060120060064850050061b0006039005216005", + "0x3500521600514100514800614a00521600513d005146006017005216005", + "0x3a0050b000603a0052160050390053a200603900521600501700539f006", + "0x60062160050060c400600621600500601200614b00548603c00521600c", + "0x3f00521b00603f00521600503d12500c2df00603d00521600503c005478", + "0x14a00521600514a0051460061e90052160051e900501a006040005216005", + "0x14a1e90100050400052160050400052cd006035005216005035005148006", + "0x2160051250051520060062160050060c4006006216005006012006040035", + "0x51460061e90052160051e900501a00614e00521600514b0052cc006006", + "0x521600514e0052cd00603500521600503500514800614a00521600514a", + "0x621600512500515200600621600500601200614e03514a1e901000514e", + "0x200051460061e90052160051e900501a00615000521600520d0052cc006", + "0x1500052160051500052cd00600c00521600500c005148006020005216005", + "0x60190052160050060350060062160050060c400615000c0201e9010005", + "0xc48701e01a00c21600c019005006125017006019005216005019005039", + "0x50060b40060210052160050100051e90060062160050060120060201e9", + "0x48820d02300c21600c0210051b600601a00521600501a00501a006006216", + "0x230051b400602600521600520d0050b8006006216005006012006025005", + "0x120060064890050061b00060280052160050260051b200620c005216005", + "0x620a00521600502a0051ac00602a0052160050060bd006006216005006", + "0x520c00505500602800521600520a0051b200620c0052160050250051b4", + "0x48a20700521600c0280051ae00620800521600520800501d006208005216", + "0x2160052070050bf0060062160050060c400600621600500601200602d005", + "0x14600601a00521600501a00501a00613b00521600502f0050c100602f005", + "0x21600512500514800600c00521600500c00503100601e00521600501e005", + "0x613d00521600513d00502d00613d01d00c21600501d005105006125005", + "0x1221600513b13d01212500c01e01a0192d300613b00521600513b00502d", + "0x500601200603900548b03500521600c14a0052d400614a148146031141", + "0x2d600603a00521600501701d00c14d006017005216005006109006006216", + "0x21600514100501a00600621600514b00505100614b03c00c216005035005", + "0x148006146005216005146005031006031005216005031005146006141005", + "0x21600503a00502d00620800521600520800501d006148005216005148005", + "0x15014e04003f03d01221600503a03c2081481460311410192db00603a005", + "0x52de00600621600500601200615200548c04400521600c1500052dc006", + "0x616200521600504a15e15604701048d00604a15e156047010216005044", + "0x503f00514600603d00521600503d00501a00604c00521600516200548e", + "0x614e00521600514e00514800604000521600504000503100603f005216", + "0x621600500601200604c14e04003f03d01200504c00521600504c00548f", + "0x3f00514600603d00521600503d00501a006168005216005152005490006", + "0x14e00521600514e00514800604000521600504000503100603f005216005", + "0x21600500601200616814e04003f03d01200516800521600516800548f006", + "0x503900549000600621600520800516800600621600501d00520a006006", + "0x603100521600503100514600614100521600514100501a00604e005216", + "0x504e00548f006148005216005148005148006146005216005146005031", + "0x50060c400600621600500601200604e14814603114101200504e005216", + "0x20801048d00616d0052160050060bd00600621600502d005051006006216", + "0x501a00501a00617000521600505000548e00605000521600516d01d012", + "0x600c00521600500c00503100601e00521600501e00514600601a005216", + "0xc01e01a01200517000521600517000548f006125005216005125005148", + "0x501000516800600621600501d00520a006006216005006012006170125", + "0x50060590060510052160050062080060062160050120051f2006006216", + "0x17200521600505205100c02f00605200521600505200502d006052005216", + "0x5700549000605700521600517205500c13d00605500521600500613b006", + "0x200052160050200051460061e90052160051e900501a006174005216005", + "0x17400548f00612500521600512500514800600c00521600500c005031006", + "0x600600521600500600501a00617412500c0201e9012005174005216005", + "0xc0060104910060120052160050120052e700600c00521600500c005031", + "0x500620d00601e00521600501a00544b00601a01901d125216005012010", + "0x21600c01e1e912500501044c0061e90052160051e90050260061e9005216", + "0x50230052f100600621600500601200602602520d125492023021020125", + "0x2f100602a0052160050062ef00600621600520c00520a00602820c00c216", + "0x2160050280050c100600621600520a00520a00620820a00c21600502a005", + "0xc100600621600502d00520a00602f02d00c2160052070052f1006207005", + "0x513d00520a00614113d00c21600513b0052f100613b005216005208005", + "0x2d0061460052160051410050c100603100521600502f0050c1006006216", + "0x514800502d00614800521600514603100c33e006031005216005031005", + "0x6021005216005021005148006020005216005020005146006148005216", + "0x2160050060bd00600621600500601200614a00549300621600c14800533f", + "0x1b000601700521600503900519d0060390052160050350052f9006035005", + "0x60bd00600621600514a005343006006216005006012006006494005006", + "0x1700521600503c00519d00603c00521600503a00530900603a005216005", + "0x2000514600603d00521600514b00549500614b005216005017005422006", + "0x14e00521600503d00549600604000521600502100514800603f005216005", + "0x1500052160050260054980060062160050060120060064970050061b0006", + "0x15000549600604000521600502500514800603f00521600520d005146006", + "0x15200521600c04400507000604400521600514e00549900614e005216005", + "0x517c00615600521600515200519600600621600500601200604700549a", + "0x521600501d00501a00604a00521600515e0052ea00615e005216005156", + "0x514800601900521600501900503100603f00521600503f00514600601d", + "0x4a04001903f01d01200504a00521600504a0052eb006040005216005040", + "0x21600501d00501a0061620052160050470052ec006006216005006012006", + "0x14800601900521600501900503100603f00521600503f00514600601d005", + "0x4001903f01d0120051620052160051620052eb006040005216005040005", + "0xbd00612500521600500c00500c02f00600c0052160050060050c1006162", + "0x549b00601012500c00512500521600512500504e006010005216005006", + "0x21600500c00503100600600521600500600501a00601a01900c216005010", + "0x12521600501201a00c0060104910060120052160050120052e700600c005", + "0x602300521600502000544b00602100521600501d00549c0060201e901e", + "0x20d12500501247400620d00521600520d00502600620d00521600500620d", + "0xbd00600621600500601200602a02820c12549d02602500c21600c021023", + "0x521600502500514600620800521600520a00539e00620a005216005006", + "0x61b000602f00521600520800539f00602d005216005026005148006207", + "0x514600613b00521600502a0053a100600621600500601200600649e005", + "0x521600513b00539f00602d00521600502800514800620700521600520c", + "0x3100549f14100521600c13d0050b000613d00521600502f0053a200602f", + "0x514601900c4a0006146005216005141005478006006216005006012006", + "0x601e00521600501e00501a00614a0052160051480054a1006148005216", + "0x502d0051480061e90052160051e9005031006207005216005207005146", + "0x1200614a02d1e920701e01200514a00521600514a0054a200602d005216", + "0x60350052160050310054a3006006216005019005103006006216005006", + "0x51e900503100620700521600520700514600601e00521600501e00501a", + "0x50350052160050350054a200602d00521600502d0051480061e9005216", + "0x100052160050100053000060062160050060c400603502d1e920701e012", + "0x21600500620800601d0052160050062080060120052160050100054a4006", + "0x1a00600621600501a00547e00601e01a00c21600501200547d006019005", + "0x21600501e00547f006005005216005005005146006006005216005006005", + "0x48000601900521600501900504e00601d00521600501d00504e00601e005", + "0x2300521600c0210054810060210201e912521600501901d01e005006012", + "0x620c02602512521600502300548300600621600500601200620d0054a5", + "0x502800517200602a02800c21600502500505200600621600520c005051", + "0x620720800c21600502600505200620a00521600502a005055006006216", + "0x62160050060b400602d005216005207005055006006216005208005172", + "0x2001039c00602d00521600502d00501d00620a00521600520a00501d006", + "0x600621600500601200603114113d1254a613b02f00c21600c02d20a00c", + "0x21600502f00514600614800521600514600539e0061460052160050060bd", + "0x1b000603900521600514800539f00603500521600513b00514800614a005", + "0x1460060170052160050310053a10060062160050060120060064a7005006", + "0x21600501700539f00603500521600514100514800614a00521600513d005", + "0x54a803c00521600c03a0050b000603a0052160050390053a2006039005", + "0x521600503c0054780060062160050060c400600621600500601200614b", + "0x1a00604000521600503f00521b00603f00521600503d12500c2df00603d", + "0x21600503500514800614a00521600514a0051460061e90052160051e9005", + "0x500601200604003514a1e90100050400052160050400052cd006035005", + "0x514b0052cc0060062160051250051520060062160050060c4006006216", + "0x614a00521600514a0051460061e90052160051e900501a00614e005216", + "0x3514a1e901000514e00521600514e0052cd006035005216005035005148", + "0x21600520d0052cc00600621600512500515200600621600500601200614e", + "0x1480060200052160050200051460061e90052160051e900501a006150005", + "0x15000c0201e90100051500052160051500052cd00600c00521600500c005", + "0x120052e700600c00521600500c00503100600600521600500600501a006", + "0x544b00601a01901d12521600501201000c0060104a9006012005216005", + "0x1e90052160051e90050260061e900521600500620d00601e00521600501a", + "0x1200602602520d1254aa02302102012521600c01e1e912500501044c006", + "0x621600520c00520a00602820c00c2160050230052f1006006216005006", + "0x20a00520a00620820a00c21600502a0052f100602a0052160050062ef006", + "0x2f02d00c2160052070052f10062070052160050280050c1006006216005", + "0x513b0052f100613b0052160052080050c100600621600502d00520a006", + "0x603100521600502f0050c100600621600513d00520a00614113d00c216", + "0x14603100c33e00603100521600503100502d0061460052160051410050c1", + "0x2000521600502000514600614800521600514800502d006148005216005", + "0x1200614a0054ab00621600c14800533f006021005216005021005148006", + "0x60390052160050350052f90060350052160050060bd006006216005006", + "0x60062160050060120060064ac0050061b000601700521600503900519d", + "0x521600503a00530900603a0052160050060bd00600621600514a005343", + "0x549500614b00521600501700542200601700521600503c00519d00603c", + "0x521600502100514800603f00521600502000514600603d00521600514b", + "0x2160050060120060064ad0050061b000614e00521600503d005496006040", + "0x514800603f00521600520d005146006150005216005026005498006006", + "0x521600514e00549900614e005216005150005496006040005216005025", + "0x1960060062160050060120060470054ae15200521600c044005070006044", + "0x21600515e0052ea00615e00521600515600517c006156005216005152005", + "0x3100603f00521600503f00514600601d00521600501d00501a00604a005", + "0x21600504a0052eb006040005216005040005148006019005216005019005", + "0x50470052ec00600621600500601200604a04001903f01d01200504a005", + "0x603f00521600503f00514600601d00521600501d00501a006162005216", + "0x51620052eb006040005216005040005148006019005216005019005031", + "0x601a01900c2160050100054af00616204001903f01d012005162005216", + "0x50120052e700600c00521600500c00503100600600521600500600501a", + "0x1d00549c0060201e901e12521600501201a00c0060104a9006012005216", + "0x620d00521600500620d00602300521600502000544b006021005216005", + "0x2602500c21600c02102320d12500501247400620d00521600520d005026", + "0x39e00620a0052160050060bd00600621600500601200602a02820c1254b0", + "0x21600502600514800620700521600502500514600620800521600520a005", + "0x50060120060064b10050061b000602f00521600520800539f00602d005", + "0x14800620700521600520c00514600613b00521600502a0053a1006006216", + "0x21600502f0053a200602f00521600513b00539f00602d005216005028005", + "0x60062160050060120060310054b214100521600c13d0050b000613d005", + "0x1480054b400614800521600514601900c4b3006146005216005141005478", + "0x20700521600520700514600601e00521600501e00501a00614a005216005", + "0x14a0054b500602d00521600502d0051480061e90052160051e9005031006", + "0x515300600621600500601200614a02d1e920701e01200514a005216005", + "0x1e00521600501e00501a0060350052160050310054b6006006216005019", + "0x2d0051480061e90052160051e9005031006207005216005207005146006", + "0x603502d1e920701e0120050350052160050350054b500602d005216005", + "0x52160050100054b70060100052160050100052190060062160050060c4", + "0x501200547d00601900521600500620800601d005216005006208006012", + "0x600600521600500600501a00600621600501a00547e00601e01a00c216", + "0x501d00504e00601e00521600501e00547f006005005216005005005146", + "0x501901d01e00500601248000601900521600501900504e00601d005216", + "0x500601200620d0054b802300521600c0210054810060210201e9125216", + "0x600621600520c00505100620c026025125216005023005483006006216", + "0x502a00505500600621600502800517200602a02800c216005025005052", + "0x600621600520800517200620720800c21600502600505200620a005216", + "0x521600520a00501d0060062160050060b400602d005216005207005055", + "0x2f00c21600c02d20a00c02001039c00602d00521600502d00501d00620a", + "0x61460052160050060bd00600621600500601200603114113d1254b913b", + "0x513b00514800614a00521600502f00514600614800521600514600539e", + "0x60120060064ba0050061b000603900521600514800539f006035005216", + "0x614a00521600513d0051460060170052160050310053a1006006216005", + "0x50390053a200603900521600501700539f006035005216005141005148", + "0x621600500601200614b0054bb03c00521600c03a0050b000603a005216", + "0x503d12500c2df00603d00521600503c0054780060062160050060c4006", + "0x61e90052160051e900501a00604000521600503f00521b00603f005216", + "0x50400052cd00603500521600503500514800614a00521600514a005146", + "0x2160050060c400600621600500601200604003514a1e9010005040005216", + "0x1e900501a00614e00521600514b0052cc006006216005125005152006006", + "0x3500521600503500514800614a00521600514a0051460061e9005216005", + "0x621600500601200614e03514a1e901000514e00521600514e0052cd006", + "0x51e900501a00615000521600520d0052cc006006216005125005152006", + "0x600c00521600500c0051480060200052160050200051460061e9005216", + "0x60062160050060c400615000c0201e90100051500052160051500052cd", + "0x621600501a0051550060230210201e901e01a0190192160051250050fc", + "0x2160050200051010060062160051e90051f200600621600501e005158006", + "0x500600501a006006216005023005153006006216005021005103006006", + "0x600c00521600500c005148006005005216005005005146006006005216", + "0x521600c20c00540c00620c02602520d01021600501900c00500601040b", + "0x1a00620a00521600502800541200600621600500601200602a0054bc028", + "0x21600502600514800602500521600502500514600620d00521600520d005", + "0x2d00601000521600501000508c00620a00521600520a00540f006026005", + "0x2602520d0194bd00601d00521600501d00501d006012005216005012005", + "0x4be13b00521600c02f00504a00602f02d20720801021600501d01201020a", + "0x14100505700614100521600513b00516200600621600500601200613d005", + "0x20800521600520800501a006146005216005031005174006031005216005", + "0x14600514a00602d00521600502d005148006207005216005207005146006", + "0x13d00514100600621600500601200614602d207208010005146005216005", + "0x20700521600520700514600620800521600520800501a006148005216005", + "0x20720801000514800521600514800514a00602d00521600502d005148006", + "0x501200520a00600621600501d00516800600621600500601200614802d", + "0x501a00614a00521600502a0051410060062160050100051ab006006216", + "0x521600502600514800602500521600502500514600620d00521600520d", + "0x50061254bf00614a02602520d01000514a00521600514a00514a006026", + "0x50100054c10060062160050060120060120054c001012500c21600c00c", + "0x612500521600512500501a00601900521600501d0054c200601d005216", + "0x620800600621600500601200601912500c0050190052160050190054c3", + "0x601e00521600501e00502d00601e00521600500622800601a005216005", + "0x1e902000c13d00602000521600500613b0061e900521600501e01a00c02f", + "0x1200521600501200501a0060230052160050210054c4006021005216005", + "0x600521600500600501a00602301200c0050230052160050230054c3006", + "0x60104c500601200521600501200502d00600c00521600500c005031006", + "0x620d00601e00521600501a00544b00601a01901d12521600501201000c", + "0xc01e1e912500501044c0061e90052160051e90050260061e9005216005", + "0x1d00501a00600621600500601200602602520d1254c6023021020125216", + "0xc21600502301d00c4c700602300521600502300502d00601d005216005", + "0x7e00602100521600502100514800602000521600502000514600602820c", + "0x502a0054c900600621600500601200620a0054c802a00521600c028005", + "0x602d00521600502000514600620700521600520c00501a006208005216", + "0x4cb0050061b000613b0052160052080054ca00602f005216005021005148", + "0x521600500620800600621600520a005051006006216005006012006006", + "0x13d00c02f00614100521600514100502d0061410052160050064cc00613d", + "0x521600503114600c13d00614600521600500613b006031005216005141", + "0x514600620c00521600520c00501a00614a005216005148005354006148", + "0x5216005021005148006019005216005019005031006020005216005020", + "0x500601200614a02101902020c01200514a00521600514a005353006021", + "0x14600620700521600501d00501a0060350052160050260054cd006006216", + "0x2160050350054ca00602f00521600502500514800602d00521600520d005", + "0x54ce01700521600c03900508f00603900521600513b00522700613b005", + "0x503c00535100603c00521600501700509000600621600500601200603a", + "0x620700521600520700501a00603d00521600514b00535200614b005216", + "0x502f00514800601900521600501900503100602d00521600502d005146", + "0x1200603d02f01902d20701200503d00521600503d00535300602f005216", + "0x20700521600520700501a00603f00521600503a005354006006216005006", + "0x2f00514800601900521600501900503100602d00521600502d005146006", + "0x603f02f01902d20701200503f00521600503f00535300602f005216005", + "0xc00503100600600521600500600501a00601a01900c216005010005339", + "0x501201a00c0060104c500601200521600501200502d00600c005216005", + "0x521600502000544b00602100521600501d00533d0060201e901e125216", + "0x501247400620d00521600520d00502600620d00521600500620d006023", + "0x621600500601200602a02820c1254cf02602500c21600c02102320d125", + "0x502500514600620800521600520a00539e00620a0052160050060bd006", + "0x602f00521600520800539f00602d005216005026005148006207005216", + "0x613b00521600502a0053a10060062160050060120060064d00050061b0", + "0x513b00539f00602d00521600502800514800620700521600520c005146", + "0x4d114100521600c13d0050b000613d00521600502f0053a200602f005216", + "0x1900c4d2006146005216005141005478006006216005006012006031005", + "0x521600501e00501a00614a0052160051480054d3006148005216005146", + "0x51480061e90052160051e900503100620700521600520700514600601e", + "0x14a02d1e920701e01200514a00521600514a0054d400602d00521600502d", + "0x52160050310054d5006006216005019005101006006216005006012006", + "0x503100620700521600520700514600601e00521600501e00501a006035", + "0x52160050350054d400602d00521600502d0051480061e90052160051e9", + "0x21600501000534b0060062160050060c400603502d1e920701e012005035", + "0x620800601d0052160050062080060120052160050100054d6006010005", + "0x621600501a00547e00601e01a00c21600501200547d006019005216005", + "0x1e00547f00600500521600500500514600600600521600500600501a006", + "0x1900521600501900504e00601d00521600501d00504e00601e005216005", + "0x21600c0210054810060210201e912521600501901d01e005006012480006", + "0x2602512521600502300548300600621600500601200620d0054d7023005", + "0x517200602a02800c21600502500505200600621600520c00505100620c", + "0x20800c21600502600505200620a00521600502a005055006006216005028", + "0x50060b400602d005216005207005055006006216005208005172006207", + "0x39c00602d00521600502d00501d00620a00521600520a00501d006006216", + "0x21600500601200603114113d1254d813b02f00c21600c02d20a00c020010", + "0x2f00514600614800521600514600539e0061460052160050060bd006006", + "0x3900521600514800539f00603500521600513b00514800614a005216005", + "0x170052160050310053a10060062160050060120060064d90050061b0006", + "0x1700539f00603500521600514100514800614a00521600513d005146006", + "0x3c00521600c03a0050b000603a0052160050390053a2006039005216005", + "0x503c0054780060062160050060c400600621600500601200614b0054da", + "0x4000521600503f00521b00603f00521600503d12500c2df00603d005216", + "0x3500514800614a00521600514a0051460061e90052160051e900501a006", + "0x1200604003514a1e90100050400052160050400052cd006035005216005", + "0x52cc0060062160051250051520060062160050060c4006006216005006", + "0x521600514a0051460061e90052160051e900501a00614e00521600514b", + "0x1e901000514e00521600514e0052cd00603500521600503500514800614a", + "0x20d0052cc00600621600512500515200600621600500601200614e03514a", + "0x200052160050200051460061e90052160051e900501a006150005216005", + "0x201e90100051500052160051500052cd00600c00521600500c005148006", + "0x1000544b0060100052160050064db00600621600512500515500615000c", + "0x601d00521600501d00502600601d00521600500620d006012005216005", + "0x1e01a01912521600c01201d00c00501044c0060120052160050120054dc", + "0x600600521600500600501a0060062160050060120060210201e91254dd", + "0x514600620d02300c21600501e00600c4c700601e00521600501e00502d", + "0x521600c20d00507e00601a00521600501a005148006019005216005019", + "0x1a00620c0052160050250054c90060062160050060120060260054de025", + "0x21600501a00514800602a005216005019005146006028005216005023005", + "0x50060120060064df0050061b000620800521600520c0054ca00620a005", + "0x50064cc006207005216005006208006006216005026005051006006216", + "0x2f00521600502d20700c02f00602d00521600502d00502d00602d005216", + "0x13d00535400613d00521600502f13b00c13d00613b00521600500613b006", + "0x1900521600501900514600602300521600502300501a006141005216005", + "0x1902301000514100521600514100535300601a00521600501a005148006", + "0x600501a0060310052160050210054cd00600621600500601200614101a", + "0x20a00521600502000514800602a0052160051e9005146006028005216005", + "0x14600508f0061460052160052080052270062080052160050310054ca006", + "0x521600514800509000600621600500601200614a0054e014800521600c", + "0x501a006017005216005039005352006039005216005035005351006035", + "0x521600520a00514800602a00521600502a005146006028005216005028", + "0x21600500601200601720a02a02801000501700521600501700535300620a", + "0x514600602800521600502800501a00603a00521600514a005354006006", + "0x521600503a00535300620a00521600520a00514800602a00521600502a", + "0x1200600c0054e200500521600c0060054e100603a20a02a02801000503a", + "0x100052160051250054e40061250052160050050054e3006006216005006", + "0x613b0060062160050060120060100050050100052160050100054e5006", + "0x521600501d0054e600601d00521600500c01200c13d006012005216005", + "0x60100052160050062080060190050050190052160050190054e5006019", + "0x501d00508c0060062160050120051ab00601d01200c216005125005091", + "0x1900c21600501001d00c09200601000521600501000504e00601d005216", + "0x50064e800601e00521600500c0054e700600621600501a00505100601a", + "0x600621600502000517200602102000c2160050190050520061e9005216", + "0x502300501d0061e90052160051e900502d006023005216005021005055", + "0x20c1254ea02602520d12521600c0231e901e0050060124e9006023005216", + "0x20d00514600620a00521600502600522500600621600500601200602a028", + "0x2d00521600520a0054eb006207005216005025005148006208005216005", + "0x2f00521600502a0054ed0060062160050060120060064ec0050061b0006", + "0x2f0054eb00620700521600502800514800620800521600520c005146006", + "0x13d00521600c13b00504a00613b00521600502d0054ee00602d005216005", + "0x50060bd00600621600513d0054f00060062160050060120061410054ef", + "0x61480052160051460053a50061460052160050310053a4006031005216", + "0x51480053a6006207005216005207005148006208005216005208005146", + "0x2160051410053a7006006216005006012006148207208125005148005216", + "0x3a600620700521600520700514800620800521600520800514600614a005", + "0x536e0060062160050060c400614a20720812500514a00521600514a005", + "0x1d0052160050062080060120052160050100054f1006010005216005010", + "0x1a00547e00601e01a00c21600501200547d006019005216005006208006", + "0x600500521600500500514600600600521600500600501a006006216005", + "0x501900504e00601d00521600501d00504e00601e00521600501e00547f", + "0x54810060210201e912521600501901d01e005006012480006019005216", + "0x21600502300548300600621600500601200620d0054f202300521600c021", + "0x2a02800c21600502500505200600621600520c00505100620c026025125", + "0x502600505200620a00521600502a005055006006216005028005172006", + "0x602d00521600520700505500600621600520800517200620720800c216", + "0x521600502d00501d00620a00521600520a00501d0060062160050060b4", + "0x1200603114113d1254f313b02f00c21600c02d20a00c02001039c00602d", + "0x614800521600514600539e0061460052160050060bd006006216005006", + "0x514800539f00603500521600513b00514800614a00521600502f005146", + "0x50310053a10060062160050060120060064f40050061b0006039005216", + "0x603500521600514100514800614a00521600513d005146006017005216", + "0xc03a0050b000603a0052160050390053a200603900521600501700539f", + "0x4780060062160050060c400600621600500601200614b0054f503c005216", + "0x503f00521b00603f00521600503d12500c2df00603d00521600503c005", + "0x614a00521600514a0051460061e90052160051e900501a006040005216", + "0x3514a1e90100050400052160050400052cd006035005216005035005148", + "0x62160051250051520060062160050060c4006006216005006012006040", + "0x14a0051460061e90052160051e900501a00614e00521600514b0052cc006", + "0x14e00521600514e0052cd00603500521600503500514800614a005216005", + "0x600621600512500515200600621600500601200614e03514a1e9010005", + "0x50200051460061e90052160051e900501a00615000521600520d0052cc", + "0x51500052160051500052cd00600c00521600500c005148006020005216", + "0x521600500500514600600600521600500600501a00615000c0201e9010", + "0x515700612500521600512500514800600c00521600500c005031006005", + "0x501a00502d00601a01200c21600501200510500601901000c216005010", + "0x1a01912500c0050060191a200601d00521600501d00518d00601a005216", + "0x250054f620d00521600c0230050700060230210201e901e01221600501d", + "0x21600c02600512f00602600521600520d005196006006216005006012006", + "0x14600600621600520c0050510060062160050060120060280054f720c005", + "0x50211e900c4250060210052160050210051480061e90052160051e9005", + "0x500601200602d0054f820700521600c20800521d00620820a02a125216", + "0x42900613b00521600502f00542800602f005216005207005427006006216", + "0x20a00603d14b03c03a01703903514a14814603114113d02321600513b005", + "0x600621600514600516800600621600503100542a00600621600513d005", + "0x621600503500520a00600621600514a00520a00600621600514800520a", + "0x21600503a00516800600621600501700542a00600621600503900542b006", + "0x503d00516800600621600514b00502300600621600503c005023006006", + "0x3100602a00521600502a00514600601e00521600501e00501a006006216", + "0x21600501000515700620a00521600520a005148006020005216005020005", + "0x601200521600501200502d00614100521600514100518d00603f01000c", + "0x507000615204415014e04001221600501214103f20a02002a01e0190ba", + "0x2160050470051960060062160050060120061560054f904700521600c152", + "0x60062160050060120061620054fa04a00521600c15e00512f00615e005", + "0x21600504400514800614e00521600514e00514600600621600504a005051", + "0x521600c04e00521d00604e16804c12521600504414e00c425006044005", + "0x42800617000521600516d0054270060062160050060120060500054fb16d", + "0x178059174057055172052023216005051005429006051005216005170005", + "0x621600505500542a00600621600505200520a00606118117f05d17a05b", + "0x21600505900520a00600621600517400520a006006216005057005168006", + "0x517a00542a00600621600505b00542b00600621600517800520a006006", + "0x18100502300600621600517f00502300600621600505d005168006006216", + "0x501a0061850052160050062ef006006216005061005168006006216005", + "0x521600515000503100604c00521600504c005146006040005216005040", + "0x502d00617200521600517200518d006168005216005168005148006150", + "0x6518601221600518517201016815004c0400190ba006185005216005185", + "0x621600500601200606d0054fc18a00521600c06b00507000606b069067", + "0x60700054fd0ba00521600c18d00512f00618d00521600518a005196006", + "0x61960052160050062080060062160050ba005051006006216005006012", + "0x507219600c02f00607200521600507200502d0060720052160050064fe", + "0x607600521600519a19d00c13d00619d00521600500613b00619a005216", + "0x506500514600618600521600518600501a0061a00052160050760053a7", + "0x6069005216005069005148006067005216005067005031006065005216", + "0x62160050060120061a00690670651860120051a00052160051a00053a6", + "0x50650051460061a100521600518600501a006006216005070005051006", + "0x61a400521600506900514800607b0052160050670050310061a2005216", + "0x61a800521600506d0053a70060062160050060120060064ff0050061b0", + "0x506700503100606500521600506500514600618600521600518600501a", + "0x51a80052160051a80053a6006069005216005069005148006067005216", + "0x60062160050100051520060062160050060120061a8069067065186012", + "0x504c00514600604000521600504000501a00607e0052160050500053a7", + "0x616800521600516800514800615000521600515000503100604c005216", + "0x621600500601200607e16815004c04001200507e00521600507e0053a6", + "0x21600504000501a006006216005010005152006006216005162005051006", + "0x14800608d00521600515000503100608c00521600514e0051460061ab005", + "0x1520060062160050060120060065000050061b000608e005216005044005", + "0x521600504000501a00608f0052160051560053a7006006216005010005", + "0x514800615000521600515000503100614e00521600514e005146006040", + "0x8f04415014e04001200508f00521600508f0053a6006044005216005044", + "0x621600501200520a006006216005010005152006006216005006012006", + "0x2a00514600601e00521600501e00501a00609000521600502d0053a7006", + "0x20a00521600520a00514800602000521600502000503100602a005216005", + "0x21600500601200609020a02002a01e0120050900052160050900053a6006", + "0x501200520a006006216005010005152006006216005028005051006006", + "0x3100608c0052160051e90051460061ab00521600501e00501a006006216", + "0x2160051ab00550100608e00521600502100514800608d005216005020005", + "0x50400607b00521600508d0055030061a200521600508c0055020061a1005", + "0x52160050910053a40060910052160050060bd0061a400521600508e005", + "0x1a10120050930052160050930053a60060930052160050920053a5006092", + "0x520a0060062160050100051520060062160050060120060931a407b1a2", + "0x1e00521600501e00501a0060940052160050250053a7006006216005012", + "0x210051480060200052160050200050310061e90052160051e9005146006", + "0x60940210201e901e0120050940052160050940053a6006021005216005", + "0x521600501000550500601000521600501000537c0060062160050060c4", + "0x501200547d00601900521600500620800601d005216005006208006012", + "0x600600521600500600501a00600621600501a00547e00601e01a00c216", + "0x501d00504e00601e00521600501e00547f006005005216005005005146", + "0x501901d01e00500601248000601900521600501900504e00601d005216", + "0x500601200620d00550602300521600c0210054810060210201e9125216", + "0x600621600520c00505100620c026025125216005023005483006006216", + "0x502a00505500600621600502800517200602a02800c216005025005052", + "0x600621600520800517200620720800c21600502600505200620a005216", + "0x521600520a00501d0060062160050060b400602d005216005207005055", + "0x2f00c21600c02d20a00c02001039c00602d00521600502d00501d00620a", + "0x61460052160050060bd00600621600500601200603114113d12550713b", + "0x513b00514800614a00521600502f00514600614800521600514600539e", + "0x60120060065080050061b000603900521600514800539f006035005216", + "0x614a00521600513d0051460060170052160050310053a1006006216005", + "0x50390053a200603900521600501700539f006035005216005141005148", + "0x621600500601200614b00550903c00521600c03a0050b000603a005216", + "0x503d12500c2df00603d00521600503c0054780060062160050060c4006", + "0x61e90052160051e900501a00604000521600503f00521b00603f005216", + "0x50400052cd00603500521600503500514800614a00521600514a005146", + "0x2160050060c400600621600500601200604003514a1e9010005040005216", + "0x1e900501a00614e00521600514b0052cc006006216005125005152006006", + "0x3500521600503500514800614a00521600514a0051460061e9005216005", + "0x621600500601200614e03514a1e901000514e00521600514e0052cd006", + "0x51e900501a00615000521600520d0052cc006006216005125005152006", + "0x600c00521600500c0051480060200052160050200051460061e9005216", + "0x621600512500515800615000c0201e90100051500052160051500052cd", + "0x21600500620d00601200521600501000544b00601000521600500650a006", + "0x44c0060120052160050120054dc00601d00521600501d00502600601d005", + "0x50060120060210201e912550b01e01a01912521600c01201d00c005010", + "0xdb00601e00521600501e00502d00600600521600500600501a006006216", + "0x1a00514800601900521600501900514600620d02300c21600501e00600c", + "0x21600500601200602600550c02500521600c20d0050dd00601a005216005", + "0x514600602800521600502300501a00620c00521600502500550d006006", + "0x521600520c00550e00620a00521600501a00514800602a005216005019", + "0x621600502600505100600621600500601200600650f0050061b0006208", + "0x21600502d00502d00602d005216005006510006207005216005006208006", + "0x13d00613b00521600500613b00602f00521600502d20700c02f00602d005", + "0x502300501a00614100521600513d00551100613d00521600502f13b00c", + "0x601a00521600501a005148006019005216005019005146006023005216", + "0x600621600500601200614101a019023010005141005216005141005224", + "0x51e900514600602800521600500600501a006031005216005021005512", + "0x620800521600503100550e00620a00521600502000514800602a005216", + "0x1200614a00551414800521600c146005385006146005216005208005513", + "0x39005216005035005515006035005216005148005387006006216005006", + "0x2a00514600602800521600502800501a006017005216005039005516006", + "0x1700521600501700522400620a00521600520a00514800602a005216005", + "0x3a00521600514a00551100600621600500601200601720a02a028010005", + "0x20a00514800602a00521600502a00514600602800521600502800501a006", + "0x51700603a20a02a02801000503a00521600503a00522400620a005216005", + "0x600621600500601200601d01200c51801012500c21600c00c005006125", + "0x501900551a00601a00521600512500501a006019005216005010005519", + "0x501d00551c00600621600500601200600651b0050061b000601e005216", + "0x601e0052160051e900551a00601a00521600501200501a0061e9005216", + "0x502001e00c51e00602000521600502000502d00602000521600500651d", + "0x621600500601200620d00551f02300521600c021005385006021005216", + "0x26005516006026005216005025005515006025005216005023005387006", + "0x20c00521600520c00522400601a00521600501a00501a00620c005216005", + "0x1a00602800521600520d00551100600621600500601200620c01a00c005", + "0x50a00602801a00c00502800521600502800522400601a00521600501a005", + "0x521600501000544b0060120052160051250051bd006010005216005006", + "0x515800601e01a00c21600500c00538300601900521600500620d00601d", + "0x1d00521600501d0054dc00601900521600501900502600600621600501e", + "0x1200620d0230211255200201e900c21600c01201d019005006012474006", + "0x602600521600502500539e0060250052160050060bd006006216005006", + "0x502600539f00602800521600502000514800620c0052160051e9005146", + "0x520d0053a10060062160050060120060065210050061b000602a005216", + "0x602800521600502300514800620c00521600502100514600620a005216", + "0xc2080050b000620800521600502a0053a200602a00521600520a00539f", + "0x2f00521600520700547800600621600500601200602d005522207005216", + "0x514600613d00521600513b00552400613b00521600502f01a00c523006", + "0x521600513d00552500602800521600502800514800620c00521600520c", + "0x600621600501a00515800600621600500601200613d02820c12500513d", + "0x502800514800620c00521600520c00514600614100521600502d005526", + "0x600552700614102820c125005141005216005141005525006028005216", + "0x52160050050053a400600621600500601200600c00552800500521600c", + "0x100050050100052160050100053a60060100052160051250053a5006125", + "0x21600500c01200c13d00601200521600500613b006006216005006012006", + "0x50050190052160050190053a600601900521600501d0053a700601d005", + "0x2160050120050390060120052160050060350060062160050060c4006019", + "0x601200601e01a00c52901901d00c21600c012005006125017006012005", + "0x1d00521600501d00501a0061e901000c216005010005105006006216005", + "0x1000520a00600621600500601200602000552a00621600c1e900533f006", + "0x2300521600502100c00c52b006021005216005125005223006006216005", + "0x1900514600601d00521600501d00501a00620d00521600502300552c006", + "0x601200620d01901d12500520d00521600520d00552d006019005216005", + "0xb400602500521600500c0051e9006006216005020005343006006216005", + "0x500601200602800552e20c02600c21600c0250051b6006006216005006", + "0x1b200620a0052160050260051b400602a00521600520c0050b8006006216", + "0xbd00600621600500601200600652f0050061b000620800521600502a005", + "0x52160050280051b400602d0052160052070051ac006207005216005006", + "0x501d00602f00521600520a00505500620800521600502d0051b200620a", + "0x500601200613d00553013b00521600c2080051ae00602f00521600502f", + "0x1a0060310052160051410050c100614100521600513b0050bf006006216", + "0x503101d00c1aa00603100521600503100502d00601d00521600501d005", + "0x21600500601200603500553114a00521600c1480051a700614814600c216", + "0x500610900603900521600514a12500c5320060062160050060c4006006", + "0x14600521600514600501a00603a00521600501701000c33e006017005216", + "0x390053b200602f00521600502f00501d006019005216005019005146006", + "0x3a03902f0191460123b300603a00521600503a00502d006039005216005", + "0x601200604000553303f00521600c03d0053b400603d14b03c125216005", + "0x521600515014e00c52b00615014e00c21600503f0053b6006006216005", + "0x514600603c00521600503c00501a00615200521600504400552c006044", + "0x1200615214b03c12500515200521600515200552d00614b00521600514b", + "0x3c00521600503c00501a006047005216005040005534006006216005006", + "0x14b03c12500504700521600504700552d00614b00521600514b005146006", + "0x2160051250053ba00600621600501000520a006006216005006012006047", + "0x61b000615e00521600503500553500615600521600514600501a006006", + "0x1250053ba00600621600513d005051006006216005006012006006536005", + "0x501a00604a0052160050060bd00600621600501000520a006006216005", + "0x60062160050060c400615e00521600504a00553500615600521600501d", + "0x4c00552c00604c00521600516202f00c52b00616200521600515e005537", + "0x16800521600516800552d006019005216005019005146006168005216005", + "0x3ba00600621600501000520a006006216005006012006168019156125005", + "0x604e00521600500620800600621600500c005168006006216005125005", + "0x516d04e00c02f00616d00521600516d00502d00616d005216005006059", + "0x605100521600505017000c13d00617000521600500613b006050005216", + "0x501e00514600601a00521600501a00501a006052005216005051005534", + "0x50060c400605201e01a12500505200521600505200552d00601e005216", + "0x50062ef0060120052160050062ef006010005216005006035006006216", + "0x1a00521600501901d0121255380060190052160050062ef00601d005216", + "0xc00519900600500521600500500514600600600521600500600501a006", + "0x1a00521600501a00553900601000521600501000503900600c005216005", + "0x1021600512501a01000c00500601d53a00612500521600512500501d006", + "0x21600500601200620d00553c02300521600c02100553b0060210201e901e", + "0x545400600621600502500516800602602500c21600502300553d006006", + "0x521600501e00501a00602800521600520c00545500620c005216005026", + "0x54560060200052160050200051990061e90052160051e900514600601e", + "0x54570060062160050060120060280201e901e010005028005216005028", + "0x52160051e900514600601e00521600501e00501a00602a00521600520d", + "0x1e01000502a00521600502a0054560060200052160050200051990061e9", + "0x500653e0061e90052160050062080060062160050060c400602a0201e9", + "0x210052160050201e900c02f00602000521600502000502d006020005216", + "0x20d00620d00521600501d02300c02f00602300521600501202100c02f006", + "0x21600502600517200620c02600c21600520d005052006025005216005006", + "0x514600600600521600500600501a00602800521600520c005055006006", + "0x521600512500519900600c00521600500c00519b006005005216005005", + "0x501d006025005216005025005026006010005216005010005148006125", + "0x521600501a00501d00601900521600501900519f006028005216005028", + "0x1902802501012500c0050061e953f00601e00521600501e0050cf00601a", + "0x54013b00521600c02f0050b000602f02d20720820a02a01d21600501e01a", + "0x2160050060bd00600621600513b0050a900600621600500601200613d005", + "0x1a0061460052160050310053a50060310052160051410053a4006141005", + "0x21600520800519b00620a00521600520a00514600602a00521600502a005", + "0x3a600602d00521600502d005148006207005216005207005199006208005", + "0x621600500601200614602d20720820a02a01d005146005216005146005", + "0x20a00514600602a00521600502a00501a00614800521600513d0053a7006", + "0x20700521600520700519900620800521600520800519b00620a005216005", + "0x20a02a01d0051480052160051480053a600602d00521600502d005148006", + "0x5410060100052160050100053ca0060062160050060c400614802d207208", + "0x1900521600500620800601d005216005006208006012005216005010005", + "0x600501a00600621600501a00547e00601e01a00c21600501200547d006", + "0x1e00521600501e00547f006005005216005005005146006006005216005", + "0x601248000601900521600501900504e00601d00521600501d00504e006", + "0x554202300521600c0210054810060210201e912521600501901d01e005", + "0x505100620c02602512521600502300548300600621600500601200620d", + "0x621600502800517200602a02800c21600502500505200600621600520c", + "0x517200620720800c21600502600505200620a00521600502a005055006", + "0x1d0060062160050060b400602d005216005207005055006006216005208", + "0x20a00c02001039c00602d00521600502d00501d00620a00521600520a005", + "0x60bd00600621600500601200603114113d12554313b02f00c21600c02d", + "0x14a00521600502f00514600614800521600514600539e006146005216005", + "0x50061b000603900521600514800539f00603500521600513b005148006", + "0x13d0051460060170052160050310053a1006006216005006012006006544", + "0x3900521600501700539f00603500521600514100514800614a005216005", + "0x614b00554503c00521600c03a0050b000603a0052160050390053a2006", + "0x603d00521600503c0054780060062160050060c4006006216005006012", + "0x1e900501a00604000521600503f00521b00603f00521600503d12500c2df", + "0x3500521600503500514800614a00521600514a0051460061e9005216005", + "0x621600500601200604003514a1e90100050400052160050400052cd006", + "0x521600514b0052cc0060062160051250051520060062160050060c4006", + "0x514800614a00521600514a0051460061e90052160051e900501a00614e", + "0x614e03514a1e901000514e00521600514e0052cd006035005216005035", + "0x15000521600520d0052cc006006216005125005152006006216005006012", + "0xc0051480060200052160050200051460061e90052160051e900501a006", + "0xc400615000c0201e90100051500052160051500052cd00600c005216005", + "0x6012005216005012005039006012005216005006035006006216005006", + "0x621600500601200601e01a00c54601901d00c21600c012005006125017", + "0x55480060200052160051e90055470061e912500c216005125005222006", + "0x21600502100502100620d02300c21600500c005021006021005216005020", + "0x2500620c00521600520d00502500600621600502500502300602602500c", + "0x21600501d00501a006028005216005028005026006028005216005026005", + "0x2ef00600621600500601200600654900621600c02820c00c20c00601d005", + "0x21600502a01000c02f00602a00521600502a00502d00602a005216005006", + "0x502600601d00521600501d00501a00620800521600500638800620a005", + "0x520802301d125389006208005216005208005026006023005216005023", + "0x2f00521600c02d00538500620a00521600520a00504e00602d20700c216", + "0x501a00613d00521600502f00538700600621600500601200613b00554a", + "0x521600513d005026006019005216005019005146006207005216005207", + "0x123d200620a00521600520a00504e0061250052160051250050cf00613d", + "0x54b14800521600c1460053d300614603114112521600520a12513d019207", + "0x54c0060170390351252160051480053d700600621600500601200614a005", + "0x14100501a00603c00521600503a00554d00603a005216005017039035125", + "0x3c00521600503c00554e006031005216005031005146006141005216005", + "0x614b00521600514a00554f00600621600500601200603c031141125005", + "0x514b00554e00603100521600503100514600614100521600514100501a", + "0x21600520a00517200600621600500601200614b03114112500514b005216", + "0x20700501a00603d00521600513b00554f0060062160051250050ca006006", + "0x3d00521600503d00554e006019005216005019005146006207005216005", + "0xbd0060062160051250050ca00600621600500601200603d019207125005", + "0x504000554d00604000521600503f02301012554c00603f005216005006", + "0x601900521600501900514600601d00521600501d00501a00614e005216", + "0x2300600621600500601200614e01901d12500514e00521600514e00554e", + "0x60062160050100051720060062160051250050ca00600621600500c005", + "0x521600504400502d006044005216005006059006150005216005006208", + "0xc13d00604700521600500613b00615200521600504415000c02f006044", + "0x21600501a00501a00615e00521600515600554f006156005216005152047", + "0x12500515e00521600515e00554e00601e00521600501e00514600601a005", + "0x600501a00601d00521600500620d0060062160050060c400615e01e01a", + "0xc00521600500c005199006005005216005005005146006006005216005", + "0x1000502d00601d00521600501d005026006125005216005125005148006", + "0x1d12500c00500601955000601200521600501200502d006010005216005", + "0x555102100521600c0200050b00060201e901e01a019012216005012010", + "0x52160050060bd0060062160050210050a9006006216005006012006023", + "0x501a0060260052160050250053a500602500521600520d0053a400620d", + "0x521600501e00519900601a00521600501a005146006019005216005019", + "0x190120050260052160050260053a60061e90052160051e900514800601e", + "0x1a00620c0052160050230053a70060062160050060120060261e901e01a", + "0x21600501e00519900601a00521600501a005146006019005216005019005", + "0x1200520c00521600520c0053a60061e90052160051e900514800601e005", + "0x60100052160050100052210060062160050060c400620c1e901e01a019", + "0x521600500620800601d005216005006208006012005216005010005552", + "0x501a00600621600501a00547e00601e01a00c21600501200547d006019", + "0x521600501e00547f006005005216005005005146006006005216005006", + "0x1248000601900521600501900504e00601d00521600501d00504e00601e", + "0x55302300521600c0210054810060210201e912521600501901d01e005006", + "0x5100620c02602512521600502300548300600621600500601200620d005", + "0x21600502800517200602a02800c21600502500505200600621600520c005", + "0x17200620720800c21600502600505200620a00521600502a005055006006", + "0x60062160050060b400602d005216005207005055006006216005208005", + "0xc02001039c00602d00521600502d00501d00620a00521600520a00501d", + "0xbd00600621600500601200603114113d12555413b02f00c21600c02d20a", + "0x521600502f00514600614800521600514600539e006146005216005006", + "0x61b000603900521600514800539f00603500521600513b00514800614a", + "0x51460060170052160050310053a1006006216005006012006006555005", + "0x521600501700539f00603500521600514100514800614a00521600513d", + "0x14b00555603c00521600c03a0050b000603a0052160050390053a2006039", + "0x3d00521600503c0054780060062160050060c4006006216005006012006", + "0x501a00604000521600503f00521b00603f00521600503d12500c2df006", + "0x521600503500514800614a00521600514a0051460061e90052160051e9", + "0x21600500601200604003514a1e90100050400052160050400052cd006035", + "0x21600514b0052cc0060062160051250051520060062160050060c4006006", + "0x14800614a00521600514a0051460061e90052160051e900501a00614e005", + "0x14e03514a1e901000514e00521600514e0052cd006035005216005035005", + "0x521600520d0052cc006006216005125005152006006216005006012006", + "0x51480060200052160050200051460061e90052160051e900501a006150", + "0x615000c0201e90100051500052160051500052cd00600c00521600500c", + "0x602000521600500653e0061e90052160050062080060062160050060c4", + "0x2100c02f0060210052160050201e900c02f00602000521600502000502d", + "0x521600500620d00620d00521600501d02300c02f006023005216005012", + "0x505500600621600502600517200620c02600c21600520d005052006025", + "0x521600500500514600600600521600500600501a00602800521600520c", + "0x514800612500521600512500519900600c00521600500c00519b006005", + "0x521600502800501d006025005216005025005026006010005216005010", + "0x50cf00601a00521600501a00502600601900521600501900519f006028", + "0x21600501e01a01902802501012500c0050061e955700601e00521600501e", + "0x1200613d00555813b00521600c02f00504a00602f02d20720820a02a01d", + "0x3100521600514100505700614100521600513b005162006006216005006", + "0x20a00514600602a00521600502a00501a006146005216005031005174006", + "0x20700521600520700519900620800521600520800519b00620a005216005", + "0x20a02a01d00514600521600514600514a00602d00521600502d005148006", + "0x1a00614800521600513d00514100600621600500601200614602d207208", + "0x21600520800519b00620a00521600520a00514600602a00521600502a005", + "0x14a00602d00521600502d005148006207005216005207005199006208005", + "0x52160050060bd00614802d20720820a02a01d005148005216005148005", + "0x555a00600600521600500600501d00600c005216005005005559006005", + "0x500600c0061e90052160050060b200600c00600c00500c00521600500c", + "0x500514600600600521600500600501a0060062160050060c4006006216", + "0x10005216005010005148006125005216005125005199006005005216005", + "0x53f900602000521600502000502d00602001200c216005012005105006", + "0x20d02302101221600501d02001012500500601d40400601d00521600501d", + "0x600621600500601200602800555b20c00521600c02600504a006026025", + "0x502300514600602100521600502100501a00601e00521600520c005162", + "0x620d00521600520d00519900600c00521600500c00519b006023005216", + "0x501e00514700601200521600501200502d006025005216005025005148", + "0x1900521600501900502600602a00521600502a00501d00602a01e00c216", + "0x1e55c00601e00521600501e1e900c0b600601a00521600501a0050cf006", + "0x55d00613b02f02d20720820a01d21600501a01902a01202520d00c023021", + "0x513d00555f00600621600500601200614100555e13d00521600c13b005", + "0x1480052160051460053fc00614600521600503101e00c560006031005216", + "0x20800514600620a00521600520a00501a00614a0052160051480053fd006", + "0x2d00521600502d00519900620700521600520700519b006208005216005", + "0x20820a01d00514a00521600514a00521f00602f00521600502f005148006", + "0x53f700600621600501e00516800600621600500601200614a02f02d207", + "0x521600520800514600620a00521600520a00501a006035005216005141", + "0x514800602d00521600502d00519900620700521600520700519b006208", + "0x2f02d20720820a01d00503500521600503500521f00602f00521600502f", + "0x21600501900502300600621600501a0050ca006006216005006012006035", + "0x50280053f70060062160051e900519300600621600501200520a006006", + "0x602300521600502300514600602100521600502100501a006039005216", + "0x502500514800620d00521600520d00519900600c00521600500c00519b", + "0x603902520d00c02302101d00503900521600503900521f006025005216", + "0x21600501000556200601000c00c21600500c0055610060062160050060c4", + "0x2100601900521600501d00556400601d005216005012005563006012005", + "0x21600501e00502500600621600501a00502300601e01a00c216005019005", + "0x2f00602000521600502000502d0060200052160051e90051bd0061e9005", + "0x500600501a00602300521600500c00556200602100521600502012500c", + "0x6023005216005023005400006005005216005005005146006006005216", + "0x2602520d12521600502102300500601056500602100521600502100504e", + "0x517000600621600500601200602800556620c00521600c026005050006", + "0x520800511300620800521600520a02a00c11100620a02a00c21600520c", + "0x602500521600502500514600620d00521600520d00501a006207005216", + "0x14500600621600500601200620702520d125005207005216005207005142", + "0x21600502500514600620d00521600520d00501a00602d005216005028005", + "0x2160050060c400602d02520d12500502d00521600502d005142006025005", + "0x20800600621600500601200601900556801d00521600c012005567006006", + "0x21600501e00520a0061e901e00c2160050100052f100601a005216005006", + "0xc21a00601a00521600501a00504e0061e90052160051e900502d006006", + "0x21600501d00556900600621600502100505100602102000c21600501a1e9", + "0x600621600502500520a00602602500c2160050230052f100620d02300c", + "0x2002600c21a00602000521600502000504e00602600521600502600502d", + "0x2a00c21600520c00505200600621600502800505100602820c00c216005", + "0x600501a00620800521600520a00505500600621600502a00517200620a", + "0xc00521600500c005199006005005216005005005146006006005216005", + "0x2d20701021600520800c0050060103c400620800521600520800501d006", + "0x600621600500601200614100556a13d00521600c13b00510700613b02f", + "0x21600520700501a00614600521600500620d00603100521600513d00514f", + "0x14800602f00521600502f00519900602d00521600502d005146006207005", + "0x21600503100502d006146005216005146005026006125005216005125005", + "0x20d03114612502f02d20701956b00620d00521600520d00502d006031005", + "0x603c00556d03a00521600c01700556c00601703903514a148012216005", + "0xc21600514b00505200614b00521600503a00556e006006216005006012", + "0x501a00604000521600503f00505500600621600503d00517200603f03d", + "0x521600503500519900615000521600514a00514600614e005216005148", + "0x61b000604700521600504000501d006152005216005039005148006044", + "0x501a00615600521600503c00514100600621600500601200600656f005", + "0x521600503500519900614a00521600514a005146006148005216005148", + "0x14801200515600521600515600514a006039005216005039005148006035", + "0x514100600621600520d00520a00600621600500601200615603903514a", + "0x521600502d00514600620700521600520700501a00615e005216005141", + "0x514a00612500521600512500514800602f00521600502f00519900602d", + "0x5100600621600500601200615e12502f02d20701200515e00521600515e", + "0x61620052160050060bd00604a00521600500620d006006216005019005", + "0x500500514600600600521600500600501a00604c0052160051620053f8", + "0x612500521600512500514800600c00521600500c005199006005005216", + "0x504c0053f900601000521600501000502d00604a00521600504a005026", + "0x5016d04e16801221600504c01004a12500c00500601957000604c005216", + "0x16200600621600500601200605200557105100521600c17000504a006170", + "0x21600504e00514600614e00521600516800501a006172005216005051005", + "0x1d00615200521600505000514800604400521600516d005199006150005", + "0x216005055005174006055005216005047005057006047005216005172005", + "0x19900615000521600515000514600614e00521600514e00501a006057005", + "0x21600505700514a006152005216005152005148006044005216005044005", + "0x505200514100600621600500601200605715204415014e012005057005", + "0x604e00521600504e00514600616800521600516800501a006174005216", + "0x517400514a00605000521600505000514800616d00521600516d005199", + "0x65720060062160051250050fe00617405016d04e168012005174005216", + "0x600600521600500600501a00601200521600500620d006010005216005", + "0x501200502600600c00521600500c005148006005005216005005005146", + "0x501001200c005006012574006010005216005010005573006012005216", + "0x60120060200055761e900521600c01e00557500601e01a01901d010216", + "0x60210052160050210055780060210052160051e9005577006006216005", + "0x1200602500557a20d00521600c02300540c006023005216005021005579", + "0x20c00521600502600557b00602600521600520d005412006006216005006", + "0x1900514600601d00521600501d00501a00602800521600520c00557c006", + "0x2800521600502800557d00601a00521600501a005148006019005216005", + "0x2a00521600502500557e00600621600500601200602801a01901d010005", + "0x1a00514800601900521600501900514600601d00521600501d00501a006", + "0x1200602a01a01901d01000502a00521600502a00557d00601a005216005", + "0x1d00521600501d00501a00620a00521600502000557e006006216005006", + "0x20a00557d00601a00521600501a005148006019005216005019005146006", + "0x620d00601000521600500657200620a01a01901d01000520a005216005", + "0x5005216005005005148006006005216005006005146006012005216005", + "0x12500540f006010005216005010005573006012005216005012005026006", + "0x3a200601a01901d12521600512501001200500601257f006125005216005", + "0x60120060200055801e900521600c01e0050b000601e00521600501a005", + "0x60062160050230050fe00602302100c21600500c00540a006006216005", + "0x2500558100602500521600520d02100c22c00620d0052160051e9005478", + "0x1900521600501900514800601d00521600501d005146006026005216005", + "0x600621600500601200602601901d125005026005216005026005582006", + "0x21600501d00514600620c00521600502000558300600621600500c0050fe", + "0x12500520c00521600520c00558200601900521600501900514800601d005", + "0x55840060100052160050100054160060062160050060c400620c01901d", + "0x601900521600500620800601d005216005006208006012005216005010", + "0x500600501a00600621600501a00547e00601e01a00c21600501200547d", + "0x601e00521600501e00547f006005005216005005005146006006005216", + "0x500601248000601900521600501900504e00601d00521600501d00504e", + "0x20d00558502300521600c0210054810060210201e912521600501901d01e", + "0x20c00505100620c026025125216005023005483006006216005006012006", + "0x600621600502800517200602a02800c216005025005052006006216005", + "0x20800517200620720800c21600502600505200620a00521600502a005055", + "0x501d0060062160050060b400602d005216005207005055006006216005", + "0x2d20a00c02001039c00602d00521600502d00501d00620a00521600520a", + "0x50060bd00600621600500601200603114113d12558613b02f00c21600c", + "0x614a00521600502f00514600614800521600514600539e006146005216", + "0x5870050061b000603900521600514800539f00603500521600513b005148", + "0x513d0051460060170052160050310053a1006006216005006012006006", + "0x603900521600501700539f00603500521600514100514800614a005216", + "0x1200614b00558803c00521600c03a0050b000603a0052160050390053a2", + "0x2df00603d00521600503c0054780060062160050060c4006006216005006", + "0x51e900501a00604000521600503f00521b00603f00521600503d12500c", + "0x603500521600503500514800614a00521600514a0051460061e9005216", + "0x600621600500601200604003514a1e90100050400052160050400052cd", + "0x14e00521600514b0052cc0060062160051250051520060062160050060c4", + "0x3500514800614a00521600514a0051460061e90052160051e900501a006", + "0x1200614e03514a1e901000514e00521600514e0052cd006035005216005", + "0x615000521600520d0052cc006006216005125005152006006216005006", + "0x500c0051480060200052160050200051460061e90052160051e900501a", + "0x514600615000c0201e90100051500052160051500052cd00600c005216", + "0x21600500500600c46b006005005216005005005148006006005216005006", + "0x21600500601200601d00558901200521600c01000546c00601012500c125", + "0x547000601a00521600501900546f00601900521600501200546e006006", + "0x2000506d00600621600501e0054710060230210201e901e01221600501a", + "0x558a00600621600502300520a00600621600502100506d006006216005", + "0x521600500c00514600602500521600520d00558b00620d0052160051e9", + "0xc12500502500521600502500558c00612500521600512500514800600c", + "0xc00514600602600521600501d00558d006006216005006012006025125", + "0x2600521600502600558c00612500521600512500514800600c005216005", + "0x600521600500600539f00600500521600500658e00602612500c125005", + "0x50b000600c00521600500500600c58f00600500521600500500502d006", + "0x21600512500547800600621600500601200601000559012500521600c00c", + "0x3a600601900521600501d0053a500601d0052160050120053a4006012005", + "0x50100053a7006006216005006012006019005005019005216005019005", + "0x62160050060c400601a00500501a00521600501a0053a600601a005216", + "0x5006208006012005216005010005591006010005216005010005435006", + "0x601e01a00c21600501200547d00601900521600500620800601d005216", + "0x21600500500514600600600521600500600501a00600621600501a00547e", + "0x4e00601d00521600501d00504e00601e00521600501e00547f006005005", + "0x210201e912521600501901d01e005006012480006019005216005019005", + "0x548300600621600500601200620d00559202300521600c021005481006", + "0x21600502500505200600621600520c00505100620c026025125216005023", + "0x5200620a00521600502a00505500600621600502800517200602a02800c", + "0x21600520700505500600621600520800517200620720800c216005026005", + "0x2d00501d00620a00521600520a00501d0060062160050060b400602d005", + "0x14113d12559313b02f00c21600c02d20a00c02001039c00602d005216005", + "0x21600514600539e0061460052160050060bd006006216005006012006031", + "0x39f00603500521600513b00514800614a00521600502f005146006148005", + "0x3a10060062160050060120060065940050061b0006039005216005148005", + "0x21600514100514800614a00521600513d005146006017005216005031005", + "0xb000603a0052160050390053a200603900521600501700539f006035005", + "0x2160050060c400600621600500601200614b00559503c00521600c03a005", + "0x21b00603f00521600503d12500c2df00603d00521600503c005478006006", + "0x21600514a0051460061e90052160051e900501a00604000521600503f005", + "0x100050400052160050400052cd00603500521600503500514800614a005", + "0x1250051520060062160050060c400600621600500601200604003514a1e9", + "0x61e90052160051e900501a00614e00521600514b0052cc006006216005", + "0x514e0052cd00603500521600503500514800614a00521600514a005146", + "0x512500515200600621600500601200614e03514a1e901000514e005216", + "0x1460061e90052160051e900501a00615000521600520d0052cc006006216", + "0x2160051500052cd00600c00521600500c005148006020005216005020005", + "0x512500533d0060100052160050064db00615000c0201e9010005150005", + "0x35600601900521600500620d00601d00521600501000544b006012005216", + "0x21600501900502600600621600501e00515500601e01a00c21600500c005", + "0x21600c01201d01900500601247400601d00521600501d0054dc006019005", + "0x52160050060bd00600621600500601200620d0230211255960201e900c", + "0x514800620c0052160051e900514600602600521600502500539e006025", + "0x60065970050061b000602a00521600502600539f006028005216005020", + "0x521600502100514600620a00521600520d0053a1006006216005006012", + "0x53a200602a00521600520a00539f00602800521600502300514800620c", + "0x500601200602d00559820700521600c2080050b000620800521600502a", + "0x613b00521600502f01a00c59900602f005216005207005478006006216", + "0x502800514800620c00521600520c00514600613d00521600513b00559a", + "0x500601200613d02820c12500513d00521600513d00559b006028005216", + "0x514600614100521600502d00559c00600621600501a005155006006216", + "0x521600514100559b00602800521600502800514800620c00521600520c", + "0x21600500500514800600600521600500600514600614102820c125005141", + "0x521600c01000546c00601012500c12521600500500600c46b006005005", + "0x46f00601900521600501200546e00600621600500601200601d00559d012", + "0x4710060230210201e901e01221600501a00547000601a005216005019005", + "0x600621600502000506d0060062160051e900547200600621600501e005", + "0x21600520d00541c00620d00521600502100541b00600621600502300520a", + "0x41d00612500521600512500514800600c00521600500c005146006025005", + "0x541e00600621600500601200602512500c125005025005216005025005", + "0x521600512500514800600c00521600500c00514600602600521600501d", + "0x62160050060c400602612500c12500502600521600502600541d006125", + "0x500620800601200521600501000559e006010005216005010005443006", + "0x601e01a00c21600501200547d00601900521600500620800601d005216", + "0x21600500500514600600600521600500600501a00600621600501a00547e", + "0x4e00601d00521600501d00504e00601e00521600501e00547f006005005", + "0x210201e912521600501901d01e005006012480006019005216005019005", + "0x548300600621600500601200620d00559f02300521600c021005481006", + "0x21600502500505200600621600520c00505100620c026025125216005023", + "0x5200620a00521600502a00505500600621600502800517200602a02800c", + "0x21600520700505500600621600520800517200620720800c216005026005", + "0x2d00501d00620a00521600520a00501d0060062160050060b400602d005", + "0x14113d1255a013b02f00c21600c02d20a00c02001039c00602d005216005", + "0x21600514600539e0061460052160050060bd006006216005006012006031", + "0x39f00603500521600513b00514800614a00521600502f005146006148005", + "0x3a10060062160050060120060065a10050061b0006039005216005148005", + "0x21600514100514800614a00521600513d005146006017005216005031005", + "0xb000603a0052160050390053a200603900521600501700539f006035005", + "0x2160050060c400600621600500601200614b0055a203c00521600c03a005", + "0x21b00603f00521600503d12500c2df00603d00521600503c005478006006", + "0x21600514a0051460061e90052160051e900501a00604000521600503f005", + "0x100050400052160050400052cd00603500521600503500514800614a005", + "0x1250051520060062160050060c400600621600500601200604003514a1e9", + "0x61e90052160051e900501a00614e00521600514b0052cc006006216005", + "0x514e0052cd00603500521600503500514800614a00521600514a005146", + "0x512500515200600621600500601200614e03514a1e901000514e005216", + "0x1460061e90052160051e900501a00615000521600520d0052cc006006216", + "0x2160051500052cd00600c00521600500c005148006020005216005020005", + "0x2160050065a300600621600500c0051f200615000c0201e9010005150005", + "0x2d00601d0052160050120055a50060120052160050100055a4006010005", + "0x55a400601a01900c21600512501d0051255a600601d00521600501d005", + "0x52160051e900502d0061e900521600501e0055a500601e00521600501a", + "0x602000521600502000501a00602102000c2160051e900600c5a70061e9", + "0x21019020125005021005216005021005573006019005216005019005031", + "0x545400600621600500601200600c0055a900500521600c0060055a8006", + "0x5216005010005456006010005216005125005455006125005216005005", + "0xc13d00601200521600500613b006006216005006012006010005005010", + "0x21600501900545600601900521600501d00545700601d00521600500c012", + "0x1d0121255ab01012500c12521600c00500600c5aa006019005005019005", + "0x500c00514600601a0052160050100055ac006006216005006012006019", + "0x602000521600501a0055ad0061e900521600512500514800601e005216", + "0x60210052160050190055af0060062160050060120060065ae0050061b0", + "0x50210055ad0061e900521600501d00514800601e005216005012005146", + "0x5b120d00521600c02300546c0060230052160050200055b0006020005216", + "0x260055b200602600521600520d00546e006006216005006012006025005", + "0x1e00521600501e00514600602800521600520c0055b300620c005216005", + "0x1e901e1250050280052160050280055b40061e90052160051e9005148006", + "0x501e00514600602a0052160050250055b5006006216005006012006028", + "0x502a00521600502a0055b40061e90052160051e900514800601e005216", + "0x500521600500500547f0060050052160050060055b600602a1e901e125", + "0x1d0055b801200521602000c0055b70060062160050060c4006005005005", + "0x55bf0210055be0200055bd1e90055bc01e0055bb01a0055ba0190055b9", + "0x260052160050065c20060062160050060120060250055c120d0055c0023", + "0x544300620c00521600502612500c02f00602600521600502600502d006", + "0x521600501000504e00620c00521600520c00504e006012005216005012", + "0x621600520a00505100620a02a02812521600501020c01212522e006010", + "0x2800504e00620700521600500500514600620800521600500600501a006", + "0x120060065c30050061b000602f00521600502a00504e00602d005216005", + "0x613b00521600513b00502d00613b0052160050065c4006006216005006", + "0x13d00504e00601d00521600501d00536e00613d00521600513b12500c02f", + "0x21600501013d01d1255c500601000521600501000504e00613d005216005", + "0x620800521600500600501a006006216005146005051006146031141125", + "0x503100504e00602d00521600514100504e006207005216005005005146", + "0x2160050065c60060062160050060120060065c30050061b000602f005216", + "0x614a00521600514812500c02f00614800521600514800502d006148005", + "0x501000504e00614a00521600514a00504e00601900521600501900537c", + "0x501700505100601703903512521600501014a0191255c7006010005216", + "0x4e00620700521600500500514600620800521600500600501a006006216", + "0x65c30050061b000602f00521600503900504e00602d005216005035005", + "0x521600503a00502d00603a0052160050065c8006006216005006012006", + "0x4e00601a00521600501a00543500603c00521600503a12500c02f00603a", + "0x1003c01a1255c900601000521600501000504e00603c00521600503c005", + "0x521600500600501a00600621600503f00505100603f03d14b125216005", + "0x504e00602d00521600514b00504e006207005216005005005146006208", + "0x65ca0060062160050060120060065c30050061b000602f00521600503d", + "0x521600504012500c02f00604000521600504000502d006040005216005", + "0x52d700600500521600500500514600600600521600500600501a00614e", + "0x521600501000504e00614e00521600514e00504e00601e00521600501e", + "0xc15200548100615204415012521600501014e01e00500601222d006010", + "0x15e1252160050470054830060062160050060120061560055cb047005216", + "0x514600620800521600515000501a00600621600516200505100616204a", + "0x521600504a00504e00602d00521600515e00504e006207005216005044", + "0x52160051560055cc0060062160050060120060065c30050061b000602f", + "0x55cd00604400521600504400514600615000521600515000501a00604c", + "0x50065ce00600621600500601200604c04415012500504c00521600504c", + "0x4e00521600516812500c02f00616800521600516800502d006168005216", + "0x1000504e00604e00521600504e00504e0061e90052160051e900534b006", + "0x17000505100617005016d12521600501004e1e91255cf006010005216005", + "0x620700521600500500514600620800521600500600501a006006216005", + "0x5c30050061b000602f00521600505000504e00602d00521600516d00504e", + "0x21600505100502d0060510052160050065d0006006216005006012006006", + "0x600600521600500600501a00605200521600505112500c02f006051005", + "0x505200504e0060200052160050200053ca006005005216005005005146", + "0x50100520200050060125d100601000521600501000504e006052005216", + "0x50060120060590055d217400521600c057005481006057055172125216", + "0x600621600517a00505100617a05b178125216005174005483006006216", + "0x517800504e00620700521600505500514600620800521600517200501a", + "0x60120060065c30050061b000602f00521600505b00504e00602d005216", + "0x617200521600517200501a00605d0052160050590055cc006006216005", + "0x5d05517212500505d00521600505d0055cd006055005216005055005146", + "0x521600517f00502d00617f0052160050065d3006006216005006012006", + "0x14600600600521600500600501a00618100521600517f12500c02f00617f", + "0x21600518100504e006021005216005021005221006005005216005005005", + "0x2160050101810210050060125d400601000521600501000504e006181005", + "0x2160050060120060670055d506500521600c186005481006186185061125", + "0x1a00600621600518a00505100618a06b069125216005065005483006006", + "0x21600506900504e006207005216005185005146006208005216005061005", + "0x50060120060065c30050061b000602f00521600506b00504e00602d005", + "0x14600606100521600506100501a00606d0052160050670055cc006006216", + "0x606d18506112500506d00521600506d0055cd006185005216005185005", + "0x18d00521600518d00502d00618d0052160050065d6006006216005006012", + "0x504e0060230052160050230052190060ba00521600518d12500c02f006", + "0x50100ba0231255d700601000521600501000504e0060ba0052160050ba", + "0x20800521600500600501a006006216005072005051006072196070125216", + "0x19600504e00602d00521600507000504e006207005216005005005146006", + "0x50065d80060062160050060120060065c30050061b000602f005216005", + "0x19d00521600519a12500c02f00619a00521600519a00502d00619a005216", + "0x1000504e00619d00521600519d00504e00620d00521600520d005300006", + "0x1a10050510061a11a007612521600501019d20d1255d9006010005216005", + "0x620700521600500500514600620800521600500600501a006006216005", + "0x5c30050061b000602f0052160051a000504e00602d00521600507600504e", + "0x2160051a200502d0061a20052160050065da006006216005006012006006", + "0x602500521600502500541600607b0052160051a212500c02f0061a2005", + "0x7b0251255db00601000521600501000504e00607b00521600507b00504e", + "0x21600500600501a00600621600507e00505100607e1a81a4125216005010", + "0x4e00602d0052160051a400504e006207005216005005005146006208005", + "0x51ab02f02d1255dc0061ab0052160050060bd00602f0052160051a8005", + "0x620800521600520800501a00608d00521600508c00522b00608c005216", + "0x8d20720812500508d00521600508d0055cd006207005216005207005146", + "0x50065de00601201000c2160051250055dd00600621600500c005103006", + "0x601a0052160050190055a500601900521600501d0055a400601d005216", + "0x36a0061e901e00c21600501001a0051255a600601a00521600501a00502d", + "0x2160050210055a50060210052160051e90055a4006020005216005012005", + "0x20d00c21600502002301e1255a600602300521600502300502d006023005", + "0x502d00620c0052160050260055a50060260052160050250055a4006025", + "0x502800501a00602a02800c21600520c00600c5a700620c00521600520c", + "0x502a00521600502a00557300620d00521600520d005031006028005216", + "0x21600500601200600c0055e000500521600c0060055df00602a20d028125", + "0x52eb0060100052160051250052ea00612500521600500500517c006006", + "0x521600500613b006006216005006012006010005005010005216005010", + "0x2eb00601900521600501d0052ec00601d00521600500c01200c13d006012", + "0x547f0060050052160050060055e1006019005005019005216005019005", + "0x51250055dd00600621600500c005153006005005005005005216005005", + "0x601900521600501d0055a400601d0052160050065e200601201000c216", + "0x1a0051255a600601a00521600501a00502d00601a0052160050190055a5", + "0x2160051e90055a400602000521600501200536a0061e901e00c216005010", + "0x5a600602300521600502300502d0060230052160050210055a5006021005", + "0x55a50060260052160050250055a400602520d00c21600502002301e125", + "0x21600520c00600c5a700620c00521600520c00502d00620c005216005026", + "0x620d00521600520d00503100602800521600502800501a00602a02800c", + "0x50052160050060055e300602a20d02812500502a00521600502a005573", + "0xc00601a00521600500600500600500500500500521600500500547f006", + "0x509100601e0052160050062080060062160050060c4006006216005006", + "0x521600502000508c0060062160051e90051ab0060201e900c216005010", + "0x602302100c21600501e02000c09200601e00521600501e00504e006020", + "0x520d00520a00602520d00c2160050120052f1006006216005023005051", + "0x21a00602100521600502100504e00602500521600502500502d006006216", + "0x501d00504c00600621600520c00505100620c02600c21600502102500c", + "0x600600521600500600501a00600621600502800516800602a02800c216", + "0x502600504e00602a00521600502a00501d006005005216005005005146", + "0x20700505000620720820a12521600502602a00500601016d006026005216", + "0xc21600502d00517000600621600500601200602f0055e402d00521600c", + "0x65e500614100521600512500541300600621600513d00505100613d13b", + "0x621600514600517200614814600c21600513b005052006031005216005", + "0x21600503100502d0060062160050060b400614a005216005148005055006", + "0x21600c14a03114100c2080124e900614a00521600514a00501d006031005", + "0x501700522500600621600500601200614b03c03a1255e6017039035125", + "0x601900521600503900514800603f00521600503500514600603d005216", + "0x60062160050060120060065e70050061b000604000521600503d0054eb", + "0x503c00514800603f00521600503a00514600614e00521600514b0054ed", + "0x61500052160050400054ee00604000521600514e0054eb006019005216", + "0x61520055e804400521600c15000504a00601900521600501901a00c15e", + "0x60470052160050440051620060062160050060c4006006216005006012", + "0x504700501d00603f00521600503f00514600620a00521600520a00501a", + "0xc04a00517f00604a15e15612521600504703f20a12505d006047005216", + "0x16800c21600516200518100600621600500601200604c0055e9162005216", + "0x60500055ea16d00521600c04e00506100600621600516800516800604e", + "0x521600517000517400617000521600516d005057006006216005006012", + "0x514800615e00521600515e00514600615600521600515600501a006051", + "0x605101915e15601000505100521600505100514a006019005216005019", + "0x6052005216005006208006006216005050005051006006216005006012", + "0x517205200c02f00617200521600517200502d0061720052160050065eb", + "0x617400521600505505700c13d00605700521600500613b006055005216", + "0x515e00514600615600521600515600501a006059005216005174005141", + "0x505900521600505900514a00601900521600501900514800615e005216", + "0x617800521600504c00514100600621600500601200605901915e156010", + "0x501900514800615e00521600515e00514600615600521600515600501a", + "0x601200617801915e15601000517800521600517800514a006019005216", + "0x501a00605b0052160051520051410060062160050060c4006006216005", + "0x521600501900514800603f00521600503f00514600620a00521600520a", + "0x21600500601200605b01903f20a01000505b00521600505b00514a006019", + "0x502f0051410060062160051250055ec00600621600501a00502a006006", + "0x620800521600520800514600620a00521600520a00501a00617a005216", + "0xc20820a01000517a00521600517a00514a00600c00521600500c005148", + "0x50100055a40060100052160050065ed00600621600500c00510100617a", + "0x601d00521600501d00502d00601d0052160050120055a5006012005216", + "0x5a500601e00521600501a0055a400601a01900c21600512501d0051255a6", + "0x51e900600c5a70061e90052160051e900502d0061e900521600501e005", + "0x1900521600501900503100602000521600502000501a00602102000c216", + "0x21600c00500600c328006021019020125005021005216005021005573006", + "0x1200521600512500532a0060062160050060120060100055ee12500c00c", + "0x50061b000601900521600501200532b00601d00521600500c00501a006", + "0x501a00532c00601a0052160050060bd0060062160050060120060065ef", + "0x501900521600501e00532b00601d00521600501000501a00601e005216", + "0x621600500601200600c0055f100500521600c0060055f000601901d00c", + "0x10005353006010005216005125005352006125005216005005005351006", + "0x1200521600500613b006006216005006012006010005005010005216005", + "0x535300601900521600501d00535400601d00521600500c01200c13d006", + "0x500547f0060050052160050060055f2006019005005019005216005019", + "0x1200600c0055f400500521600c0060055f3006005005005005005216005", + "0x10005216005125005174006125005216005005005057006006216005006", + "0x613b00600621600500601200601000500501000521600501000514a006", + "0x521600501d00514100601d00521600500c01200c13d006012005216005", + "0x50052160050060055f500601900500501900521600501900514a006019", + "0x60050052160050060055f600600500500500500521600500500547f006", + "0x55f800500521600c0060055f700600500500500500521600500500547f", + "0x512500551600612500521600500500551500600621600500601200600c", + "0x6216005006012006010005005010005216005010005224006010005216", + "0x1d00551100601d00521600500c01200c13d00601200521600500613b006", + "0xc00600522a006019005005019005216005019005224006019005216005", + "0x600621600500500520a0060062160050060120061250055f900c005216", + "0x501200522400601200521600501000551600601000521600500c005515", + "0x6006216005125005023006006216005006012006012005005012005216", + "0x21600500613b00601900521600500501d00c02f00601d005216005006208", + "0x61e900521600501e00551100601e00521600501901a00c13d00601a005", + "0x50060350060062160050060c40061e90050051e90052160051e9005224", + "0xc21600c01d00500612501700601d00521600501d00503900601d005216", + "0x201252160050100055fb0060062160050060120061e901e00c5fa01a019", + "0x1900501a0060062160050060b400620d0052160050120051e9006023021", + "0x500601200620c0055fc02602500c21600c20d0051b6006019005216005", + "0x1b200602a0052160050250051b40060280052160050260050b8006006216", + "0xbd0060062160050060120060065fd0050061b000620a005216005028005", + "0x521600520c0051b40062070052160052080051ac006208005216005006", + "0x501d00602d00521600502a00505500620a0052160052070051b200602a", + "0x500601200613b0055fe02f00521600c20a0051ae00602d00521600502d", + "0x1e900614100521600513d0050c100613d00521600502f0050bf006006216", + "0x21600c0310051b600614100521600514100502d00603100521600502d005", + "0x350052160051480050b800600621600500601200614a0055ff14814600c", + "0x50061b00060170052160050350051b20060390052160051460051b4006", + "0x503a0051ac00603a0052160050060bd006006216005006012006006600", + "0x601700521600503c0051b200603900521600514a0051b400603c005216", + "0xc0170051ae00614b00521600514b00501d00614b005216005039005055", + "0x4000521600503d0050bf00600621600500601200603f00560103d005216", + "0x502d0061500052160050400050c100614e00521600514102000c14d006", + "0x21600514e00502d00604400521600515002100c14d006150005216005150", + "0x1021600502304414e00c01060200604400521600504400502d00614e005", + "0x521600515200519900604a12500c21600512500560300615e156047152", + "0x502d00615600521600515600502d00604700521600504700502d006152", + "0x4e16800c60404c16200c21600c04a01a01912501700615e00521600515e", + "0x21600515e1560471255380060062160050060c4006006216005006012006", + "0x19900604c00521600504c00514600616200521600516200501a00616d005", + "0x21600516d005539006125005216005125005039006152005216005152005", + "0x514b16d12515204c16201d53a00614b00521600514b00501d00616d005", + "0x601200605500560517200521600c05200553b006052051170050010216", + "0x521600517405700c60600617405700c21600517200553d006006216005", + "0x514600605000521600505000501a006178005216005059005607006059", + "0x5216005178005608006051005216005051005199006170005216005170", + "0x5216005055005609006006216005006012006178051170050010005178", + "0x519900617000521600517000514600605000521600505000501a00605b", + "0x605b05117005001000505b00521600505b005608006051005216005051", + "0x20a00600621600514b0051680060062160050060c4006006216005006012", + "0x600621600515600520a00600621600512500560a006006216005047005", + "0x5d00521600500605900617a00521600500620800600621600515e00520a", + "0x613b00617f00521600505d17a00c02f00605d00521600505d00502d006", + "0x521600506100560900606100521600517f18100c13d006181005216005", + "0x519900604e00521600504e00514600616800521600516800501a006185", + "0x618515204e168010005185005216005185005608006152005216005152", + "0x600621600512500560a00600621600503f005051006006216005006012", + "0x50650052f90060650052160050060bd00618600521600514102000c14d", + "0x21600506900560c00606900521600506702302118601060b006067005216", + "0x521600506b00502d00618d00521600518d00519d00618d06d18a06b010", + "0x512f00606d00521600506d00502d00618a00521600518a00502d00606b", + "0x2160050ba00505100600621600500601200607000560d0ba00521600c18d", + "0x502d00607200521600519606b00c14d006196005216005006109006006", + "0x61a007619d19a01021600506d18a07200c010602006072005216005072", + "0x521600519a0051990060062160051a000520a00600621600507600520a", + "0x21600500601200600660e0050061b00061a200521600519d00502d0061a1", + "0x7b18a00c14d00607b005216005006109006006216005070005051006006", + "0x506d1a406b00c0106020061a40052160051a400502d0061a4005216005", + "0x621600508c00520a0060062160051ab00520a00608c1ab07e1a8010216", + "0x50060c40061a200521600507e00502d0061a10052160051a8005199006", + "0x608e00521600508d00560700608d0052160051a214b00c606006006216", + "0x51a100519900601a00521600501a00514600601900521600501900501a", + "0x601200608e1a101a01901000508e00521600508e0056080061a1005216", + "0x60bd00600621600512500560a00600621600513b005051006006216005", + "0x509002302102001060b00609000521600508f00530900608f005216005", + "0x21600509500519d00609509409309201021600509100560c006091005216", + "0x2d00609300521600509300502d00609200521600509200502d006095005", + "0x60120061b500560f09600521600c09500512f006094005216005094005", + "0xc14d00609b005216005006109006006216005096005051006006216005", + "0x9309c00c01060200609c00521600509c00502d00609c00521600509b092", + "0x50a100520a00600621600509700520a0060a109709f05f010216005094", + "0x1b00061be00521600509f00502d0061bd00521600505f005199006006216", + "0x61090060062160051b5005051006006216005006012006006610005006", + "0x52160050a600502d0060a60052160051c109300c14d0061c1005216005", + "0x50aa00520a0060ac0aa1bc0a80102160050940a609200c0106020060a6", + "0x502d0061bd0052160050a80051990060062160050ac00520a006006216", + "0xb00052160051be02d00c6060060062160050060c40061be0052160051bc", + "0x1a00514600601900521600501900501a0060a90052160050b0005607006", + "0xa90052160050a90056080061bd0052160051bd00519900601a005216005", + "0x60062160050120051680060062160050060120060a91bd01a019010005", + "0x9800521600500620800600621600501000561100600621600512500560a", + "0xb209800c02f0060b20052160050b200502d0060b2005216005006059006", + "0xb60052160051b90b400c13d0060b400521600500613b0061b9005216005", + "0x1e900514600601e00521600501e00501a0061b60052160050b6005609006", + "0x1b60052160051b600560800600c00521600500c0051990061e9005216005", + "0x230052160050060d90060200052160050060d70061b600c1e901e010005", + "0x60062160050060c400600621600500600c006025005216005006612006", + "0x512500519900600500521600500500514600600600521600500600501a", + "0x21600501d1250050060103c400601d00521600501d00501d006125005216", + "0x21600c0280051070061e90052160051e902000c0e30060281e920c026010", + "0x620800521600502a00514f00600621600500601200620a00561302a005", + "0x20700501a00602d00521600500620800620d20700c21600520802600c5a7", + "0xc00521600500c00519b00620c00521600520c005146006207005216005", + "0x1e0050cf00601a00521600501a00501d00602d00521600502d00504e006", + "0x2d00c20c20701d61500620d00521600520d02500c61400601e005216005", + "0x61600602100521600502102300c18200613d02113b02f01021600501e01a", + "0x514100522900600621600500601200603100561714100521600c13d005", + "0x621600514a0050ca00600621600514800516800603514a148146010216", + "0x513b00514600602f00521600502f00501a006006216005035005051006", + "0x614600521600514600504e00601000521600501000514800613b005216", + "0x501900519f00620d00521600520d005573006012005216005012005026", + "0x3c03a01703901021600501920d01214601013b02f019618006019005216", + "0x561b00600621600500601200603d00561a14b00521600c03c005619006", + "0x604000521600503f0053a400603f0052160050060bd00600621600514b", + "0x501700514600603900521600503900501a00614e0052160050400053a5", + "0x61e90052160051e900519900602100521600502100519b006017005216", + "0x2101703901d00514e00521600514e0053a600603a00521600503a005148", + "0x501a00615000521600503d0053a700600621600500601200614e03a1e9", + "0x521600502100519b006017005216005017005146006039005216005039", + "0x53a600603a00521600503a0051480061e90052160051e9005199006021", + "0x600621600500601200615003a1e902101703901d005150005216005150", + "0x621600501200502300600621600520d00561c0060062160050190050cc", + "0x13b00514600602f00521600502f00501a0060440052160050310053a7006", + "0x1e90052160051e900519900602100521600502100519b00613b005216005", + "0x13b02f01d0050440052160050440053a6006010005216005010005148006", + "0x50230060062160050190050cc0060062160050060120060440101e9021", + "0x16800600621600501e0050ca006006216005023005184006006216005012", + "0x15200521600520a0053a700600621600502500561d00600621600501a005", + "0xc00519b00620c00521600520c00514600602600521600502600501a006", + "0x100052160050100051480061e90052160051e900519900600c005216005", + "0x600561e0061520101e900c20c02601d0051520052160051520053a6006", + "0x21600500605b00600500500500500521600500500547f006005005216005", + "0x50060b20060210052160050060b20061e900521600500605b00601a005", + "0x500600501a0060062160050060c400600621600500600c00620d005216", + "0x600c00521600500c005199006005005216005005005146006006005216", + "0x2500502600602501000c2160050100052cf006125005216005125005148", + "0x521600502600502d00602601200c216005012005105006025005216005", + "0x61f00620c00521600520c00502d00620c01d00c21600501d005105006026", + "0x20700507000620720820a02a02801221600520c02602512500c005006019", + "0x521600502d00519600600621600500601200602f00562002d00521600c", + "0x5100600621600500601200614100562113d00521600c13b00512f00613b", + "0x600621600501200520a00600621600501a00506900600621600513d005", + "0x621600520d00519300600621600501d00520a006006216005010005023", + "0x52160050060bd0060062160050210051930060062160051e9005069006", + "0x501a0061480052160051460053a50061460052160050310053a4006031", + "0x521600520a00519900602a00521600502a005146006028005216005028", + "0x280120051480052160051480053a600620800521600520800514800620a", + "0x510500600621600514100505100600621600500601200614820820a02a", + "0x21600514a00562200614a00521600514a00502d00614a01200c216005012", + "0x19900602a00521600502a00514600602800521600502800501a006020005", + "0x2160050100052cf00620800521600520800514800620a00521600520a005", + "0x3902000c21600502000514700603500521600503500502600603501000c", + "0x1d62300602000521600502002100c0b600603900521600503900501d006", + "0x21600c03d00510700603d14b03c03a01701221600503903520820a02a028", + "0x614e00521600503f00514f00600621600500601200604000562403f005", + "0x501200510500601e00521600515014e00c33e006150005216005006109", + "0x1d00521600501d00502d00604400521600504400502d00604401200c216", + "0x514600601700521600501700501a00602300521600501d04400c625006", + "0x521600514b00514800603c00521600503c00519900603a00521600503a", + "0x14700615200521600515200502600615201000c2160050100052cf00614b", + "0x1e1e900c17a00604700521600504700501d00604702300c216005023005", + "0x14b03c03a01701d62300602300521600502320d00c0b600601e005216005", + "0x562616800521600c04c00510700604c16204a15e156012216005047152", + "0x21600500610900616d00521600516800514f00600621600500601200604e", + "0x1a0061700052160050062ef00601900521600505016d00c33e006050005", + "0x21600504a00519900615e00521600515e005146006156005216005156005", + "0x605101000c2160050100052cf00616200521600516200514800604a005", + "0x517000502d00602300521600502300501d006051005216005051005026", + "0x5116204a15e15601962700601900521600501901a00c17a006170005216", + "0x562805900521600c1740050b0006174057055172052012216005170023", + "0x21600505200501a0060062160050590050a9006006216005006012006178", + "0x148006055005216005055005199006172005216005172005146006052005", + "0x505b00502600605b01000c2160050100052cf006057005216005057005", + "0x17a01e00c21600501e00510500602000521600502000501d00605b005216", + "0x21600517a02005b05705517205201962700617a00521600517a00502d006", + "0x601200606500562918600521600c1850050b000618506118117f05d012", + "0x606701200c2160050120051050060062160051860050a9006006216005", + "0x1e06700c62a00601e00521600501e00502d00606700521600506700502d", + "0x17f00521600517f00514600605d00521600505d00501a006069005216005", + "0x100052cf006061005216005061005148006181005216005181005199006", + "0x521600506900501d00606b00521600506b00502600606b01000c216005", + "0x1070060700ba18d06d18a01221600506906b06118117f05d01d623006069", + "0x501200502d00600621600500601200607200562b19600521600c070005", + "0x19a00521600501901200c62a00601900521600501900502d006012005216", + "0x6d00514600618a00521600518a00501a00619d00521600519600514f006", + "0xba0052160050ba00514800618d00521600518d00519900606d005216005", + "0x19d00502d00619a00521600519a00501d006010005216005010005026006", + "0x1a11a007601221600519d19a0100ba18d06d18a01962700619d005216005", + "0x60062160050060120061a800562c1a400521600c07b0050b000607b1a2", + "0x521600507e0053a400607e0052160050060bd0060062160051a40050a9", + "0x514600607600521600507600501a00608c0052160051ab0053a50061ab", + "0x52160051a20051480061a10052160051a10051990061a00052160051a0", + "0x500601200608c1a21a11a007601200508c00521600508c0053a60061a2", + "0x14600607600521600507600501a00608d0052160051a80053a7006006216", + "0x2160051a20051480061a10052160051a10051990061a00052160051a0005", + "0x601200608d1a21a11a007601200508d00521600508d0053a60061a2005", + "0x520a00600621600501000502300600621600501200520a006006216005", + "0x18a00521600518a00501a00608e0052160050720053a7006006216005019", + "0xba00514800618d00521600518d00519900606d00521600506d005146006", + "0x608e0ba18d06d18a01200508e00521600508e0053a60060ba005216005", + "0x600621600501200520a00600621600501900520a006006216005006012", + "0x52160050650053a700600621600501e00520a006006216005010005023", + "0x519900617f00521600517f00514600605d00521600505d00501a00608f", + "0x521600508f0053a6006061005216005061005148006181005216005181", + "0x21600501900520a00600621600500601200608f06118117f05d01200508f", + "0x501e00520a00600621600501000502300600621600501200520a006006", + "0x501a0060900052160051780053a7006006216005020005168006006216", + "0x5216005055005199006172005216005172005146006052005216005052", + "0x520120050900052160050900053a6006057005216005057005148006055", + "0x502300600621600501200520a006006216005006012006090057055172", + "0x16800600621600502000516800600621600501e00520a006006216005010", + "0x9100521600504e0053a700600621600501a005069006006216005023005", + "0x4a00519900615e00521600515e00514600615600521600515600501a006", + "0x910052160050910053a600616200521600516200514800604a005216005", + "0x621600501a00506900600621600500601200609116204a15e156012005", + "0x21600501d00520a00600621600501000502300600621600501200520a006", + "0x51e900506900600621600520d005193006006216005020005168006006", + "0x14600601700521600501700501a0060920052160050400053a7006006216", + "0x21600514b00514800603c00521600503c00519900603a00521600503a005", + "0x601200609214b03c03a0170120050920052160050920053a600614b005", + "0x502300600621600501200520a00600621600501a005069006006216005", + "0x6900600621600520d00519300600621600501d00520a006006216005010", + "0x9300521600502f0053a70060062160050210051930060062160051e9005", + "0x20a00519900602a00521600502a00514600602800521600502800501a006", + "0x930052160050930053a600620800521600520800514800620a005216005", + "0x500500547f00600500521600500600562d00609320820a02a028012005", + "0x230052160050060050060200052160050060d7006005005005005005216", + "0x600521600500600501a0060062160050060c400600621600500600c006", + "0x1d00501d006125005216005125005199006005005216005005005146006", + "0xe30060261e902520d01021600501d1250050060103c400601d005216005", + "0x1200602800562e20c00521600c0260051070061e90052160051e902000c", + "0xc21600502a20d00c5a700602a00521600520c00514f006006216005006", + "0x514600620a00521600520a00501a00620700521600500620800620820a", + "0x521600501a005026006010005216005010005148006025005216005025", + "0x502600620700521600520700504e00601900521600501900519f00601a", + "0x1a01002520a01a62f006208005216005208005573006012005216005012", + "0x2100521600502102300c15e00613b02102f02d010216005208012207019", + "0x620800600621600500601200614100563013d00521600c13b005619006", + "0x2160051480050cc00614a14814612521600513d005631006031005216005", + "0x517200603903500c21600514600505200600621600514a005051006006", + "0x2d00521600502d00501a006017005216005039005055006006216005035", + "0x3100504e00600c00521600500c00519b00602f00521600502f005146006", + "0x1e00521600501e0050cf00601700521600501700501d006031005216005", + "0x3d00561600603d14b03c03a01021600501e01703100c02f02d01d632006", + "0x1021600503f00522900600621600500601200604000563303f00521600c", + "0x510060062160050440050ca00600621600515000516800615204415014e", + "0x21600504700517200615604700c21600514e005052006006216005152005", + "0x517400604a00521600515e00505700615e005216005156005055006006", + "0x521600503c00514600603a00521600503a00501a00616200521600504a", + "0x51480061e90052160051e900519900614b00521600514b00519b00603c", + "0x211e914b03c03a01d00516200521600516200514a006021005216005021", + "0x503a00501a00604c005216005040005141006006216005006012006162", + "0x614b00521600514b00519b00603c00521600503c00514600603a005216", + "0x504c00514a0060210052160050210051480061e90052160051e9005199", + "0x50ca00600621600500601200604c0211e914b03c03a01d00504c005216", + "0x2d00521600502d00501a00616800521600514100514100600621600501e", + "0x1e900519900600c00521600500c00519b00602f00521600502f005146006", + "0x16800521600516800514a0060210052160050210051480061e9005216005", + "0x21600501e0050ca0060062160050060120061680211e900c02f02d01d005", + "0x50190050cc00600621600501200502300600621600502300502a006006", + "0x501a00604e00521600502800514100600621600501a005023006006216", + "0x521600500c00519b00602500521600502500514600620d00521600520d", + "0x514a0060100052160050100051480061e90052160051e900519900600c", + "0x60062160050060c400604e0101e900c02520d01d00504e00521600504e", + "0x21600500500514600600600521600500600501a00601e005216005006634", + "0x14800612500521600512500519900600c00521600500c00519b006005005", + "0x21600501e00563500601d00521600501d00501d006010005216005010005", + "0x2600601a00521600501a0050cf00601200521600501200502d00601e005", + "0x501901a01201e01d01012500c0050061e9636006019005216005019005", + "0x620c00563802600521600c02500563700602520d0230210201e901d216", + "0x502800516800620a02a028125216005026005639006006216005006012", + "0x563c00620800521600520a00563b00600621600502a00563a006006216", + "0x52160050200051460061e90052160051e900501a006207005216005208", + "0x514800602300521600502300519900602100521600502100519b006020", + "0x20d0230210201e901d00520700521600520700563d00620d00521600520d", + "0x51e900501a00602d00521600520c00563e006006216005006012006207", + "0x602100521600502100519b0060200052160050200051460061e9005216", + "0x502d00563d00620d00521600520d005148006023005216005023005199", + "0xc00601200521600500663f00602d20d0230210201e901d00502d005216", + "0x503900601d0052160050060350060062160050060c4006006216005006", + "0x1e901e00c64001a01900c21600c01d00500612501700601d00521600501d", + "0x62160050060b400602000521600500c005563006006216005006012006", + "0x20d00564202302100c21600c02000564100601900521600501900501a006", + "0x216005021005644006025005216005023005643006006216005006012006", + "0x50060120060066460050061b0006026005216005025005645006010005", + "0x564400602800521600520c00564700620c0052160050060bd006006216", + "0x21600501001200c64800602600521600502800564500601000521600520d", + "0x600621600500601200620a00564a02a00521600c026005649006010005", + "0x521600501900501a00620800521600502a00564b0060062160050060c4", + "0x504e00620800521600520800501d00601a00521600501a005146006019", + "0x5000602f02d20712521600512520801a01901016d006125005216005125", + "0x501000564d00600621600500601200613d00564c13b00521600c02f005", + "0x600621600514600505100614603100c21600513b005170006141005216", + "0x514100540000602d00521600502d00514600620700521600520700501a", + "0x21600503114102d20701056500603100521600503100504e006141005216", + "0x21600500601200601700564e03900521600c03500505000603514a148125", + "0x501a00600621600503c00505100603c03a00c216005039005170006006", + "0x521600503a00504e00603d00521600514a00514600614b005216005148", + "0x521600501700514500600621600500601200600664f0050061b000603f", + "0x514200614a00521600514a00514600614800521600514800501a006040", + "0x1000565000600621600500601200604014a148125005040005216005040", + "0x620700521600520700501a00614e00521600513d005145006006216005", + "0x14e02d20712500514e00521600514e00514200602d00521600502d005146", + "0x600621600520a0050510060062160050060c4006006216005006012006", + "0x21600501a00514600614b00521600501900501a006006216005010005650", + "0xc1110061500052160050060bd00603f00521600512500504e00603d005", + "0x21600514b00501a00615200521600504400511300604400521600515003f", + "0x12500515200521600515200514200603d00521600503d00514600614b005", + "0x12500517200600621600501200565100600621600500601200615203d14b", + "0x605900604700521600500620800600621600500c005403006006216005", + "0x521600515604700c02f00615600521600515600502d006156005216005", + "0x514500616200521600515e04a00c13d00604a00521600500613b00615e", + "0x52160051e900514600601e00521600501e00501a00604c005216005162", + "0x52160050061be00604c1e901e12500504c00521600504c0051420061e9", + "0x190052160050062080060062160050060c400600621600500600c00601a", + "0x510500601e00521600501e00502d00601e01200c216005012005105006", + "0x51e901e00c6520061e90052160051e900502d0061e901d00c21600501d", + "0x600500521600500500514600600600521600500600501a006020005216", + "0x50100052cf00612500521600512500514800600c00521600500c005199", + "0x2000521600502000501d00602100521600502100502600602101000c216", + "0x21600502002112500c00500601d62300601900521600501901a00c0aa006", + "0x601200602a00565302800521600c20c00510700620c02602520d023012", + "0x1a0062080052160050062ef00620a00521600502800514f006006216005", + "0x21600502500519900620d00521600520d005146006023005216005023005", + "0x2d00620800521600520800502d006026005216005026005148006025005", + "0x21600501d00502d00601200521600501200502d00620a00521600520a005", + "0x65400601900521600501900504e00601000521600501000502600601d005", + "0x613d13b02f02d20701221600501901001d01220a20802602520d0231e9", + "0x14100514900600621600500601200603100565514100521600c13d00510c", + "0x621600514a00505100600621600514800520a00614a148146125216005", + "0x20700501a006039005216005035005657006035005216005146005656006", + "0x2f00521600502f00519900602d00521600502d005146006207005216005", + "0x2d20701200503900521600503900565800613b00521600513b005148006", + "0x501a00601700521600503100565900600621600500601200603913b02f", + "0x521600502f00519900602d00521600502d005146006207005216005207", + "0x20701200501700521600501700565800613b00521600513b00514800602f", + "0x502300600621600501900517200600621600500601200601713b02f02d", + "0x65900600621600501200520a00600621600501d00520a006006216005010", + "0x21600520d00514600602300521600502300501a00603a00521600502a005", + "0x65800602600521600502600514800602500521600502500519900620d005", + "0x1a0052160050061be00603a02602520d02301200503a00521600503a005", + "0x60062160050060c400600621600500600c0061e900521600500605b006", + "0xc01d00556700601900521600501901a00c0aa006019005216005006208", + "0x602300521600500620800600621600500601200602100565a020005216", + "0x502500502d00600621600520d00520a00602520d00c2160050120052f1", + "0x2600c21600502302500c21a00602300521600502300504e006025005216", + "0x520a00602a02800c21600502000556900600621600520c00505100620c", + "0x621600520a00520a00620820a00c2160050280052f100600621600502a", + "0x20800c21a00602600521600502600504e00620800521600520800502d006", + "0xc21600520700505200600621600502d00505100602d20700c216005026", + "0x501a00613d00521600513b00505500600621600502f00517200613b02f", + "0x521600500c005199006005005216005005005146006006005216005006", + "0x14101021600513d00c0050060103c400613d00521600513d00501d00600c", + "0x621600500601200603500565b14a00521600c148005107006148146031", + "0x502d00603901e00c21600501e00510500601e00521600514a00514f006", + "0x521600514100501a006017005216005039005622006039005216005039", + "0x5148006146005216005146005199006031005216005031005146006141", + "0x21600503a00502600603a01000c2160050100052cf006125005216005125", + "0x601e00521600501e1e900c17a00601700521600501700501d00603a005", + "0x4000510700604003f03d14b03c01221600501703a12514603114101d623", + "0x521600514e00514f00600621600500601200615000565c14e00521600c", + "0x14b00514600603c00521600503c00501a0061520052160050062ef006044", + "0x3f00521600503f00514800603d00521600503d00519900614b005216005", + "0x1000502600604400521600504400502d00615200521600515200502d006", + "0x1900521600501900504e00601e00521600501e00502d006010005216005", + "0x16204a15e15604701221600501901e01004415203f03d14b03c01e65d006", + "0x517000600621600500601200616800565e04c00521600c162005050006", + "0x521600504700501a00600621600516d00505100616d04e00c21600504c", + "0x514800605100521600515e005199006170005216005156005146006050", + "0x600665f0050061b000617200521600504e00504e00605200521600504a", + "0x521600504700501a006055005216005168005141006006216005006012", + "0x514800615e00521600515e005199006156005216005156005146006047", + "0x5504a15e15604701200505500521600505500514a00604a00521600504a", + "0x621600501e00520a006006216005019005172006006216005006012006", + "0x503c00501a006057005216005150005141006006216005010005023006", + "0x603d00521600503d00519900614b00521600514b00514600603c005216", + "0x3d14b03c01200505700521600505700514a00603f00521600503f005148", + "0x501000502300600621600501900517200600621600500601200605703f", + "0x501a0061740052160050350051410060062160051e9005069006006216", + "0x5216005146005199006031005216005031005146006141005216005141", + "0x14101200517400521600517400514a006125005216005125005148006146", + "0x5069006006216005021005051006006216005006012006174125146031", + "0x521600505900502d00605901200c2160050120051050060062160051e9", + "0x514600600600521600500600501a006178005216005059005622006059", + "0x521600512500514800600c00521600500c005199006005005216005005", + "0x1d00605b00521600505b00502600605b01000c2160050100052cf006125", + "0x5d17a01221600517805b12500c00500601d623006178005216005178005", + "0x621600500601200618600566018500521600c06100510700606118117f", + "0x517a00501a0060670052160050062ef00606500521600518500514f006", + "0x617f00521600517f00519900605d00521600505d00514600617a005216", + "0x506500502d00606700521600506700502d006181005216005181005148", + "0x601000521600501000502600601900521600501900504e006065005216", + "0x501201001906506718117f05d17a01e66100601200521600501200502d", + "0x120060700056620ba00521600c18d00510c00618d06d18a06b069012216", + "0x21600507200520a00619a0721961252160050ba005149006006216005006", + "0x6b00514600605000521600506900501a00600621600519a005051006006", + "0x5200521600506d00514800605100521600518a005199006170005216005", + "0x517200607619d00c21600517200505200617200521600519600504e006", + "0x1a10052160051a00050570061a000521600507600505500600621600519d", + "0x17000514600605000521600505000501a0061a20052160051a1005174006", + "0x52005216005052005148006051005216005051005199006170005216005", + "0x2160050060120061a20520511700500120051a20052160051a200514a006", + "0x514600606900521600506900501a00607b005216005070005141006006", + "0x521600506d00514800618a00521600518a00519900606b00521600506b", + "0x500601200607b06d18a06b06901200507b00521600507b00514a00606d", + "0x1900517200600621600501000502300600621600501200520a006006216", + "0x617a00521600517a00501a0061a4005216005186005141006006216005", + "0x518100514800617f00521600517f00519900605d00521600505d005146", + "0x44b0061a418117f05d17a0120051a40052160051a400514a006181005216", + "0x66301a01901d12521600c01212500c00501044c006012005216005010005", + "0x2d00600600521600500600501a0060062160050060120060201e901e125", + "0x1d00514600602302100c21600501a00600c66400601a00521600501a005", + "0x20d00521600c02300518a00601900521600501900514800601d005216005", + "0x566600602600521600520d00540e006006216005006012006025005665", + "0x521600502800566800602800521600520c00566700620c005216005026", + "0x514800601d00521600501d00514600602100521600502100501a00602a", + "0x602a01901d02101000502a00521600502a005669006019005216005019", + "0x620a005216005006208006006216005025005051006006216005006012", + "0x520820a00c02f00620800521600520800502d00620800521600500666a", + "0x602f00521600520702d00c13d00602d00521600500613b006207005216", + "0x501d00514600602100521600502100501a00613b00521600502f00566b", + "0x513b00521600513b00566900601900521600501900514800601d005216", + "0x613d00521600502000566c00600621600500601200613b01901d021010", + "0x500600501a00603100521600514100566800614100521600513d005667", + "0x61e90052160051e900514800601e00521600501e005146006006005216", + "0x521600c0060052320060311e901e006010005031005216005031005669", + "0x57c00612500521600500500557b00600621600500601200600c00566d005", + "0x601200601000500501000521600501000557d006010005216005125005", + "0x601d00521600500c01200c13d00601200521600500613b006006216005", + "0x41300601900500501900521600501900557d00601900521600501d00557e", + "0x21600512500544b00601d00521600501200536a006012005216005010005", + "0x210201e912566e01e01a00c21600c01d01900c005006012474006019005", + "0x521600502300539e0060230052160050060bd006006216005006012006", + "0x539f00601e00521600501e00514800601a00521600501a00514600620d", + "0x210053a100600621600500601200620d01e01a12500520d00521600520d", + "0x200052160050200051480061e90052160051e9005146006025005216005", + "0x521600500600566f0060250201e912500502500521600502500539f006", + "0xc00521600c00600552700600500500500500521600500500547f006005", + "0xc0053a400600621600500500520a006006216005006012006125005670", + "0x120052160050120053a60060120052160050100053a5006010005216005", + "0x5006208006006216005125005172006006216005006012006012005005", + "0x601a00521600500613b00601900521600500501d00c02f00601d005216", + "0x1e90053a60061e900521600501e0053a700601e00521600501901a00c13d", + "0x500500547f0060050052160050060056710061e90050051e9005216005", + "0x21600500500547f006005005216005006005672006005005005005005216", + "0x500601200600c00567400500521600c006005673006005005005005005", + "0x5b40060100052160051250055b30061250052160050050055b2006006216", + "0x21600500613b006006216005006012006010005005010005216005010005", + "0x601900521600501d0055b500601d00521600500c01200c13d006012005", + "0x612500600c2160050060056750060190050050190052160050190055b4", + "0x501000518d00600621600501200506d00601201000c216005125005676", + "0x1d00c21600500c01000c1b500600c00521600500c00504e006010005216", + "0x506d00601e01a00c216005006005676006006216005019005051006019", + "0x1d00521600501d00504e00601e00521600501e00518d00600621600501a", + "0x60bd0060062160050200050510060201e900c21600501d01e00c1b5006", + "0x1e90052160051e900504e00600500521600500500504e006021005216005", + "0x21600512500567800612500600c2160050060056770060211e9005125005", + "0x2d00600621600501d00506d0060062160050120051ab00601d012010125", + "0x500c01000c21a00600c00521600500c00504e006010005216005010005", + "0x1e00600c21600500600567700600621600501a00505100601a01900c216", + "0x506d0060062160051e900520a0060210201e912521600501e005678006", + "0x1900521600501900504e00602000521600502000508c006006216005021", + "0x567800600621600520d00505100620d02300c21600501902000c092006", + "0x2160050260051ab00600621600502500520a00620c026025125216005006", + "0xc1b500602300521600502300504e00620c00521600520c00518d006006", + "0x52160050060bd00600621600502a00505100602a02800c21600502320c", + "0x512500502800521600502800504e00600500521600500500504e00620a", + "0x1201000c21600512500567a00612500600c21600500600567900620a028", + "0x500c00504e00601000521600501000508c00600621600501200506d006", + "0x621600501900505100601901d00c21600500c01000c09200600c005216", + "0x1e00518d00600621600501a0051ab00601e01a00c21600500600567a006", + "0xc21600501d01e00c1b500601d00521600501d00504e00601e005216005", + "0x500504e0060210052160050060bd0060062160050200050510060201e9", + "0x567b0060211e90051250051e90052160051e900504e006005005216005", + "0x521600500c00504e00612500521600512500508c006125005216005006", + "0xbd00600621600501200505100601201000c21600500c12500c09200600c", + "0x521600501000504e00600500521600500500504e00601d005216005006", + "0xc00c21600500c00567c0060062160050060c400601d010005125005010", + "0x502d00600621600501900516800601901d00c21600501200567d006012", + "0x21600501001d00c21a00601000521600501000504e00601d00521600501d", + "0x60201e900c21600500c00567d00600621600501e00505100601e01a00c", + "0x21600500500514600600600521600500600501a0060062160051e900520a", + "0x16d00601a00521600501a00504e00602000521600502000501d006005005", + "0x67e02500521600c20d00505000620d02302112521600501a020005006010", + "0x505100602820c00c216005025005170006006216005006012006026005", + "0x521600502a20c1251255dc00602a0052160050060bd006006216005028", + "0x514600602100521600502100501a00620800521600520a00522b00620a", + "0x120062080230211250052080052160052080055cd006023005216005023", + "0x62070052160050260055cc006006216005125005172006006216005006", + "0x52070055cd00602300521600502300514600602100521600502100501a", + "0x568000612500600c21600500600567f006207023021125005207005216", + "0x21600501d0051ab0060062160050120051ab00601d012010125216005125", + "0xc21a00600c00521600500c00504e00601000521600501000502d006006", + "0x21600500600567f00600621600501a00505100601a01900c21600500c010", + "0x62160051e900520a0060210201e912521600501e00568000601e00600c", + "0x501900504e00602000521600502000508c0060062160050210051ab006", + "0x621600520d00505100620d02300c21600501902000c092006019005216", + "0x51ab00600621600502500520a00620c026025125216005006005680006", + "0x2300521600502300504e00620c00521600520c00508c006006216005026", + "0x60bd00600621600502a00505100602a02800c21600502320c00c092006", + "0x2800521600502800504e00600500521600500500504e00620a005216005", + "0x1200c00c21600500c0056810060062160050060c400620a028005125005", + "0xcc00600621600501900516800601e01a01901d010216005012005682006", + "0x1d00521600501d00502d00600621600501e00516800600621600501a005", + "0x510060201e900c21600501001d00c21a00601000521600501000504e006", + "0x21600502100568200602100c00c21600500c005681006006216005020005", + "0x60062160050250050cc00600621600502300520a00602602520d023010", + "0x21600500500514600600600521600500600501a006006216005026005168", + "0x16d0061e90052160051e900504e00620d00521600520d00501d006005005", + "0x68320a00521600c02a00505000602a02820c1252160051e920d005006010", + "0x568200620700c00c21600500c005681006006216005006012006208005", + "0x502f00516800600621600502d00520a00613d13b02f02d010216005207", + "0x568500614100521600513b00568400600621600513d005168006006216", + "0x21600514800505100614814600c21600520a005170006031005216005141", + "0x1703903501021600500c00568200614a00521600503114600c02f006006", + "0x50170050cc00600621600503900516800600621600503500520a00603a", + "0x1d00602800521600502800514600620c00521600520c00501a006006216", + "0x3a02820c01016d00614a00521600514a00504e00603a00521600503a005", + "0x1200604000568603f00521600c03d00505000603d14b03c12521600514a", + "0x621600515000505100615014e00c21600503f005170006006216005006", + "0x522b00615200521600504414e1251255dc0060440052160050060bd006", + "0x521600514b00514600603c00521600503c00501a006047005216005152", + "0x621600500601200604714b03c1250050470052160050470055cd00614b", + "0x503c00501a0061560052160050400055cc006006216005125005172006", + "0x51560052160051560055cd00614b00521600514b00514600603c005216", + "0x568700600621600512500517200600621600500601200615614b03c125", + "0x20c00521600520c00501a00615e0052160052080055cc00600621600500c", + "0x2820c12500515e00521600515e0055cd006028005216005028005146006", + "0x1200568900601200c00c21600500c0056880060062160050060c400615e", + "0x1d00521600501d00502d00600621600501900516800601901d00c216005", + "0x5100601e01a00c21600501001d00c21a00601000521600501000504e006", + "0x2160051e900520a0060201e900c21600500c00568900600621600501e005", + "0x501d00600500521600500500514600600600521600500600501a006006", + "0x1a02000500601016d00601a00521600501a00504e006020005216005020", + "0x601200602600568a02500521600c20d00505000620d023021125216005", + "0x600621600502800505100602820c00c216005025005170006006216005", + "0x20a00522b00620a00521600502a20c1251255dc00602a0052160050060bd", + "0x2300521600502300514600602100521600502100501a006208005216005", + "0x60062160050060120062080230211250052080052160052080055cd006", + "0x21600502100501a0062070052160050260055cc006006216005125005172", + "0x1250052070052160052070055cd006023005216005023005146006021005", + "0x1012521600512500568c00612500600c21600500600568b006207023021", + "0x1000502d00600621600501d00519a00600621600501200506d00601d012", + "0xc21600500c01000c21a00600c00521600500c00504e006010005216005", + "0x68c00601e00600c21600500600568b00600621600501a00505100601a019", + "0x502100519a0060062160051e900520a0060210201e912521600501e005", + "0x1b500601900521600501900504e00602000521600502000518d006006216", + "0x500600568c00600621600520d00505100620d02300c21600501902000c", + "0x600621600502600506d00600621600502500520a00620c026025125216", + "0x2320c00c07600602300521600502300504e00620c00521600520c00519d", + "0x620a0052160050060bd00600621600502a00505100602a02800c216005", + "0x20a02800512500502800521600502800504e00600500521600500500504e", + "0x601d01201012521600512500568e00612500600c21600500600568d006", + "0x521600501000518d00600621600501d00519a00600621600501200520a", + "0x601a01900c21600500c01000c1b500600c00521600500c00504e006010", + "0x501e00568e00601e00600c21600500600568d00600621600501a005051", + "0x600621600502100519a0060062160051e900506d0060210201e9125216", + "0x1902000c21a00601900521600501900504e00602000521600502000502d", + "0x2512521600500600568e00600621600520d00505100620d02300c216005", + "0x20c00519d00600621600502600520a00600621600502500506d00620c026", + "0xc21600502320c00c07600602300521600502300504e00620c005216005", + "0x500504e00620a0052160050060bd00600621600502a00505100602a028", + "0x568f00620a02800512500502800521600502800504e006005005216005", + "0x501200506d00601201000c21600512500569000612500600c216005006", + "0x1b500600c00521600500c00504e00601000521600501000518d006006216", + "0x500600569000600621600501900505100601901d00c21600500c01000c", + "0x601e00521600501e00518d00600621600501a00506d00601e01a00c216", + "0x50510060201e900c21600501d01e00c1b500601d00521600501d00504e", + "0x600500521600500500504e0060210052160050060bd006006216005020", + "0x1470060062160050060c40060211e90051250051e90052160051e900504e", + "0x501900502000601900521600501d0051e900601d01000c216005010005", + "0x1e900521600501e00554700601e01200c21600501200522200601a005216", + "0x502300602302100c21600501a0050210060200052160051e9005548006", + "0x621600520d00502300602520d00c216005020005021006006216005021", + "0x2600502600620c005216005025005025006026005216005023005025006", + "0x621600c20c02600c20c00620c00521600520c005026006026005216005", + "0x51250051720060062160050120050ca006006216005006012006006691", + "0x5006692006028005216005006208006006216005010005168006006216", + "0x20a00521600502a02800c02f00602a00521600502a00502d00602a005216", + "0x20700569300620700521600520a20800c13d00620800521600500613b006", + "0x500521600500500514600600600521600500600501a00602d005216005", + "0x500601000502d00521600502d00569400600c00521600500c00519b006", + "0x2160050063d800602f0052160050062ef00600621600500601200602d00c", + "0x19b00600500521600500500514600600600521600500600501a00613b005", + "0x21600512500504e00601000521600501000501d00600c00521600500c005", + "0x19f00602f00521600502f00502d0060120052160050120050cf006125005", + "0x1021600513b02f01212501000c00500601a69500613b00521600513b005", + "0x21600500601200614a00569714800521600c14600569600614603114113d", + "0x21600503a0050cc00614b03c03a01703903501d216005148005698006006", + "0x60bd00603d00521600501703c00c02f00600621600514b005051006006", + "0x504000569a00604000521600503f03903503d01069900603f005216005", + "0x614100521600514100514600613d00521600513d00501a00614e005216", + "0x3114113d01000514e00521600514e00569400603100521600503100519b", + "0x513d00501a00615000521600514a00569300600621600500601200614e", + "0x603100521600503100519b00614100521600514100514600613d005216", + "0x60062160050060c400615003114113d010005150005216005150005694", + "0x19005006125017006019005216005019005039006019005216005006035", + "0x2160050060b40060062160050060120060201e900c69b01e01a00c21600c", + "0x569d02302100c21600c12500569c00601a00521600501a00501a006006", + "0x50250051640060250052160050230050bf00600621600500601200620d", + "0x60280052160050260050e900620c00521600502100504e006026005216", + "0xfa00602a0052160050060bd00600621600500601200600669e0050061b0", + "0x21600520a0050e900620c00521600520d00504e00620a00521600502a005", + "0x600621600500601200620700569f20800521600c02800501e006028005", + "0xc6a100602f01d00c21600501d0053c600602d01200c2160050120056a0", + "0x513b0054dc00613d01000c2160050100052cf00613b00521600502f02d", + "0x1481461256a203114100c21600c20813b13d00c01e01247400613b005216", + "0x503100514800603500521600514100514600600621600500601200614a", + "0x514a0051720060062160050060120060066a30050061b0006039005216", + "0x6a4006039005216005148005148006035005216005146005146006006216", + "0x521600501d00519f00601a00521600501a00501a006017005216005006", + "0x3c03a00c21600501701d01a1256a500601700521600501700519f00601d", + "0x60c400600621600500601200603d0056a714b00521600c03c0056a6006", + "0x603a00521600503a00501a00603f00521600514b005234006006216005", + "0x520c00504e006039005216005039005148006035005216005035005146", + "0x601200521600501200557300601000521600501000502600620c005216", + "0x1021600503f01201020c03903503a01961800603f00521600503f00519f", + "0x2160050060120060470056a815200521600c04400561900604415014e040", + "0x521600504a15e1561256a900604a15e156125216005152005631006006", + "0x514600604000521600504000501a00604c0052160051620056aa006162", + "0x521600504c0056ab00615000521600515000514800614e00521600514e", + "0x52160050470056ac00600621600500601200604c15014e04001000504c", + "0x514800614e00521600514e00514600604000521600504000501a006168", + "0x616815014e0400100051680052160051680056ab006150005216005150", + "0x2300600621600501200561c0060062160050060c4006006216005006012", + "0x4e00521600503d0056ac00600621600520c005172006006216005010005", + "0x3900514800603500521600503500514600603a00521600503a00501a006", + "0x1200604e03903503a01000504e00521600504e0056ab006039005216005", + "0x561c0060062160052070050510060062160050060c4006006216005006", + "0x6a900616d0052160050060bd006006216005010005023006006216005012", + "0x1a00501a0061700052160050500056aa00605000521600516d01d20c125", + "0xc00521600500c00514800601e00521600501e00514600601a005216005", + "0x621600500601200617000c01e01a0100051700052160051700056ab006", + "0x21600501000502300600621600501200561c006006216005125005172006", + "0x21600500605900605100521600500620800600621600501d0050cc006006", + "0x617200521600505205100c02f00605200521600505200502d006052005", + "0x50570056ac00605700521600517205500c13d00605500521600500613b", + "0x60200052160050200051460061e90052160051e900501a006174005216", + "0xc0201e90100051740052160051740056ab00600c00521600500c005148", + "0x501d00502d00601200521600501200502d0060062160050060c4006174", + "0x600521600500600501a00601900521600501d01200c62500601d005216", + "0x12500514800600c00521600500c005199006005005216005005005146006", + "0x1900521600501900501d006010005216005010005026006125005216005", + "0x51070060210201e901e01a01221600501901012500c00500601d623006", + "0x21600502300514f00600621600500601200620d0056ad02300521600c021", + "0x2ef00600621600502600520a00620c02600c2160050250052f1006025005", + "0x21600502a00520a00620a02a00c2160050280052f1006028005216005006", + "0x20a00602d20700c2160052080052f100620800521600520c0050c1006006", + "0xc21600502f0052f100602f00521600520a0050c1006006216005207005", + "0x50c100614100521600502d0050c100600621600513b00520a00613d13b", + "0x62160050060b400614600521600503114100c33e00603100521600513d", + "0x120061480056ae00621600c14600533f00614600521600514600502d006", + "0x603500521600514a0052f900614a0052160050060bd006006216005006", + "0x60062160050060120060066af0050061b000603900521600503500519d", + "0x52160050170053090060170052160050060bd006006216005148005343", + "0x50390054220060062160050060c400603900521600503a00519d00603a", + "0x603d00521600514b0052ea00614b00521600503c00517c00603c005216", + "0x51e900519900601e00521600501e00514600601a00521600501a00501a", + "0x503d00521600503d0052eb0060200052160050200051480061e9005216", + "0x3f00521600520d0052ec00600621600500601200603d0201e901e01a012", + "0x1e900519900601e00521600501e00514600601a00521600501a00501a006", + "0x3f00521600503f0052eb0060200052160050200051480061e9005216005", + "0x52160050066b000600500521600500620800603f0201e901e01a012005", + "0x2f00612500521600500c00500c02f00600c00521600500c00502d00600c", + "0x1200517200601d01200c21600501000505200601000521600500612500c", + "0x501900521600501900501d00601900521600501d005055006006216005", + "0x500514600600600521600500600501a0060062160050060c4006019005", + "0x1200521600501200501d00600c00521600500c005199006005005216005", + "0x21600c01e00510700601e01a01901d01021600501200c0050060103c4006", + "0x60210052160051e900514f0060062160050060120060200056b11e9005", + "0x60b400602500521600520d00544b00620d02300c21600502101d00c5a7", + "0x21600c02501012501901044c00602300521600502300501a006006216005", + "0x502800544e00600621600500601200620820a02a1256b202820c026125", + "0x602f00521600520c00514800602d005216005026005146006207005216", + "0x60062160050060120060066b30050061b000613b00521600520700544f", + "0x520a00514800602d00521600502a00514600613d005216005208005451", + "0x614100521600513b00545200613b00521600513d00544f00602f005216", + "0x50060c40060062160050060120061460056b403100521600c141005107", + "0x45500614a00521600514800545400614800521600503100514f006006216", + "0x21600502d00514600602300521600502300501a00603500521600514a005", + "0x45600602f00521600502f00514800601a00521600501a00519900602d005", + "0x600621600500601200603502f01a02d023012005035005216005035005", + "0x521600502300501a0060390052160051460054570060062160050060c4", + "0x514800601a00521600501a00519900602d00521600502d005146006023", + "0x3902f01a02d02301200503900521600503900545600602f00521600502f", + "0x5216005020005457006006216005010005023006006216005006012006", + "0x519900601900521600501900514600601d00521600501d00501a006017", + "0x521600501700545600612500521600512500514800601a00521600501a", + "0x2160050066b500600c00521600500620800601712501a01901d012005017", + "0x601000521600512500c00c02f00612500521600512500502d006125005", + "0x505200601d00521600500501200c02f00601200521600500601000c02f", + "0x521600501a00505500600621600501900517200601a01900c21600501d", + "0x1a0060062160050060c400601e00500501e00521600501e00501d00601e", + "0x21600500c005199006005005216005005005146006006005216005006005", + "0x1021600501200c0050060103c400601200521600501200501d00600c005", + "0x2160050060120060210056b602000521600c1e90051070061e901e01a019", + "0x602520d00c21600502301900c5a700602300521600502000514f006006", + "0x521600520d00501a0060062160050060b400602600521600502500544b", + "0x620820a02a1256b702820c00c21600c01d02601012501a01247400620d", + "0x521600502800514800620700521600520c005146006006216005006012", + "0x62160052080051720060062160050060120060066b80050061b000602d", + "0x50060c400602d00521600520a00514800620700521600502a005146006", + "0x53a500613b00521600502f0053a400602f0052160050060bd006006216", + "0x521600520700514600620d00521600520d00501a00613d00521600513b", + "0x53a600602d00521600502d00514800601e00521600501e005199006207", + "0x20a00600621600500601200613d02d01e20720d01200513d00521600513d", + "0x1410052160050210053a700600621600501000502300600621600501d005", + "0x1e00519900601a00521600501a00514600601900521600501900501a006", + "0x1410052160051410053a600612500521600512500514800601e005216005", + "0x52160050066b900600c00521600500620800614112501e01a019012005", + "0x2f00601000521600512500c00c02f00612500521600512500502d006125", + "0x1d00505200601d00521600500501200c02f00601200521600500601000c", + "0x1e00521600501a00505500600621600501900517200601a01900c216005", + "0x60350060062160050060c400601e00500501e00521600501e00501d006", + "0x21600c01a00500612501700601a00521600501a00503900601a005216005", + "0xc2160050100053c600600621600500601200602102000c6ba1e901e00c", + "0x602602500c21600512500502100620d005216005023005233006023010", + "0x502600502500600621600520c00502300602820c00c21600520d005021", + "0x601e00521600501e00501a00620a00521600502800502500602a005216", + "0x50190056a00060062160050060120060066bb00621600c20a02a00c20c", + "0x21600520720800c6a100620701000c2160050100053c600620801900c216", + "0x54dc00602f01d00c21600501d0052cf0060062160050060b400602d005", + "0x1256bc14113d13b12521600c02d02f00c1e901044c00602d00521600502d", + "0x514600614a00521600514100544e006006216005006012006148146031", + "0x521600514a00544f00603900521600513d00514800603500521600513b", + "0x52160051480054510060062160050060120060066bd0050061b0006017", + "0x544f00603900521600514600514800603500521600503100514600603a", + "0x521600c03c00510700603c00521600501700545200601700521600503a", + "0x14b00514f0060062160050060c400600621600500601200603d0056be14b", + "0x14e0052160050066a400604000521600503f01200c02f00603f005216005", + "0x14e00519f00601000521600501000519f00601e00521600501e00501a006", + "0x504000504e00604415000c21600514e01001e1256a500614e005216005", + "0x62160050060120060470056bf15200521600c0440056a6006040005216", + "0x3500514600615000521600515000501a006156005216005152005234006", + "0x25005216005025005026006039005216005039005148006035005216005", + "0x1d00502600604000521600504000504e00615600521600515600519f006", + "0x15602503903515001a62f00601900521600501900557300601d005216005", + "0x56c016800521600c04c00561900604c16204a15e01021600501901d040", + "0x1256a900617005016d12521600516800563100600621600500601200604e", + "0x515e00501a0060520052160050510056aa00605100521600517005016d", + "0x616200521600516200514800604a00521600504a00514600615e005216", + "0x600621600500601200605216204a15e0100050520052160050520056ab", + "0x504a00514600615e00521600515e00501a00617200521600504e0056ac", + "0x51720052160051720056ab00616200521600516200514800604a005216", + "0x2300600621600501900561c00600621600500601200617216204a15e010", + "0x600621600502500502300600621600504000517200600621600501d005", + "0x503500514600615000521600515000501a0060550052160050470056ac", + "0x50550052160050550056ab006039005216005039005148006035005216", + "0x561c0060062160050060c4006006216005006012006055039035150010", + "0x230060062160050100050cc00600621600501d005023006006216005019", + "0x5700521600503d0056ac006006216005012005172006006216005025005", + "0x3900514800603500521600503500514600601e00521600501e00501a006", + "0x1200605703903501e0100050570052160050570056ab006039005216005", + "0x2300600621600501d00502300600621600501900561c006006216005006", + "0x2160051740100121256a90061740052160050060bd006006216005025005", + "0x14600601e00521600501e00501a0061780052160050590056aa006059005", + "0x2160051780056ab00600c00521600500c0051480061e90052160051e9005", + "0x21600501900561c00600621600500601200617800c1e901e010005178005", + "0x50120051720060062160050100050cc00600621600501d005023006006", + "0x500605900605b005216005006208006006216005125005023006006216", + "0x5d00521600517a05b00c02f00617a00521600517a00502d00617a005216", + "0x1810056ac00618100521600505d17f00c13d00617f00521600500613b006", + "0x2100521600502100514600602000521600502000501a006061005216005", + "0x210200100050610052160050610056ab00600c00521600500c005148006", + "0x2160050066c100601d0052160050062ef0060062160050060c400606100c", + "0x19b00600500521600500500514600600600521600500600501a006019005", + "0x21600512500504e0060120052160050120050cf00600c00521600500c005", + "0x19f00601d00521600501d00502d00601000521600501000501d006125005", + "0x1021600501901d01012501200c00500601a6c2006019005216005019005", + "0x2160050060120060230056c402100521600c0200056c30060201e901e01a", + "0x21600502600520a00602a02820c02602520d01d2160050210056c5006006", + "0x620a00521600502a20d02502801069900600621600520c0050cc006006", + "0x501e00514600601a00521600501a00501a00620800521600520a00569a", + "0x52080052160052080056940061e90052160051e900519b00601e005216", + "0x62070052160050230056930060062160050060120062081e901e01a010", + "0x51e900519b00601e00521600501e00514600601a00521600501a00501a", + "0x60b20062071e901e01a0100052070052160052070056940061e9005216", + "0x50060350060062160050060c400600621600500600c006020005216005", + "0xc21600c021005006125017006021005216005021005039006021005216", + "0x20c0052160050120051e900600621600500601200602602500c6c620d023", + "0x21600c20c0051b600602300521600502300501a0060062160050060b4006", + "0x20800521600502a0050b800600621600500601200620a0056c702a02800c", + "0x50061b000602d0052160052080051b20062070052160050280051b4006", + "0x502f0051ac00602f0052160050060bd0060062160050060120060066c8", + "0x602d00521600513b0051b200620700521600520a0051b400613b005216", + "0x2d0051ae0061e90052160051e902000c0b60061e9005216005207005055", + "0x60062160050060c40060062160050060120061410056c913d00521600c", + "0x521600500653e00614600521600500620800603100521600513d0050bf", + "0x10500614a00521600514814600c02f00614800521600514800502d006148", + "0x310050c100603900521600503514a00c02f00603501900c216005019005", + "0x521600501703900c02f00601700521600501700502d006017005216005", + "0x517200603d14b00c21600503a00505200603c00521600500620d00603a", + "0x60400052160050063d800603f00521600503d00505500600621600514b", + "0x500c00519b00620d00521600520d00514600602300521600502300501a", + "0x601000521600501000514800612500521600512500519900600c005216", + "0x504000519f00603f00521600503f00501d00603c00521600503c005026", + "0x14e00521600514e00502600614e01e00c21600501e0052cf006040005216", + "0x1e95570061500052160051500050cf00615001a00c21600501a005222006", + "0x604a15e15604715204401d21600515014e04003f03c01012500c20d023", + "0x16200516200600621600500601200604c0056ca16200521600c04a00504a", + "0x521600504400501a00604e00521600516801d00c6cb006168005216005", + "0x519900604700521600504700519b006152005216005152005146006044", + "0x52160051e900501d00615e00521600515e005148006156005216005156", + "0x50cf00601900521600501900502d00604e00521600504e0056350061e9", + "0x1560471520441e963600601e00521600501e00502600601a00521600501a", + "0xc17200563700617205205117005016d01d21600501e01a01904e1e915e", + "0x1741252160050550056390060062160050060120060570056cc055005216", + "0x17a00521600505b0056ce00605b0052160051780591741256cd006178059", + "0x17000519b00605000521600505000514600616d00521600516d00501a006", + "0x52005216005052005148006051005216005051005199006170005216005", + "0x500601200617a05205117005016d01d00517a00521600517a0056cf006", + "0x14600616d00521600516d00501a00605d0052160050570056d0006006216", + "0x21600505100519900617000521600517000519b006050005216005050005", + "0x1d00505d00521600505d0056cf006052005216005052005148006051005", + "0x600621600501e00502300600621600500601200605d05205117005016d", + "0x62160051e900516800600621600501900520a00600621600501a0050ca", + "0x504400501a00617f00521600504c0056d000600621600501d00563a006", + "0x604700521600504700519b006152005216005152005146006044005216", + "0x517f0056cf00615e00521600515e005148006156005216005156005199", + "0x60c400600621600500601200617f15e15604715204401d00517f005216", + "0x50ca00600621600501e005023006006216005141005051006006216005", + "0x6118100c21600501d0056d100600621600501900520a00600621600501a", + "0x6ce0061860052160051851811e91256cd00618500521600506100564d006", + "0x21600520d00514600602300521600502300501a006065005216005186005", + "0x14800612500521600512500519900600c00521600500c00519b00620d005", + "0x12500c20d02301d0050650052160050650056cf006010005216005010005", + "0x501a0050ca00600621600501e005023006006216005006012006065010", + "0x2000519300600621600501d00563a00600621600501900520a006006216", + "0x6059006067005216005006208006006216005012005168006006216005", + "0x521600506906700c02f00606900521600506900502d006069005216005", + "0x56d000606d00521600506b18a00c13d00618a00521600500613b00606b", + "0x521600502600514600602500521600502500501a00618d00521600506d", + "0x514800612500521600512500519900600c00521600500c00519b006026", + "0x1012500c02602501d00518d00521600518d0056cf006010005216005010", + "0x512500502d0061250052160050066d200600c00521600500620800618d", + "0x521600500601000c02f00601000521600512500c00c02f006125005216", + "0x601a01900c21600501d00505200601d00521600500501200c02f006012", + "0x21600501e00501d00601e00521600501a005055006006216005019005172", + "0x390061e90052160050060350060062160050060c400601e00500501e005", + "0x2300c6d302102000c21600c1e90050061250170061e90052160051e9005", + "0x1200510500602501000c21600501000510500600621600500601200620d", + "0x21600520c00502d00620c00521600502602500c33e00602601200c216005", + "0x280056d400621600c20c00533f00602000521600502000501a00620c005", + "0x621600501900520a00600621600501a005023006006216005006012006", + "0x52160050060bd00600621600501200520a00600621600501d00520a006", + "0x620800521600520a00545b00620a00521600502a01001e12545a00602a", + "0x500c00519900602100521600502100514600602000521600502000501a", + "0x520800521600520800545c00612500521600512500514800600c005216", + "0x600621600502800534300600621600500601200620812500c021020012", + "0x1900510500620700521600520700502d00620701d00c21600501d005105", + "0xc21600501000510500602d00521600502d00502d00602d01900c216005", + "0x13b00521600502f02d2071256d500602f00521600502f00502d00602f010", + "0xc00519900602100521600502100514600602000521600502000501a006", + "0x1a00c21600501a0052cf00612500521600512500514800600c005216005", + "0x1d62300613b00521600513b00501d00613d00521600513d00502600613d", + "0x21600c14a00510700614a14814603114101221600513b13d12500c021020", + "0x601700521600503500514f0060062160050060120060390056d6035005", + "0x3c01000c14d00603c00521600500610900603a00521600501701e00c02f", + "0x3100521600503100514600614100521600514100501a00614b005216005", + "0x14b00502d006148005216005148005148006146005216005146005199006", + "0x1d00521600501d00502d00601200521600501200502d00614b005216005", + "0x3a00504e00601a00521600501a00502600601900521600501900502d006", + "0x1221600503a01a01901d01214b1481460311411e965400603a005216005", + "0x50060120061520056d704400521600c15000510c00615014e04003f03d", + "0x21600515e15604712545a00615e156047125216005044005149006006216", + "0x14600603d00521600503d00501a00616200521600504a00545b00604a005", + "0x21600514e00514800604000521600504000519900603f00521600503f005", + "0x601200616214e04003f03d01200516200521600516200545c00614e005", + "0x603d00521600503d00501a00604c00521600515200545f006006216005", + "0x514e00514800604000521600504000519900603f00521600503f005146", + "0x1200604c14e04003f03d01200504c00521600504c00545c00614e005216", + "0x20a00600621600501a00502300600621600501000520a006006216005006", + "0x600621600501200520a00600621600501d00520a006006216005019005", + "0x21600514100501a00616800521600503900545f00600621600501e005172", + "0x148006146005216005146005199006031005216005031005146006141005", + "0x14814603114101200516800521600516800545c006148005216005148005", + "0x21600501d00520a00600621600501200520a006006216005006012006168", + "0x501a00502300600621600501000520a00600621600501e005172006006", + "0x500605900604e00521600500620800600621600501900520a006006216", + "0x5000521600516d04e00c02f00616d00521600516d00502d00616d005216", + "0x5100545f00605100521600505017000c13d00617000521600500613b006", + "0x20d00521600520d00514600602300521600502300501a006052005216005", + "0x5200545c00612500521600512500514800600c00521600500c005199006", + "0x60350060062160050060c400605212500c20d023012005052005216005", + "0x21600c01e00500612501700601e00521600501e00503900601e005216005", + "0xc21600501000510500600621600500601200602302100c6d80201e900c", + "0x2600521600502520d00c33e00602501200c21600501200510500620d010", + "0x2600533f0061e90052160051e900501a00602600521600502600502d006", + "0x600621600501900520a00600621600500601200620c0056d900621600c", + "0x621600501000520a00600621600501200520a00600621600501d005023", + "0x2a00511300602a00521600502801a00c1110060280052160050060bd006", + "0x200052160050200051460061e90052160051e900501a00620a005216005", + "0x20a00514200612500521600512500514800600c00521600500c005199006", + "0x534300600621600500601200620a12500c0201e901200520a005216005", + "0x521600520800502d00620801900c21600501900510500600621600520c", + "0x62a00620700521600520700502d00620701000c216005010005105006208", + "0x50200051460061e90052160051e900501a00602d00521600520720800c", + "0x612500521600512500514800600c00521600500c005199006020005216", + "0x2d00501d00602f00521600502f00502600602f01d00c21600501d0052cf", + "0x3114113d13b01221600502d02f12500c0201e901d62300602d005216005", + "0x14f00600621600500601200614a0056da14800521600c146005107006146", + "0x513b00501a00603900521600503501a00c02f006035005216005148005", + "0x614100521600514100519900613d00521600513d00514600613b005216", + "0x501200502d00601000521600501000502d006031005216005031005148", + "0x601900521600501900502d00601d00521600501d005026006012005216", + "0x503901901d01201003114113d13b01e65d00603900521600503900504e", + "0x120060400056db03f00521600c03d00505000603d14b03c03a017012216", + "0x21600515014e00c11100615014e00c21600503f005170006006216005006", + "0x14600601700521600501700501a006152005216005044005113006044005", + "0x21600514b00514800603c00521600503c00519900603a00521600503a005", + "0x601200615214b03c03a01701200515200521600515200514200614b005", + "0x601700521600501700501a006047005216005040005145006006216005", + "0x514b00514800603c00521600503c00519900603a00521600503a005146", + "0x1200604714b03c03a01701200504700521600504700514200614b005216", + "0x20a00600621600501d00502300600621600501900520a006006216005006", + "0x600621600501a00517200600621600501000520a006006216005012005", + "0x513d00514600613b00521600513b00501a00615600521600514a005145", + "0x603100521600503100514800614100521600514100519900613d005216", + "0x621600500601200615603114113d13b012005156005216005156005142", + "0x21600501a00517200600621600501200520a00600621600501000520a006", + "0x21600500620800600621600501d00502300600621600501900520a006006", + "0xc02f00604a00521600504a00502d00604a00521600500605900615e005", + "0x21600516204c00c13d00604c00521600500613b00616200521600504a15e", + "0x14600602100521600502100501a00604e005216005168005145006168005", + "0x21600512500514800600c00521600500c005199006023005216005023005", + "0x60c400604e12500c02302101200504e00521600504e005142006125005", + "0x1700601e00521600501e00503900601e005216005006035006006216005", + "0x600621600500601200602302100c6dc0201e900c21600c01e005006125", + "0xc33e00602501200c21600501200510500620d01000c216005010005105", + "0x2160051e900501a00602600521600502600502d00602600521600502520d", + "0x20a00600621600500601200620c0056dd00621600c02600533f0061e9005", + "0x600621600501200520a00600621600501900502300600621600501a005", + "0x2a00545b00602a00521600502801001d12545a0060280052160050060bd", + "0x200052160050200051460061e90052160051e900501a00620a005216005", + "0x20a00545c00612500521600512500514800600c00521600500c005199006", + "0x534300600621600500601200620a12500c0201e901200520a005216005", + "0x521600520800502d00620801a00c21600501a00510500600621600520c", + "0x62a00620700521600520700502d00620701000c216005010005105006208", + "0x50200051460061e90052160051e900501a00602d00521600520720800c", + "0x612500521600512500514800600c00521600500c005199006020005216", + "0x2d00501d00602f00521600502f00502600602f01900c2160050190052cf", + "0x3114113d13b01221600502d02f12500c0201e901d62300602d005216005", + "0x14f00600621600500601200614a0056de14800521600c146005107006146", + "0x21600500610900603900521600503501d00c02f006035005216005148005", + "0x613b00521600513b00501a00603a00521600501701000c14d006017005", + "0x503100514800614100521600514100519900613d00521600513d005146", + "0x601200521600501200502d00603a00521600503a00502d006031005216", + "0x501a00502d00601900521600501900502600603900521600503900504e", + "0x14b03c01221600501a01903901203a03114113d13b01e66100601a005216", + "0x62160050060120061500056df14e00521600c04000510c00604003f03d", + "0x15600521600504715204412545a00604715204412521600514e005149006", + "0x14b00514600603c00521600503c00501a00615e00521600515600545b006", + "0x3f00521600503f00514800603d00521600503d00519900614b005216005", + "0x21600500601200615e03f03d14b03c01200515e00521600515e00545c006", + "0x514600603c00521600503c00501a00604a00521600515000545f006006", + "0x521600503f00514800603d00521600503d00519900614b00521600514b", + "0x500601200604a03f03d14b03c01200504a00521600504a00545c00603f", + "0x1000520a00600621600501900502300600621600501a00520a006006216", + "0x545f00600621600501d00517200600621600501200520a006006216005", + "0x521600513d00514600613b00521600513b00501a00616200521600514a", + "0x545c00603100521600503100514800614100521600514100519900613d", + "0x20a00600621600500601200616203114113d13b012005162005216005162", + "0x600621600501d005172006006216005019005023006006216005012005", + "0x4c00521600500620800600621600501a00520a00600621600501000520a", + "0x16804c00c02f00616800521600516800502d006168005216005006059006", + "0x5000521600504e16d00c13d00616d00521600500613b00604e005216005", + "0x2300514600602100521600502100501a00617000521600505000545f006", + "0x12500521600512500514800600c00521600500c005199006023005216005", + "0x500600c2e100617012500c02302101200517000521600517000545c006", + "0x2160051250052e30060062160050060120060100056e012500c00c21600c", + "0x1b00060190052160050120052e400601d00521600500c00501a006012005", + "0x52e500601a0052160050060bd0060062160050060120060066e1005006", + "0x521600501e0052e400601d00521600501000501a00601e00521600501a", + "0x60200052160050066e200601e0052160050060b200601901d00c005019", + "0x390060210052160050060350060062160050060c400600621600500600c", + "0x2500c6e320d02300c21600c021005006125017006021005216005021005", + "0x2160050060b400620c0052160051250051e9006006216005006012006026", + "0x56e402a02800c21600c20c0051b600602300521600502300501a006006", + "0x50280051b400620800521600502a0050b800600621600500601200620a", + "0x60120060066e50050061b000602d0052160052080051b2006207005216", + "0x1b400613b00521600502f0051ac00602f0052160050060bd006006216005", + "0x21600520700505500602d00521600513b0051b200620700521600520a005", + "0x6e613d00521600c02d0051ae00601a00521600501a01e00c0b600601a005", + "0x1200554700603100521600513d0050bf006006216005006012006141005", + "0x14800c21600c1460056e700603100521600503100502d006146005216005", + "0x23100603900521600514a0056e90060062160050060120060350056e814a", + "0x66eb0050061b00060170052160050390056ea0061e9005216005148005", + "0x521600503a0056ec00603a0052160050060bd006006216005006012006", + "0xc6ed00601700521600503c0056ea0061e900521600503500523100603c", + "0x601200603d0056ef14b00521600c0170056ee0061e90052160051e9020", + "0x568400603f00521600514b0056f00060062160050060c4006006216005", + "0x521600520d00514600602300521600502300501a00604000521600503f", + "0x519f00603100521600503100502d00600c00521600500c00519b00620d", + "0x521600501900519f00601d00521600501d00502d006040005216005040", + "0x1001901d04003100c20d02301a6f100601000521600501000504e006019", + "0x120061560056f304700521600c1520056f200615204415014e010216005", + "0x4a0102160050470056f400615e0052160051e90053bb006006216005006", + "0x14600614e00521600514e00501a00600621600516800505100616804c162", + "0x21600501a00501d00604400521600504400519b006150005216005150005", + "0x2d00615e00521600515e0050cf00604c00521600504c00504e00601a005", + "0x4415014e01a69500616200521600516200519f00604a00521600504a005", + "0x5100521600c17000569600617005016d04e01021600516204a15e04c01a", + "0x17405705517201d2160050510056980060062160050060120060520056f5", + "0x505b0056f700605b00521600517805917405705517201d6f6006178059", + "0x616d00521600516d00514600604e00521600504e00501a00617a005216", + "0x5016d04e01000517a00521600517a0056f800605000521600505000519b", + "0x504e00501a00605d0052160050520056f900600621600500601200617a", + "0x605000521600505000519b00616d00521600516d00514600604e005216", + "0x600621600500601200605d05016d04e01000505d00521600505d0056f8", + "0x52160051560056f900600621600501a0051680060062160051e90056fa", + "0x519b00615000521600515000514600614e00521600514e00501a00617f", + "0x617f04415014e01000517f00521600517f0056f8006044005216005044", + "0x16800600621600503d0050510060062160050060c4006006216005006012", + "0x60062160050100051720060062160051e90056fa00600621600501a005", + "0x621600503100520a00600621600501d00520a0060062160050190050cc", + "0x21600506100502d0060610052160050066fb006181005216005006208006", + "0x13d00618600521600500613b00618500521600506118100c02f006061005", + "0x502300501a0060670052160050650056f900606500521600518518600c", + "0x600c00521600500c00519b00620d00521600520d005146006023005216", + "0x600621600500601200606700c20d0230100050670052160050670056f8", + "0x60062160050200056fc0060062160051410050510060062160050060c4", + "0x606b00521600506901001901d01201a01d6f60060690052160050060bd", + "0x520d00514600602300521600502300501a00618a00521600506b0056f7", + "0x518a00521600518a0056f800600c00521600500c00519b00620d005216", + "0x6fc00600621600501e00519300600621600500601200618a00c20d023010", + "0x60062160050190050cc006006216005010005172006006216005020005", + "0x62160050120050ca00600621600512500516800600621600501d00520a", + "0x21600518d00502d00618d00521600500605900606d005216005006208006", + "0x13d00607000521600500613b0060ba00521600518d06d00c02f00618d005", + "0x502500501a0060720052160051960056f90061960052160050ba07000c", + "0x600c00521600500c00519b006026005216005026005146006025005216", + "0xc00c0050061256fd00607200c0260250100050720052160050720056f8", + "0x21600501000523000600621600500601200601d01200c6fe01012500c216", + "0x1b000601e0052160050190056ff00601a00521600512500501a006019005", + "0x1a0061e900521600501d005701006006216005006012006006700005006", + "0x521600500670200601e0052160051e90056ff00601a005216005012005", + "0x6a600602100521600502001e00c70300602000521600502000502d006020", + "0x502300523400600621600500601200620d00570402300521600c021005", + "0x620c005216005026005706006026005216005025005705006025005216", + "0x620c01a00c00520c00521600520c00570700601a00521600501a00501a", + "0x521600501a00501a00602800521600520d005708006006216005006012", + "0x1e00521600500670900602801a00c00502800521600502800570700601a", + "0x61e90052160050060350060062160050060c400600621600500600c006", + "0xc70a02102000c21600c1e90050061250170061e90052160051e9005039", + "0x50060b400602500521600512500554700600621600500601200620d023", + "0x70b20c02600c21600c0250056e700602000521600502000501a006006216", + "0x2600523100602a00521600520c0056e9006006216005006012006028005", + "0x1200600670c0050061b000620800521600502a0056ea00620a005216005", + "0x602d0052160052070056ec0062070052160050060bd006006216005006", + "0x520a0053bb00620800521600502d0056ea00620a005216005028005231", + "0x2f00521600c2080056ee00601a00521600501a01e00c70d00601a005216", + "0x502f0056f00060062160050060c400600621600500601200613b00570e", + "0x602000521600502000501a00614100521600513d00568400613d005216", + "0x514100519f00600c00521600500c00519b006021005216005021005146", + "0x601d00521600501d00502d00601200521600501200501d006141005216", + "0x1021600501901d01214100c02102001970f00601900521600501900519f", + "0x21600500601200603900571103500521600c14a00571000614a148146031", + "0x3d00521600c14b00501e00614b03c03a017010216005035005712006006", + "0x1a00604000521600503d01000c02f00600621600500601200603f005713", + "0x21600514800519b006146005216005146005146006031005216005031005", + "0x1d00604000521600504000504e00601a00521600501a0050cf006148005", + "0x21600503c00519f00603a00521600503a00502d006017005216005017005", + "0x15204415014e01021600503c03a01704001a14814603101a6c200603c005", + "0x56c500600621600500601200615600571404700521600c1520056c3006", + "0x504e16804c16204a15e01d71500604e16804c16204a15e01d216005047", + "0x614e00521600514e00501a00605000521600516d00571600616d005216", + "0x505000522f00604400521600504400519b006150005216005150005146", + "0x515600571700600621600500601200605004415014e010005050005216", + "0x615000521600515000514600614e00521600514e00501a006170005216", + "0x4415014e01000517000521600517000522f00604400521600504400519b", + "0x52160050060bd00600621600503f005051006006216005006012006170", + "0x21600505200571600605200521600505101003c03a01701a01d715006051", + "0x19b00614600521600514600514600603100521600503100501a006172005", + "0x17214814603101000517200521600517200522f006148005216005148005", + "0x621600501a0050ca006006216005010005172006006216005006012006", + "0x14600514600603100521600503100501a006055005216005039005717006", + "0x5500521600505500522f00614800521600514800519b006146005216005", + "0x510060062160050060c4006006216005006012006055148146031010005", + "0x1001901d01201a01d7150060570052160050060bd00600621600513b005", + "0x521600502000501a006059005216005174005716006174005216005057", + "0x522f00600c00521600500c00519b006021005216005021005146006020", + "0x571800600621600500601200605900c021020010005059005216005059", + "0x20a0060062160050190050cc00600621600501000517200600621600501e", + "0x60062160051250050ca00600621600501200516800600621600501d005", + "0x521600505b00502d00605b005216005006059006178005216005006208", + "0xc13d00605d00521600500613b00617a00521600505b17800c02f00605b", + "0x21600502300501a00618100521600517f00571700617f00521600517a05d", + "0x22f00600c00521600500c00519b00620d00521600520d005146006023005", + "0x612500521600500620800618100c20d023010005181005216005181005", + "0x501012500c02f00601000521600501000502d006010005216005006719", + "0x521600500501d00c02f00601d00521600500601200c02f006012005216", + "0x61e901e00c21600501a00505200601a00521600500c01900c02f006019", + "0x21600502000501d0060200052160051e900505500600621600501e005172", + "0xc400600621600500600c00601e00521600500671a006020005005020005", + "0x600600521600500600501a0061e90052160050066c1006006216005006", + "0x2000519f00602001d00c21600501d0053c60061e90052160051e900519f", + "0xc0230056a600602302100c2160050201e900612571b006020005216005", + "0x2600521600520d00523400600621600500601200602500571c20d005216", + "0x100053c600620c00521600502600571d00602600521600502600519f006", + "0x20720800c71f20a02a00c21600c02820c02112571e00602801000c216005", + "0x52160051250050c100600621600520a0050cc006006216005006012006", + "0xc72000602d00521600502d00502d00602a00521600502a00501a00602d", + "0x501200502d00602f00521600502f00501a00613b02f00c21600502d02a", + "0x521600513d00501a00601a13d00c21600501202f00c720006012005216", + "0x53c600613b00521600513b00572100600500521600500500514600613d", + "0x501a01e00c72200614100521600514100519f00614101d00c21600501d", + "0x14800572400614814603112521600514113b00513d01072300601a005216", + "0xc21600501a00572600600621600500601200603500572514a00521600c", + "0x614b03c00c21600503a00572600603a00521600514a005727006017039", + "0x600621600503f00542a00614e04003f03d01021600503c03900c125728", + "0x604715204415001021600514b01703d12572800600621600504000542a", + "0x21600504714e00c72900600621600515200542a00600621600504400542a", + "0x72a00615600521600515600572100603100521600503100501a006156005", + "0x4a00501e00615000521600515000519b00604a15e00c21600515603100c", + "0x521600515e00501a00600621600500601200604c00572b16200521600c", + "0x1256a500601000521600501000519f00601d00521600501d00519f00615e", + "0x605000572c16d00521600c04e0056a600604e16800c21600501001d15e", + "0x510052160050060bd00617000521600516d005234006006216005006012", + "0x617200521600505200572e00605200521600505101917016201072d006", + "0x515000519b00614600521600514600514600616800521600516800501a", + "0x601200617215014616801000517200521600517200572f006150005216", + "0x573000600621600501900517200600621600516200520a006006216005", + "0x521600514600514600616800521600516800501a006055005216005050", + "0x16801000505500521600505500572f00615000521600515000519b006146", + "0x1900517200600621600504c005051006006216005006012006055150146", + "0x620800600621600501d0050cc0060062160050100050cc006006216005", + "0x617400521600517400502d0061740052160050066fb006057005216005", + "0x5917800c13d00617800521600500613b00605900521600517405700c02f", + "0x15e00521600515e00501a00617a00521600505b00573000605b005216005", + "0x17a00572f00615000521600515000519b006146005216005146005146006", + "0x1900517200600621600500601200617a15014615e01000517a005216005", + "0x57310060062160050100050cc00600621600501d0050cc006006216005", + "0x3100521600503100501a00605d00521600503500573000600621600501a", + "0x5d00572f00600c00521600500c00519b006146005216005146005146006", + "0x2070050cc00600621600500601200605d00c14603101000505d005216005", + "0xc02f00600621600501e00573200600621600501d0050cc006006216005", + "0x52160050060bd0061810052160051250050c100617f005216005012019", + "0x18600521600518500572e00618500521600506117f01018101072d006061", + "0xc00519b00600500521600500500514600620800521600520800501a006", + "0x1200618600c00520801000518600521600518600572f00600c005216005", + "0xcc00600621600501d0050cc006006216005019005172006006216005006", + "0x600621600501e00573200600621600501200520a006006216005010005", + "0x21600502100501a00606500521600502500573000600621600512500520a", + "0x72f00600c00521600500c00519b006005005216005005005146006021005", + "0xc00521600c00600573300606500c005021010005065005216005065005", + "0xc00570500600621600500500520a006006216005006012006125005734", + "0x12005216005012005707006012005216005010005706006010005216005", + "0x50062080060062160051250050cc006006216005006012006012005005", + "0x601a00521600500613b00601900521600500501d00c02f00601d005216", + "0x1e90057070061e900521600501e00570800601e00521600501901a00c13d", + "0x521600500671a00601a0052160050060b20061e90050051e9005216005", + "0x21600500671a00620d00521600500673500602100521600500605b0061e9", + "0x52160050066c10060062160050060c400600621600500600c006026005", + "0x53c600620c00521600520c00519f00600600521600500600501a00620c", + "0x2820c00612571b00602800521600502800519f00602801d00c21600501d", + "0x500601200620700573620800521600c20a0056a600620a02a00c216005", + "0x71d00602d00521600502d00519f00602d005216005208005234006006216", + "0x2f02a12571e00613b12500c2160051250053c600602f00521600502d005", + "0x1410050cc00600621600500601200614603100c73714113d00c21600c13b", + "0x50690060062160051e900573200600621600501a005193006006216005", + "0x1200c21600501200510500613d00521600513d00501a006006216005021", + "0x602514a00c21600514813d00c72000614800521600514800502d006148", + "0x21600503903500c729006039005216005006739006035005216005006738", + "0x72100600500521600500500514600614a00521600514a00501a006017005", + "0x503a00519f00603a12500c2160051250053c6006017005216005017005", + "0x503a01700514a01072300602500521600502502600c72200603a005216", + "0x500601200604000573a03f00521600c03d00572400603d14b03c125216", + "0x673900615000521600500673800614e00521600503f005727006006216", + "0x521600503c00501a00615200521600504415000c729006044005216005", + "0x12573b00615200521600515200572100614e00521600514e00572100603c", + "0x615e00573c02300521600c15600572400615604700c21600515214e03c", + "0x521600514b00514600604700521600504700501a006006216005006012", + "0x19f00604a01d00c21600501d0053c600602500521600502500572100614b", + "0x14b04701073e00602300521600502320d00c73d00604a00521600504a005", + "0x616d00573f04e00521600c16800572400616804c16212521600504a025", + "0xc216005050005726006050005216005023005727006006216005006012", + "0x605517200c21600505200572600605200521600504e005727006051170", + "0x600621600505900542a00617805917405701021600517217000c125728", + "0x617f05d17a05b01021600505505105712572800600621600517800542a", + "0x521600516200501a00600621600517f00542a00600621600505d00542a", + "0x1256a500612500521600512500519f00601d00521600501d00519f006162", + "0x5b00519b00617400521600517400574000606118100c21600512501d162", + "0x18500521600c0610056a600617a00521600517a00574000605b005216005", + "0x1a00606500521600517a17400c729006006216005006012006186005741", + "0x506518100c72a006065005216005065005721006181005216005181005", + "0x506906b01201001074200606b00521600518500523400606906700c216", + "0x606700521600506700501a00606d00521600518a00574300618a005216", + "0x506d00574400605b00521600505b00519b00604c00521600504c005146", + "0x517400542a00600621600500601200606d05b04c06701000506d005216", + "0x17a00542a00600621600501000516800600621600501200520a006006216", + "0x618100521600518100501a00618d005216005186005745006006216005", + "0x518d00574400605b00521600505b00519b00604c00521600504c005146", + "0x501200520a00600621600500601200618d05b04c18101000518d005216", + "0x1250050cc00600621600501d0050cc006006216005010005168006006216", + "0x1a0060ba00521600516d005745006006216005023005746006006216005", + "0x21600500c00519b00604c00521600504c005146006162005216005162005", + "0x50060120060ba00c04c1620100050ba0052160050ba00574400600c005", + "0x1000516800600621600501200520a0060062160051250050cc006006216", + "0x574700600621600502500573100600621600501d0050cc006006216005", + "0x4700521600504700501a00607000521600515e00574500600621600520d", + "0x7000574400600c00521600500c00519b00614b00521600514b005146006", + "0x1250050cc00600621600500601200607000c14b047010005070005216005", + "0x516800600621600501200520a00600621600520d005747006006216005", + "0x74500600621600502500573100600621600501d0050cc006006216005010", + "0x21600514b00514600603c00521600503c00501a006196005216005040005", + "0x1000519600521600519600574400600c00521600500c00519b00614b005", + "0x57470060062160051460050cc00600621600500601200619600c14b03c", + "0x60720052160050100051e900600621600502600573200600621600520d", + "0xc21600c0720051b600603100521600503100501a0060062160050060b4", + "0x61a000521600519d0050b800600621600500601200607600574819d19a", + "0x7490050061b00061a20052160051a00051b20061a100521600519a0051b4", + "0x21600507b0051ac00607b0052160050060bd006006216005006012006006", + "0x550061a20052160051a40051b20061a10052160050760051b40061a4005", + "0xc1a20051ae00601900521600501901a00c0b60060190052160051a1005", + "0x600621600501d0050cc00600621600500601200607e00574a1a8005216", + "0x2160051250053c60060200052160051a80050bf00600621600501200520a", + "0x521600502002100c17a00608d08c00c2160051ab00574b0061ab12500c", + "0x6c100608f00521600508e0050c100608e02000c216005020005105006020", + "0x2160050910050cc00609209100c21600509000574b006090005216005006", + "0xc74c00609400521600509200568400609300521600508d005684006006", + "0x60062160050060c400600621600500601200600674d00621600c094093", + "0x509500502d00603100521600503100501a0060950052160050200050c1", + "0x1b500521600500673800601e09600c21600509503100c720006095005216", + "0x9600501a00609c00521600509b1b500c72900609b005216005006739006", + "0x9c00521600509c005721006005005216005005005146006096005216005", + "0x1072300601e00521600501e1e900c72200608c00521600508c00519f006", + "0x574e0a100521600c09700572400609709f05f12521600508c09c005096", + "0x2160050067380061be0052160050a10057270060062160050060120061bd", + "0x1a0060a80052160050a61c100c7290060a60052160050067390061c1005", + "0x2160050a80057210061be0052160051be00572100605f00521600505f005", + "0x521600c0aa0057240060aa1bc00c2160050a81be05f12573b0060a8005", + "0x60980a900c21600501e0057260060062160050060120060b000574f0ac", + "0xc1257280060b41b900c2160050b20057260060b20052160050ac005727", + "0x1b400542a0060062160050b800542a0061b40b81b60b60102160051b90a9", + "0xbd00542a0061ac0bd1b01b20102160050b40980b6125728006006216005", + "0x61ae0052160051b01b600c7290060062160051ac00542a006006216005", + "0x1ae1bc00c72a0061ae0052160051ae0057210061bc0052160051bc00501a", + "0x1aa0057430061aa0052160050c112508f0190107420060c10bf00c216005", + "0x9f00521600509f0051460060bf0052160050bf00501a0061a7005216005", + "0x9f0bf0100051a70052160051a70057440061b20052160051b200519b006", + "0x508f00520a0060062160050190051680060062160050060120061a71b2", + "0xb000574500600621600501e0057310060062160051250050cc006006216", + "0x9f00521600509f0051460061bc0052160051bc00501a0060c4005216005", + "0x9f1bc0100050c40052160050c400574400600c00521600500c00519b006", + "0x508f00520a0060062160050190051680060062160050060120060c400c", + "0x1bd00574500600621600501e0057310060062160051250050cc006006216", + "0x9f00521600509f00514600605f00521600505f00501a0060c6005216005", + "0x9f05f0100050c60052160050c600574400600c00521600500c00519b006", + "0x21600508c0050cc0060062160050060c40060062160050060120060c600c", + "0x508f00510500600621600502000520a0060062160051e9005732006006", + "0x50c812508f0190107420060c80052160051a50051640061a508f00c216", + "0x603100521600503100501a00619c0052160051a30057430061a3005216", + "0x519c00574400600c00521600500c00519b006005005216005005005146", + "0x2160050060c400600621600500601200619c00c00503101000519c005216", + "0x51e90057320060062160051250050cc00600621600507e005051006006", + "0xca0050fa0060ca0052160050060bd006006216005021005069006006216", + "0x519b00574300619b0052160050cc01d0120190107420060cc005216005", + "0x600500521600500500514600603100521600503100501a006199005216", + "0xc00503101000519900521600519900574400600c00521600500c00519b", + "0x21600520d0057470060062160051250050cc006006216005006012006199", + "0x501d0050cc00600621600501000516800600621600501200520a006006", + "0x1e900573200600621600501a005193006006216005026005732006006216", + "0x1a00619f005216005207005745006006216005021005069006006216005", + "0x21600500c00519b00600500521600500500514600602a00521600502a005", + "0x612571e00619f00c00502a01000519f00521600519f00574400600c005", + "0x523000600621600500601200601d01200c75001012500c21600c00c005", + "0x52160050190056ff00601a00521600512500501a006019005216005010", + "0x521600501d0057010060062160050060120060067510050061b000601e", + "0x675200601e0052160051e90056ff00601a00521600501200501a0061e9", + "0x521600502001e00c70300602000521600502000502d006020005216005", + "0x23400600621600500601200620d00575302300521600c0210056a6006021", + "0x216005026005706006026005216005025005705006025005216005023005", + "0xc00520c00521600520c00570700601a00521600501a00501a00620c005", + "0x1a00501a00602800521600520d00570800600621600500601200620c01a", + "0x600519f00602801a00c00502800521600502800570700601a005216005", + "0x1012575512500c00c21600c00500600c754006006005005006005216005", + "0x1912500c72900601900521600500673900600621600500601200601d012", + "0x1e900521600501a00572100601e00521600500c00501a00601a005216005", + "0x521600501201d00c7290060062160050060120060067560050061b0006", + "0x1e00c0051e900521600502000572100601e00521600501000501a006020", + "0x52160050067390060100052160050067570060062160050060c40061e9", + "0x14600600600521600500600501a00601d00521600501201000c729006012", + "0x21600512500519f00601d00521600501d005721006005005216005005005", + "0xc01e00572400601e01a01912521600512501d005006010758006125005", + "0x210052160051e90057270060062160050060120060200057591e9005216", + "0x2100572100600c00521600500c00572100601900521600501900501a006", + "0xc20d00572400620d02300c21600502100c01912575a006021005216005", + "0x20c00521600502500572700600621600500601200602600575b025005216", + "0x2300501a00602a00521600502800575d00602800521600520c00575c006", + "0x2a00521600502a00575e00601a00521600501a005146006023005216005", + "0x620a00521600502600575f00600621600500601200602a01a023125005", + "0x520a00575e00601a00521600501a00514600602300521600502300501a", + "0x21600500c00573100600621600500601200620a01a02312500520a005216", + "0x514600601900521600501900501a00620800521600502000575f006006", + "0x72600620801a01912500520800521600520800575e00601a00521600501a", + "0x21600501000576100601000521600500676000612500c00c216005005005", + "0x1200521600501200574000601d12500c21600512500576100601201000c", + "0x2160050060120061e901e00c76301a01900c21600c01d012006125762006", + "0x576400602102000c21600512500576400600621600501a00542a006006", + "0x521600502100576500600621600502300542a00620d02300c216005010", + "0xc76600601900521600501900501a00602600521600520d005765006025", + "0x521600500c00574000600621600500601200600676700621600c026025", + "0xc21600500c0057640060062160050060120060067680050061b000620c", + "0x42a00620720800c21600520a00576400620a00521600500673900602a028", + "0xc21600502d00576400602d00521600502a005765006006216005208005", + "0x576400613d00521600520700576500600621600502f00542a00613b02f", + "0x521600513b00576500600621600514100542a00603114100c21600513d", + "0x600676900621600c14814600c766006148005216005031005765006146", + "0x600621600502800542a00600621600502000542a006006216005006012", + "0x21600501900501a00603500521600514a0050fa00614a0052160050060bd", + "0x621600500601200603501900c0050350052160050350050e9006019005", + "0x500676a00603900521600502000523800620c005216005028005740006", + "0x3c00521600520c00523800603a00521600501703900c76b006017005216", + "0x516400614b00521600503c03a00c14d00603a00521600503a00502d006", + "0x521600503d0050e900601900521600501900501a00603d00521600514b", + "0x42a0060062160051e900542a00600621600500601200603d01900c00503d", + "0x600621600500c00542a00600621600501000542a006006216005125005", + "0x21600501e00501a00604000521600503f0050fa00603f0052160050060bd", + "0x21600500600501a00604001e00c0050400052160050400050e900601e005", + "0x76c00600c00521600500c005721006005005216005005005721006006005", + "0x1d00576e01200521600c01000576d00601012500c21600500c005006125", + "0x21600501900575d00601900521600501200575c006006216005006012006", + "0xc00501a00521600501a00575e00612500521600512500501a00601a005", + "0x21600500620800600621600501d00505100600621600500601200601a125", + "0xc02f0061e90052160051e900502d0061e900521600500676f00601e005", + "0x21600502002100c13d00602100521600500613b0060200052160051e901e", + "0x75e00612500521600512500501a00620d00521600502300575f006023005", + "0x50067570060062160050060c400620d12500c00520d00521600520d005", + "0x601d00521600501201000c729006012005216005006739006010005216", + "0x501d00572100600500521600500500514600600600521600500600501a", + "0x21600512501d00500601075800612500521600512500519f00601d005216", + "0x2160050060120060200057701e900521600c01e00572400601e01a019125", + "0x572100601900521600501900501a0060210052160051e9005727006006", + "0x502100c01912577100602100521600502100572100600c00521600500c", + "0x21600500601200602600577202500521600c20d00572400620d02300c216", + "0x575d00602800521600520c00575c00620c005216005025005727006006", + "0x521600501a00514600602300521600502300501a00602a005216005028", + "0x621600500601200602a01a02312500502a00521600502a00575e00601a", + "0x1a00514600602300521600502300501a00620a00521600502600575f006", + "0x601200620a01a02312500520a00521600520a00575e00601a005216005", + "0x1a00620800521600502000575f00600621600500c005731006006216005", + "0x21600520800575e00601a00521600501a005146006019005216005019005", + "0x100052160050060350060062160050060c400620801a019125005208005", + "0x77301d01200c21600c010005006125017006010005216005010005039006", + "0x3d80061e901e00c21600512500574b00600621600500601200601a01900c", + "0x2160050210050cc00602302100c21600502000574b006020005216005006", + "0x501a00602500521600502300568400620d0052160051e9005684006006", + "0x21600500601200600677400621600c02520d00c74c006012005216005012", + "0x574b0060280052160050066a400620c02600c21600501e00574b006006", + "0x521600520c00568400600621600502a0050cc00620a02a00c216005028", + "0x600677500621600c20720800c74c00620700521600520a005684006208", + "0xc21600500c00577600601200521600501200501a006006216005006012", + "0x602f00c00c21600500c00577600602d00521600502d00572100602d00c", + "0x72400613d13b00c21600502f02d01212575a00602f00521600502f005721", + "0x21600500677800600621600500601200603100577714100521600c13d005", + "0x614802600c2160050260053c600613b00521600513b00501a006146005", + "0x14813b12577900614600521600514600519f00614800521600514800519f", + "0x601200601700577a03900521600c0350056a600603514a00c216005146", + "0x603c00521600503900523400603a005216005141005727006006216005", + "0x503a00572100601d00521600501d00514600614a00521600514a00501a", + "0x21600503c03a01d14a01075800603c00521600503c00519f00603a005216", + "0x21600500601200614e00577b04000521600c03f00572400603f03d14b125", + "0x2600519f00614b00521600514b00501a006150005216005006778006006", + "0x21600515002614b12577c00615000521600515000519f006026005216005", + "0x621600500601200615600577d04700521600c1520056a600615204400c", + "0x4a00574b00604a00521600504700523400615e005216005040005727006", + "0x61680052160050066a40060062160051620050cc00604c16200c216005", + "0x504c00568400600621600504e0050cc00616d04e00c21600516800574b", + "0x77e00621600c17005000c74c00617000521600516d005684006050005216", + "0x21600515e00575c00600621600500c005731006006216005006012006006", + "0x14600604400521600504400501a00605200521600505100575d006051005", + "0x605203d04412500505200521600505200575e00603d00521600503d005", + "0x521600500c00572100604400521600504400501a006006216005006012", + "0x5517200c21600515e00c04412575a00615e00521600515e00572100600c", + "0x572700600621600500601200617400577f05700521600c055005724006", + "0x521600517800575d00617800521600505900575c006059005216005057", + "0x575e00603d00521600503d00514600617200521600517200501a00605b", + "0x17400575f00600621600500601200605b03d17212500505b00521600505b", + "0x3d00521600503d00514600617200521600517200501a00617a005216005", + "0x600621600500601200617a03d17212500517a00521600517a00575e006", + "0x521600515600575f00600621600500c005731006006216005040005746", + "0x575e00603d00521600503d00514600604400521600504400501a00605d", + "0xc00573100600621600500601200605d03d04412500505d00521600505d", + "0x1a00617f00521600514e00575f0060062160050260050cc006006216005", + "0x21600517f00575e00603d00521600503d00514600614b00521600514b005", + "0x621600500c00573100600621600500601200617f03d14b12500517f005", + "0x21600501700575f0060062160051410057460060062160050260050cc006", + "0x75e00601d00521600501d00514600614a00521600514a00501a006181005", + "0x573100600621600500601200618101d14a125005181005216005181005", + "0x606100521600503100575f0060062160050260050cc00600621600500c", + "0x506100575e00601d00521600501d00514600613b00521600513b00501a", + "0x2160050260050cc00600621600500601200606101d13b125005061005216", + "0x501a00618600521600518500575d00618500521600500c00575c006006", + "0x521600518600575e00601d00521600501d005146006012005216005012", + "0x600621600500c00573100600621600500601200618601d012125005186", + "0x6700521600500673900606500521600500673800600621600501e0050cc", + "0x575d00606b00521600506900575c00606900521600506706500c729006", + "0x521600501d00514600601200521600501200501a00618a00521600506b", + "0x621600500601200618a01d01212500518a00521600518a00575e00601d", + "0x52160050062080060062160051250050cc00600621600500c005731006", + "0x6d00c02f00618d00521600518d00502d00618d00521600500605900606d", + "0x52160050ba07000c13d00607000521600500613b0060ba00521600518d", + "0x514600601900521600501900501a00607200521600519600575f006196", + "0x1a00607201a01912500507200521600507200575e00601a00521600501a", + "0x21600500c005721006005005216005005005721006006005216005006005", + "0x521600c01000576d00601012500c21600500c00500612578000600c005", + "0x75d00601900521600501200575c00600621600500601200601d005781012", + "0x21600501a00575e00612500521600512500501a00601a005216005019005", + "0x600621600501d00505100600621600500601200601a12500c00501a005", + "0x52160051e900502d0061e900521600500678200601e005216005006208", + "0xc13d00602100521600500613b0060200052160051e901e00c02f0061e9", + "0x21600512500501a00620d00521600502300575f006023005216005020021", + "0x21600500600501a00620d12500c00520d00521600520d00575e006125005", + "0x78300600c00521600500c005721006005005216005005005721006006005", + "0x12f00601d01200c21600501000578400601012500c21600500c005006125", + "0x501900505100600621600500601200601a00578501900521600c01d005", + "0x1b00061e900521600501e00578700601e005216005012005786006006216", + "0x573100600621600501a005051006006216005006012006006788005006", + "0x60210052160050200057890060200052160050060bd006006216005012", + "0x51e900578700612500521600512500501a0061e9005216005021005787", + "0x500c00578a00600c00521600500c0057210061e912500c0051e9005216", + "0x621600500601200601200578c01000521600c12500578b006125005216", + "0x601a00578f01900521600c01d00578e00601d00521600501000578d006", + "0x1a0060210201e901e010216005019005006125790006006216005006012", + "0x502101e00c79200602100521600502100579100601e00521600501e005", + "0x250052160050201e900c79300600621600520d00505100620d02300c216", + "0x2600575c00600621600520c00573100620c02600c216005025005794006", + "0x2a00521600502a00575e00602a00521600502800575d006028005216005", + "0x573100600621600501a00505100600621600500601200602a02300c005", + "0x2d00620800521600500679500620a005216005006208006006216005005", + "0x21600500613b00620700521600520820a00c02f006208005216005208005", + "0x613b00521600502f00575f00602f00521600520702d00c13d00602d005", + "0x613b00600c00513b00521600513b00575e00600600521600500600501a", + "0x13d00521600501200575f006006216005005005731006006216005006012", + "0x13d00600c00513d00521600513d00575e00600600521600500600501a006", + "0x12500579700612500521600500c00579600600c00521600500c00519f006", + "0x521600501000579900600621600500601200601200579801000521600c", + "0x79c00600621600500601200601a00579b01900521600c01d00579a00601d", + "0x57050060062160050200050cc0060201e901e125216005019005006125", + "0x521600501e00501a0060230052160050210057060060210052160051e9", + "0x600621600500601200602301e00c00502300521600502300570700601e", + "0x20d0052160050062080060062160050050050cc00600621600501a005051", + "0x2520d00c02f00602500521600502500502d006025005216005006795006", + "0x2800521600502620c00c13d00620c00521600500613b006026005216005", + "0x2a00570700600600521600500600501a00602a005216005028005708006", + "0x2160050050050cc00600621600500601200602a00600c00502a005216005", + "0x570700600600521600500600501a00620a005216005012005708006006", + "0x579600600c00521600500c00519f00620a00600c00520a00521600520a", + "0x500601200601200579d01000521600c12500579700612500521600500c", + "0x579e01900521600c01d00579a00601d005216005010005799006006216", + "0x60201e901e12521600501900500612579c00600621600500601200601a", + "0x2160050210057060060210052160050200057050060062160051e90050cc", + "0xc00502300521600502300570700601e00521600501e00501a006023005", + "0x50050050cc00600621600501a00505100600621600500601200602301e", + "0x2500502d00602500521600500679500620d005216005006208006006216", + "0x20c00521600500613b00602600521600502520d00c02f006025005216005", + "0x501a00602a00521600502800570800602800521600502620c00c13d006", + "0x601200602a00600c00502a00521600502a005707006006005216005006", + "0x1a00620a0052160050120057080060062160050050050cc006006216005", + "0x1a00620a00600c00520a00521600520a005707006006005216005006005", + "0x21600500c005721006005005216005005005721006006005216005006005", + "0xc21600501000578400601012500c21600500c00500612579f00600c005", + "0x600621600500601200601a0057a001900521600c01d00512f00601d012", + "0x21600501e00578700601e005216005012005786006006216005019005051", + "0x21600501a0050510060062160050060120060067a10050061b00061e9005", + "0x50200057890060200052160050060bd006006216005012005731006006", + "0x612500521600512500501a0061e9005216005021005787006021005216", + "0x1012500c2160050050057260061e912500c0051e90052160051e9005787", + "0x1a01900c21600c01d01000612576200601d01200c21600500c005726006", + "0x53090060200052160050060bd0060062160050060120061e901e00c7a2", + "0x21600501900501a00602300521600502101a00c7a3006021005216005020", + "0x50060120060067a40050061b000602500521600502300523a00620d005", + "0xc7a300620c0052160050260052f90060260052160050060bd006006216", + "0x21600502800523a00620d00521600501e00501a00602800521600520c1e9", + "0xc21600c01212520d12576200620a02a00c2160050250057a5006025005", + "0x521600502a20700c72900600621600500601200602f02d00c7a6207208", + "0x7a800614100521600520800501a00613d00521600520a13b00c7a700613b", + "0x7380060062160050060120060067a90050061b000603100521600513d005", + "0xc14602a02d125762006146005216005146005740006146005216005006", + "0x514a02f00c72900600621600500601200603903500c7aa14a14800c216", + "0x3c00521600514800501a00603a00521600520a01700c7a7006017005216", + "0x62160050060120060067ab0050061b000614b00521600503a0057a8006", + "0x50060bd00603d00521600503902f00c72900600621600520a00519a006", + "0x14e00521600504003d00c7a700604000521600503f0052f900603f005216", + "0x3c00550100614b00521600514e0057a800603c00521600503500501a006", + "0x600572100603114100c00503100521600514b0057ac006141005216005", + "0xc00521600c0050057ae0060050052160050060057ad006006005216005", + "0x523900601000521600500c0057b00060062160050060120061250057af", + "0x521600501d0057b200601d0052160050120057b1006012005216005010", + "0x7b40060062160050060120060190050050190052160050190057b3006019", + "0xc7b500601a00500501a00521600501a0057b300601a005216005125005", + "0x521600500c00501a0061250052160050060bd00600c005216005005006", + "0x52160050060057b600600600521600500600519f00612500c00c00500c", + "0x7b90060062160050060120061250057b800c00521600c0050057b7006005", + "0x2160050120057bb0060120052160050100057ba00601000521600500c005", + "0x50050190052160050190057bd00601900521600501d0057bc00601d005", + "0x501a0057bd00601a0052160051250057be006006216005006012006019", + "0x500c00572600601012500c21600500500572600601a00500501a005216", + "0xc21600501200576100601912500c21600512500576100601d01200c216", + "0x521600500600501a0060201e901e12521600501a01900c7bf00601a012", + "0x602302100c21600502000600c792006020005216005020005791006006", + "0x20d12500c7bf00620d01d00c21600501d005761006006216005023005051", + "0x21600520c00579100602100521600502100501a00620c026025125216005", + "0x600621600502a00505100602a02800c21600520c02100c79200620c005", + "0x576100620720800c21600520a00523700620a00521600502602500c7c0", + "0x501a00613d13b02f12521600501202d00c7bf00602d01000c216005010", + "0x21600513d02800c79200613d00521600513d005791006028005216005028", + "0x614600521600513b02f00c7c000600621600503100505100603114100c", + "0x52370060350052160051e901e00c7c000614a14800c216005146005237", + "0x21600520700574000603900521600503900574000601703900c216005035", + "0x740006148005216005148005740006208005216005208005740006207005", + "0x2070391411257c100601700521600501700574000614a00521600514a005", + "0x520800576400600621600500601200603d14b00c7c203c03a00c21600c", + "0x76400614e00521600500673900600621600503f00542a00604003f00c216", + "0x21600504000576500600621600515000542a00604415000c21600514e005", + "0x76500600621600504700542a00615604700c216005152005764006152005", + "0x504a00542a00616204a00c21600515e00576400615e005216005044005", + "0x1a00616800521600516200576500604c005216005156005765006006216", + "0x50060120060067c300621600c16804c00c76600603a00521600503a005", + "0x14800542a00600621600501000542a00600621600501d00542a006006216", + "0x51480057640060062160050060120060067c40050061b0006006216005", + "0x76400605000521600500673900600621600504e00542a00616d04e00c216", + "0x21600516d00576500600621600517000542a00605117000c216005050005", + "0x76500600621600517200542a00605517200c216005052005764006052005", + "0x517400542a00605917400c216005057005764006057005216005051005", + "0x76600605b005216005059005765006178005216005055005765006006216", + "0x52160050060bd0060062160050060120060067c500621600c05b17800c", + "0x61b000617f00521600505d00519d00605d00521600517a00530900617a", + "0x1810052f90061810052160050060bd0060062160050060120060067c6005", + "0x18500521600517f00542200617f00521600506100519d006061005216005", + "0x60650057c718600521600c18500512f00618500521600518500519d006", + "0x6067005216005006739006006216005186005051006006216005006012", + "0xc7c806b06900c21600c01006703a125762006067005216005067005740", + "0x501d00542a00600621600506b00542a00600621600500601200606d18a", + "0x501a0060ba00521600518d00530900618d0052160050060bd006006216", + "0x60067c90050061b00061960052160050ba00519d006070005216005069", + "0x607200521600500673900600621600506d00542a006006216005006012", + "0xc7ca19d19a00c21600c01d07218a125762006072005216005072005740", + "0x2160050060bd00600621600519d00542a0060062160050060120061a0076", + "0x19d00607b00521600519a00501a0061a20052160051a10053090061a1005", + "0x42a0060062160050060120060067cb0050061b00061a40052160051a2005", + "0x7e0052160051a80052f90061a80052160050060bd0060062160051a0005", + "0x7b0055010061a400521600507e00519d00607b00521600507600501a006", + "0x1ab0052160050700055010061960052160051a40052ed006070005216005", + "0x62160050060120060067cc0050061b000608c0052160051960052ed006", + "0x21600501000542a00600621600501d00542a006006216005065005051006", + "0x3a00501a00608e00521600508d0052f900608d0052160050060bd006006", + "0x521600508c03c00c7a300608c00521600508e00519d0061ab005216005", + "0x61b000609100521600508f00523a0060900052160051ab00501a00608f", + "0x14800542a00600621600520800542a0060062160050060120060067cd005", + "0x60bd00600621600501000542a00600621600501d00542a006006216005", + "0x521600509303d00c7a30060930052160050920052f9006092005216005", + "0x57a500609100521600509400523a00609000521600514b00501a006094", + "0x9c00c7ce09b1b500c21600c14a0950901257c100609609500c216005091", + "0x1b500501a00609f00521600509609b00c7a300600621600500601200605f", + "0x120060067cf0050061b00060a100521600509f00523a006097005216005", + "0x2f90061bd0052160050060bd00600621600509600519a006006216005006", + "0x509c00501a0061c10052160051be05f00c7a30061be0052160051bd005", + "0xa80a600c2160050a10057a50060a10052160051c100523a006097005216", + "0x1a0060aa0052160050a81bc00c7a70061bc0052160050a601700c729006", + "0x7210060aa09700c0050aa0052160050aa0057a8006097005216005097005", + "0x21600c00500578e0060050052160050060057d0006006005216005006005", + "0x601000521600500c0057d20060062160050060120061250057d100c005", + "0x120060120050050120052160050120057d40060120052160050100057d3", + "0x7d500601d005216005006208006006216005125005051006006216005006", + "0x21600501901d00c02f00601900521600501900502d006019005216005006", + "0x7d60061e900521600501a01e00c13d00601e00521600500613b00601a005", + "0x519f0060200050050200052160050200057d40060200052160051e9005", + "0x521600c00500579a0060050052160050060057d7006006005216005006", + "0x7da00601000521600500c0057d90060062160050060120061250057d800c", + "0x60120060120050050120052160050120057db006012005216005010005", + "0x67dc00601d005216005006208006006216005125005051006006216005", + "0x521600501901d00c02f00601900521600501900502d006019005216005", + "0x57dd0061e900521600501a01e00c13d00601e00521600500613b00601a", + "0x60057de0060200050050200052160050200057db0060200052160051e9", + "0x600c0052160050060bd0060062160050060120060050057df00621600c", + "0x7e20050061b00060100052160051250057e100612500521600500c0057e0", + "0x50120057e1006012005216005005005239006006216005006012006006", + "0x50060120060050057e300621600c006005236006010005005010005216", + "0x57e500612500521600500c0057e400600c0052160050060bd006006216", + "0x57ba0060062160050060120060067e60050061b0006010005216005125", + "0x12600601214d0100050050100052160050120057e5006012005216005005", + "0x11c13712600601200601012500c00500613611c13712600601203911c137", + "0x1203911c13712600601218e01012500c00500613611c137126006012039", + "0x12600601203911c13712600601235d01012500c00500613611c137126006", + "0x11c13712600601203911c13712600601243f01012500c00500613611c137", + "0x613611c13712600601203911c13712600601254101012500c005006136", + "0xc00500613611c13712600601203911c13712600601263101012500c005", + "0x1012500c00500613611c13712600601203911c137126006012728010125", + "0x127e801012500c00500613611c13712600601203911c1371260060127e7", + "0x1260060127e901012500c00500613611c13712600601203911c137126006", + "0x11c1371260060127ea01012500c00500613611c13712600601203911c137", + "0x12601003911c1371260107eb01012500c00500613611c137126006012039", + "0x500613611c13712601003911c1371260107ec12500c00500613611c137", + "0x613611c1370f10ef12600601903911c1370f10ef1260060197ed12500c", + "0xf10ef12600601903911c1370f10ef1260060197ee01d01201012500c005", + "0x12601d03911c1370f10ef12601d7ef01d01201012500c00500613611c137", + "0x1d03911c1370f10ef12601d7f001201012500c00500613611c1370f10ef", + "0x1203911c1370f11260127f101201012500c00500613611c1370f10ef126", + "0x12600601203911c1371260060127f201012500c00500613611c1370f1126", + "0x613611c13712601003911c1371260107f301012500c00500613611c137", + "0x127f512500c00500613611c13712601003911c1371260107f412500c005", + "0x1260060127f601012500c00500613611c13712600601203911c137126006", + "0x3a03900c0390057f701012500c00500613611c13712600601203911c137", + "0x1012500c00500613611c00613712601201710111c00613712601d7f8006", + "0x391371261257fa12500c00500613e13712612501a0391371260107f9012", + "0x613712601203901710111c0061371260197fb00c005006142137126125", + "0x7fd00500614703912612503912600c7fc01d01201012500c00500614311c", + "0x1201012500c00500614911c00613712601201706510111c006137126019", + "0x1201706510111c0061371260197ff00500602501a00c01a02600c7fe01d", + "0x6510111c00613712601980001d01201012500c00500614311c006137126", + "0x11c00613712601980101d01201012500c00500614311c006137126012017", + "0x13712601980201d01201012500c00500614911c006137126012065017101", + "0x1980301d01201012500c00500614311c00613712601206501710111c006", + "0x1d01201012500c00500614311c00613712601206501710111c006137126", + "0x12601207b10111c00613712601d80500500614c03912612503912600c804", + "0x12601201710111c00613712601d80601201012500c00500614311c006137", + "0x80800500602501a00c01a07b00c80701201012500c00500614d11c006137", + "0x1201012500c00500614f11c00613712601207b01710111c006137126019", + "0x1207b06510111c00613712601980a00500602501a00c01a06500c80901d", + "0x12601010111c13712601080b01d01201012500c00500615111c006137126", + "0x80d00c00500615513712612503913712612580c12500c00500615311c137", + "0xc01712600c80e01012500c00500615711c13712503901a10111c137012", + "0x13712602181000c00500616113712612503913712612580f00500615f126", + "0x500614311c0f10060ef13712601904003901003901710111c0f10060ef", + "0x1710111c0f10060ef1371261e98110201e901e01a01901d01201012500c", + "0x1e01a01901d01201012500c00500614311c0f10060ef137126019040039", + "0x1003901710111c0f10ef13712602081300500616912600c01712600c812", + "0x1e901e01a01901d01201012500c00500613611c0f10ef13712601d040076", + "0x4007603903a01710111c0f10ef13712602081500616e03900c039005814", + "0x108161e901e01a01901d01201012500c00500616f11c0f10ef13712601d", + "0x1710111c0f113712601d81712500c00500613e13712612501a0e9137126", + "0x6510111c00613712601d81801201012500c00500613611c0f1137126012", + "0x12601010111c13712601081901201012500c00500614311c006137126012", + "0x500614d11c13712601010111c13712601081a12500c00500617311c137", + "0x12500c00500614311c00613712601207b10111c00613712601d81b12500c", + "0xc00500614311c00613712601207b06510111c00613712601981c012010", + "0x500617611c0061371260120170df11c00613712601d81d01d012010125", + "0x17911c00613712601210101a01701711c00613712601a81e01201012500c", + "0x500613e13712612501a03913712601081f01901d01201012500c005006", + "0xc82101012500c00500615513712612501701a03913712601282012500c", + "0x1371260120170170df11c00613712601982200500617311c13712511c137", + "0x13712601009010111c13712601282301d01201012500c00500618011c006", + "0x61371260120170df03911c00613712601982401012500c00500614311c", + "0x1371260121840db11c00613712601d82501d01201012500c00500618211c", + "0x1982700500602501a00c01a01700c82601201012500c00500614911c006", + "0x1d01201012500c00500618711c0061371260120261840db11c006137126", + "0x12601d82901012500c00500614311c13712601009510111c137126012828", + "0x12601982a01201012500c00500614911c0061371260121840d711c006137", + "0x82b01d01201012500c00500618911c0061371260120261840d711c006137", + "0x13712601982c01012500c00500614311c13712601009410111c137126012", + "0x12612582d01d01201012500c00500613611c13712601003901707b10111c", + "0x61371260120170cf11c00613712601d82e00c00500618e12600c076035", + "0x13712601207b0170cf11c00613712601982f01201012500c00500614d11c", + "0x13712601009110111c13712601283001d01201012500c00500619511c006", + "0x500614d11c1371260100a611c13712601083101012500c00500614311c", + "0x500615711c13712507b19c11c13701083300619b00519900583212500c", + "0x83501012500c00500614311c13712601008d10111c13712601283412500c", + "0x1201012500c00500615711c00613712601206501710111c006137126019", + "0x1083701012500c00500614311c13712601008e10111c13712601283601d", + "0x12600c07607612612583812500c0050061a311c1371260100c411c137126", + "0x583a12500c0050061a511c1371250760c411c13701083900c0050061a3", + "0x1012500c0050061aa13712612501703d03913712601283b0061570051a7", + "0xef1371261e983d12500c0050061760f11371260100390f113712601083c", + "0x1201012500c00500615711c0f10ef13712601d04003901001701711c0f1", + "0x12500c00500614311c13712601009210111c13712601283e01e01a01901d", + "0x12601d84001012500c0050061ac13712612501a04007613712601283f010", + "0x12601284101201012500c00500615711c0f113712601201701711c0f1137", + "0xf10ef1371261e984201012500c00500614311c13712601009310111c137", + "0x1d01201012500c00500613611c0f10ef13712601d04007601001701711c", + "0x761b201711c0f10ef13712601e8440061b003900c03900584301e01a019", + "0x12601084501a01901d01201012500c00500616f11c0f10ef13712601d040", + "0x121b201711c0f113712601d84612500c00500613e13712612501a055137", + "0x1371260100b411c13712601084701201012500c00500613611c0f1137126", + "0x12500c0050061b611c1371250670b411c13701084812500c0050061b411c", + "0x13700c84a01012500c00500614311c13712601009610111c137126012849", + "0x8f10111c13712601284c0061570051a700584b0050061b911c13712511c", + "0x1bc11c13712507b0a611c13701084d01012500c00500614311c137126010", + "0x10111c13712601284f00500617311c13712511c13700c84e12500c005006", + "0x61261250170df00612601085001012500c00500614311c13712601008c", + "0x61bd11c13712511c13700c8520061760051be00585112500c00500605d", + "0x50061ab13712612501a01a1b51371260128540061b5005090005853005", + "0x1a800585612500c00500605d0061261251840db00612601085501012500c", + "0x605d0061261251840d70061260108580061b5005095005857006149005", + "0x12601003901707b06711c13712601985a0061b500509400585912500c005", + "0x5d0061261250170cf00612601085b01d01201012500c00500613611c137", + "0x85e00614d0051a400585d00500614c12600c01712600c85c12500c005006", + "0x8e0058610061b500508d0058600061360051a200585f0061b5005091005", + "0x12601d8640050061a30050171a000c8630061a30051a10058620061b5005", + "0x1371261e986501201012500c00500619a0f113712601003919d1390f1137", + "0x1012500c00500615711c0f10ef13712601d04003901003907611c0f10ef", + "0x1701707611c0f11371260198670061b500509200586601e01a01901d012", + "0x8690061b500509300586801d01201012500c00500615711c0f1137126012", + "0x500613611c0f10ef13712601d04007601003907611c0f10ef1371261e9", + "0x1d04007603901711c0f10ef13712601e86a01e01a01901d01201012500c", + "0x5513712601086b01a01901d01201012500c00500619611c0f10ef137126", + "0x12601201701707611c0f113712601986c12500c00500613e13712612501a", + "0x1b201707611c0f113712601986d01d01201012500c00500618d11c0f1137", + "0x5d07611c13712601286e01d01201012500c00500613611c0f1137126012", + "0x11c1370128700061b400506900586f01012500c00500618a11c137126010", + "0xc8720061b500509600587101012500c0050061a711c13712506705d076", + "0x58750061b500508c0058740061b500508f0058730050061570050171a7", + "0x1a08d12587700c00500602501a01a12501a01a08c1258760061bd005185", + "0xc00500602501a01a12501a01a08e12587800c00500602501a01a12501a", + "0x12501a01a09013712601287a00c00500602501a01a12501a01a08f125879", + "0xc00500602501a01a12501a01a09112587b01012500c0050061ab137126", + "0x13712601287d01012500c0050061ab13712612501a01a09213712601287c", + "0x1a01a12501a01a09412587e01012500c0050061ab13712612501a01a093", + "0x1a09612588000c00500602501a01a12501a01a09512587f00c005006025", + "0xef13712601004003901a0ef13712601d88100c00500602501a01a12501a", + "0x11c13712601001005d07601a11c13712601988201201012500c00500617a", + "0x13712601201701707611c0f113712601988301d01201012500c005006178", + "0x13712601d88500603900501700588401d01201012500c00500614911c0f1", + "0x1701700c88601201012500c00500617611c0f113712601203907611c0f1", + "0x615711c0f113712601201703907611c0f1137126019887005006039005", + "0x7611c13712601a88900500603900501701700c88801d01201012500c005", + "0x12601d88a01901d01201012500c00500617811c13712601005d07601a010", + "0x1371261e988b01201012500c00500617a0ef13712601004003901a0ef137", + "0x1012500c00500617411c0f10ef13712601d07604001705103911c0f10ef", + "0x1711c0f11371261e988d00500603900501701700c88c01e01a01901d012", + "0x1a01901d01201012500c00500617911c0f113712601201a076017017017", + "0x500613e11c0f113712601201a01707601701711c0f113712601e88e01e", + "0x12601201707601a01701711c0f113712601e88f01a01901d01201012500c", + "0x14712600c01712600c89001a01901d01201012500c00500617911c0f1137", + "0xc00500616d0ef13712601001001704001a0390ef13712601a891005006", + "0x13712601a89300c00500616812600c01001012612589201901d012010125", + "0x89401901d01201012500c0050061620ef13712601001001703901a0400ef", + "0x1001a0100170100170ef13712601a89500c005006039005017017017125", + "0x500616800501715000c89601901d01201012500c0050061520ef137126", + "0x1201012500c00500614b0ef1371260100100170390100ef137126019897", + "0xc89a00601000501000589900c00500616812600c01001012612589801d", + "0xc00500614813712612501002013712601089b00500602012600c017126", + "0x500614812600c02002012612589d00500603a12600c02012600c89c125", + "0x2013712601089f12500c00500614813712612501002013712601089e00c", + "0xc00500614812600c0200201261258a012500c005006148137126125010", + "0x614812600c0200201261258a200c00500614112600c0200201261258a1", + "0x12600c0100101261258a400c00500616812600c0100101261258a300c005", + "0x200201261258a600c00500614112600c0200201261258a500c005006168", + "0x602512600c20712600c8a800613b0050200058a700c00500613d12600c", + "0x58ab00c00500613d12600c0200201261258aa00620a0050100058a9005", + "0x50100058ae00602d0050200058ad0061e90050100058ac00620d005020", + "0x8af006028" + ], + "sierra_program_debug_info": { + "type_names": [ + [0, "Pedersen"], + [1, "Uninitialized"], + [2, "u128"], + [3, "Tuple"], + [4, "u8"], + [5, "NonZero"], + [6, "Tuple>"], + [7, "core::panics::Panic"], + [8, "Array"], + [9, "Tuple>"], + [ + 10, + "core::panics::PanicResult::<(core::zeroable::NonZero::,)>" + ], + [11, "core::integer::u256"], + [12, "NonZero"], + [13, "Tuple>"], + [ + 14, + "core::panics::PanicResult::<(core::zeroable::NonZero::,)>" + ], + [15, "Unit"], + [16, "core::bool"], + [17, "Tuple"], + [18, "core::option::Option::>"], + [ + 19, + "Tuple>>" + ], + [ + 20, + "core::panics::PanicResult::<(core::option::Option::>,)>" + ], + [21, "Tuple"], + [22, "U128MulGuarantee"], + [ + 23, + "core::option::Option::>" + ], + [ + 24, + "Tuple>>" + ], + [ + 25, + "core::panics::PanicResult::<(core::option::Option::>,)>" + ], + [26, "Tuple"], + [27, "core::option::Option::"], + [28, "Tuple"], + [29, "Uninitialized>"], + [30, "core::panics::PanicResult::<(core::integer::u256,)>"], + [31, "Uninitialized"], + [32, "Snapshot>"], + [33, "core::array::Span::"], + [34, "felt252"], + [35, "core::option::Option::"], + [ + 36, + "Tuple, felt252, u8, core::option::Option::>" + ], + [ + 37, + "core::panics::PanicResult::<(core::array::Span::, core::felt252, core::integer::u8, core::option::Option::)>" + ], + [38, "Array"], + [39, "Snapshot>"], + [40, "core::array::Span::"], + [41, "Uninitialized>"], + [42, "core::result::Result::"], + [43, "Tuple, Unit>"], + [ + 44, + "core::panics::PanicResult::<(core::felt252, core::integer::u8, core::array::Array::, ())>" + ], + [45, "Box"], + [46, "core::option::Option::>"], + [47, "Uninitialized>>"], + [ + 48, + "Tuple, core::array::Span::, felt252, u8, Array, Unit>" + ], + [ + 49, + "core::panics::PanicResult::<(core::array::Span::, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>" + ], + [50, "Tuple"], + [51, "core::panics::PanicResult::<(core::integer::u8,)>"], + [ + 52, + "Tuple, core::array::Span::, felt252, u8, Array, Unit>" + ], + [ + 53, + "core::panics::PanicResult::<(core::array::Span::, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>" + ], + [54, "Box>"], + [ + 55, + "core::option::Option::>>" + ], + [56, "Array>"], + [57, "Snapshot>>"], + [58, "Uninitialized>>>"], + [59, "core::array::Span::>"], + [ + 60, + "Tuple, Array>, core::array::Span::>>" + ], + [ + 61, + "core::panics::PanicResult::<(core::array::Span::, core::array::Array::>, core::array::Span::>)>" + ], + [62, "Tuple, u8, Unit>"], + [ + 63, + "core::panics::PanicResult::<(core::array::Array::, core::integer::u8, ())>" + ], + [ + 64, + "Tuple, core::array::Span::, core::array::Span::, Unit>" + ], + [ + 65, + "core::panics::PanicResult::<(core::array::Array::, core::array::Span::, core::array::Span::, ())>" + ], + [66, "StorageBaseAddress"], + [67, "Uninitialized"], + [68, "core::poseidon::HashState"], + [69, "Box"], + [ + 70, + "core::result::Result::, core::array::Array::>" + ], + [71, "core::pedersen::HashState"], + [72, "ContractAddress"], + [73, "dojo::executor::IExecutorDispatcher"], + [ + 74, + "core::result::Result::>" + ], + [ + 75, + "Tuple>>" + ], + [ + 76, + "core::panics::PanicResult::<(core::result::Result::>,)>" + ], + [77, "Tuple>"], + [78, "core::panics::PanicResult::<(core::array::Array::,)>"], + [79, "dojo::database::index::WhereCondition"], + [80, "Tuple>>"], + [ + 81, + "core::panics::PanicResult::<(core::array::Span::>,)>" + ], + [82, "Tuple, felt252>"], + [ + 83, + "core::panics::PanicResult::<(core::array::Span::, core::felt252)>" + ], + [84, "Tuple"], + [85, "u32"], + [86, "core::result::Result::"], + [ + 87, + "core::result::Result::>" + ], + [ + 88, + "core::result::Result::, core::array::Array::>" + ], + [89, "ClassHash"], + [ + 90, + "core::result::Result::>" + ], + [ + 91, + "core::result::Result::>" + ], + [92, "Tuple, Array, Unit>"], + [ + 93, + "core::panics::PanicResult::<(core::array::Array::, core::array::Array::, ())>" + ], + [94, "dojo::world::world::WorldSpawned"], + [95, "dojo::world::world::ContractDeployed"], + [96, "dojo::world::world::ContractUpgraded"], + [97, "dojo::world::world::WorldUpgraded"], + [98, "dojo::world::world::MetadataUpdate"], + [99, "dojo::world::world::ModelRegistered"], + [100, "dojo::world::world::StoreSetRecord"], + [101, "dojo::world::world::StoreDelRecord"], + [102, "dojo::world::world::WriterUpdated"], + [103, "dojo::world::world::OwnerUpdated"], + [104, "dojo::world::world::ExecutorUpdated"], + [105, "dojo::world::world::Event"], + [106, "Box"], + [107, "Box"], + [108, "core::starknet::info::v2::ExecutionInfo"], + [109, "u64"], + [110, "core::starknet::info::BlockInfo"], + [111, "Tuple>"], + [ + 112, + "core::panics::PanicResult::<(core::box::Box::,)>" + ], + [ + 113, + "core::result::Result::>" + ], + [114, "StorageAddress"], + [115, "dojo::world::world::contract_base::ContractMemberState"], + [116, "Tuple"], + [ + 117, + "core::panics::PanicResult::<(dojo::world::world::contract_base::ContractMemberState, ())>" + ], + [118, "Array"], + [119, "Snapshot>"], + [120, "core::array::Span::"], + [121, "core::starknet::info::v2::ResourceBounds"], + [122, "core::starknet::info::v2::TxInfo"], + [123, "Tuple>"], + [ + 124, + "core::panics::PanicResult::<(core::box::Box::,)>" + ], + [125, "dojo::world::world::executor_dispatcher::ContractMemberState"], + [ + 126, + "Tuple" + ], + [ + 127, + "core::panics::PanicResult::<(dojo::world::world::executor_dispatcher::ContractMemberState, ())>" + ], + [128, "Tuple"], + [129, "core::panics::PanicResult::<(dojo::executor::IExecutorDispatcher,)>"], + [130, "core::option::Option::"], + [131, "core::option::Option::<()>"], + [132, "Tuple, u32, Unit>"], + [ + 133, + "core::panics::PanicResult::<(core::array::Array::, core::integer::u32, ())>" + ], + [134, "Uninitialized, u32, Unit>>"], + [135, "core::option::Option::>"], + [ + 136, + "Tuple, core::option::Option::>>" + ], + [ + 137, + "core::panics::PanicResult::<(core::array::Span::, core::option::Option::>)>" + ], + [138, "core::result::Result::<(), core::array::Array::>"], + [139, "dojo::world::world::nonce::ContractMemberState"], + [140, "Tuple"], + [ + 141, + "core::panics::PanicResult::<(dojo::world::world::nonce::ContractMemberState, ())>" + ], + [142, "Tuple"], + [143, "core::panics::PanicResult::<(core::integer::u32,)>"], + [144, "dojo::components::upgradeable::IUpgradeableDispatcher"], + [145, "Tuple>"], + [146, "Tuple>>"], + [ + 147, + "core::panics::PanicResult::<((core::starknet::contract_address::ContractAddress, core::array::Span::),)>" + ], + [ + 148, + "core::result::Result::<(core::starknet::contract_address::ContractAddress, core::array::Span::), core::array::Array::>" + ], + [149, "Uninitialized"], + [150, "dojo::world::world::models::ContractMemberState"], + [151, "Tuple"], + [ + 152, + "core::panics::PanicResult::<(dojo::world::world::models::ContractMemberState, ())>" + ], + [153, "NonZero"], + [154, "Box"], + [155, "Tuple>"], + [156, "core::panics::PanicResult::<(core::box::Box::<@core::felt252>,)>"], + [157, "Tuple"], + [158, "Uninitialized>"], + [159, "dojo::world::world::writers::ContractMemberState"], + [160, "Tuple"], + [ + 161, + "core::panics::PanicResult::<(dojo::world::world::writers::ContractMemberState, ())>" + ], + [162, "dojo::world::world::owners::ContractMemberState"], + [163, "Tuple"], + [ + 164, + "core::panics::PanicResult::<(dojo::world::world::owners::ContractMemberState, ())>" + ], + [165, "Tuple"], + [166, "dojo::world::world::metadata_uri::ContractMemberState"], + [ + 167, + "Tuple, dojo::world::world::metadata_uri::ContractMemberState, felt252, Unit>" + ], + [ + 168, + "core::panics::PanicResult::<(core::array::Span::, dojo::world::world::metadata_uri::ContractMemberState, core::felt252, ())>" + ], + [169, "Tuple"], + [ + 170, + "core::panics::PanicResult::<(dojo::world::world::metadata_uri::ContractMemberState, ())>" + ], + [171, "Tuple, felt252, Unit>"], + [ + 172, + "core::panics::PanicResult::<(core::array::Array::, core::felt252, ())>" + ], + [173, "Tuple"], + [174, "core::panics::PanicResult::<(core::felt252,)>"], + [ + 175, + "core::panics::PanicResult::<(core::starknet::contract_address::ContractAddress,)>" + ], + [ + 176, + "Tuple, core::array::Span::>>" + ], + [ + 177, + "Tuple, core::array::Span::>>>" + ], + [ + 178, + "core::panics::PanicResult::<((core::array::Span::, core::array::Span::>),)>" + ], + [179, "core::option::Option::>"], + [180, "Uninitialized>"], + [181, "core::option::Option::"], + [182, "Uninitialized"], + [183, "Bitwise"], + [184, "Uninitialized"], + [185, "Poseidon"], + [186, "Uninitialized"], + [187, "core::option::Option::>"], + [ + 188, + "Tuple, core::option::Option::>>" + ], + [ + 189, + "core::panics::PanicResult::<(core::array::Span::, core::option::Option::>)>" + ], + [190, "core::option::Option::"], + [191, "core::option::Option::>"], + [192, "Uninitialized"], + [193, "Uninitialized>"], + [194, "Tuple"], + [195, "core::panics::PanicResult::<((),)>"], + [196, "core::option::Option::>"], + [ + 197, + "Tuple, core::option::Option::>>" + ], + [ + 198, + "core::panics::PanicResult::<(core::array::Span::, core::option::Option::>)>" + ], + [199, "Uninitialized>"], + [200, "dojo::world::world::ContractState"], + [201, "Tuple"], + [ + 202, + "core::panics::PanicResult::<(dojo::world::world::ContractState, core::integer::u32)>" + ], + [203, "Tuple"], + [ + 204, + "core::panics::PanicResult::<(dojo::world::world::ContractState, core::starknet::class_hash::ClassHash)>" + ], + [205, "Tuple"], + [ + 206, + "core::panics::PanicResult::<(dojo::world::world::ContractState, core::starknet::contract_address::ContractAddress)>" + ], + [207, "Tuple"], + [ + 208, + "core::panics::PanicResult::<(core::starknet::class_hash::ClassHash,)>" + ], + [209, "core::option::Option::"], + [210, "Tuple"], + [211, "core::panics::PanicResult::<(core::bool,)>"], + [ + 212, + "core::option::Option::" + ], + [213, "Tuple"], + [214, "core::panics::PanicResult::<(dojo::world::world::ContractState, ())>"], + [215, "core::option::Option::>"], + [ + 216, + "Tuple, core::option::Option::>>" + ], + [ + 217, + "core::panics::PanicResult::<(core::array::Span::, core::option::Option::>)>" + ], + [218, "Uninitialized"], + [219, "Tuple, Unit>"], + [ + 220, + "core::panics::PanicResult::<(core::array::Array::, ())>" + ], + [221, "Tuple>"], + [222, "BuiltinCosts"], + [223, "System"], + [224, "core::panics::PanicResult::<(core::array::Span::,)>"], + [225, "GasBuiltin"], + [226, "RangeCheck"], + [227, "Uninitialized"] + ], + "libfunc_names": [ + [0, "alloc_local"], + [1, "alloc_local"], + [2, "finalize_locals"], + [3, "revoke_ap_tracking"], + [4, "withdraw_gas"], + [5, "branch_align"], + [6, "store_temp>"], + [7, "function_call"], + [8, "store_temp"], + [9, "enum_match>"], + [10, "struct_deconstruct>"], + [11, "array_len"], + [12, "snapshot_take"], + [13, "drop"], + [14, "u32_const<0>"], + [15, "rename"], + [16, "store_temp"], + [17, "u32_eq"], + [18, "drop>"], + [19, "drop>"], + [20, "drop"], + [21, "array_new"], + [ + 22, + "felt252_const<7733229381460288120802334208475838166080759535023995805565484692595>" + ], + [23, "store_temp"], + [24, "array_append"], + [25, "struct_construct"], + [26, "struct_construct>>"], + [ + 27, + "enum_init,)>, 1>" + ], + [28, "store_temp"], + [29, "store_temp"], + [30, "store_temp"], + [ + 31, + "store_temp,)>>" + ], + [32, "get_builtin_costs"], + [33, "store_temp"], + [34, "withdraw_gas_all"], + [ + 35, + "struct_construct" + ], + [ + 36, + "struct_construct" + ], + [37, "struct_construct"], + [ + 38, + "struct_construct" + ], + [39, "struct_construct"], + [40, "struct_construct"], + [41, "struct_construct"], + [42, "struct_construct"], + [43, "snapshot_take"], + [44, "drop"], + [45, "function_call"], + [46, "store_local"], + [47, "store_local"], + [ + 48, + "enum_match,)>>" + ], + [49, "struct_deconstruct>>"], + [50, "snapshot_take>"], + [51, "drop>"], + [52, "store_temp>"], + [ + 53, + "function_call::serialize>" + ], + [ + 54, + "enum_match, ())>>" + ], + [55, "struct_deconstruct, Unit>>"], + [56, "drop"], + [57, "snapshot_take>"], + [58, "drop>"], + [59, "struct_construct>"], + [60, "struct_construct>>"], + [ + 61, + "enum_init,)>, 0>" + ], + [62, "felt252_const<375233589013918064796019>"], + [ + 63, + "felt252_const<485748461484230571791265682659113160264223489397539653310998840191492913>" + ], + [64, "alloc_local"], + [65, "store_local"], + [ + 66, + "function_call::deserialize>" + ], + [ + 67, + "enum_match, core::option::Option::>)>>" + ], + [ + 68, + "struct_deconstruct, core::option::Option::>>>" + ], + [ + 69, + "enum_match>>" + ], + [70, "function_call"], + [ + 71, + "enum_match>" + ], + [72, "drop>"], + [ + 73, + "felt252_const<485748461484230571791265682659113160264223489397539653310998840191492914>" + ], + [74, "drop>"], + [ + 75, + "function_call" + ], + [ + 76, + "enum_match>" + ], + [77, "drop"], + [78, "store_temp"], + [79, "function_call"], + [80, "enum_match>"], + [81, "struct_deconstruct>"], + [82, "snapshot_take"], + [83, "drop"], + [84, "store_temp"], + [85, "function_call"], + [86, "function_call"], + [87, "function_call"], + [88, "function_call"], + [89, "function_call"], + [90, "function_call"], + [ + 91, + "function_call" + ], + [ + 92, + "enum_match>" + ], + [93, "drop"], + [94, "store_temp"], + [95, "function_call"], + [96, "function_call"], + [ + 97, + "enum_match>" + ], + [98, "struct_deconstruct>"], + [99, "snapshot_take"], + [ + 100, + "function_call" + ], + [101, "function_call"], + [ + 102, + "enum_match>" + ], + [ + 103, + "struct_deconstruct>" + ], + [104, "snapshot_take"], + [ + 105, + "function_call" + ], + [106, "function_call"], + [ + 107, + "enum_match>" + ], + [ + 108, + "struct_deconstruct>" + ], + [109, "function_call"], + [ + 110, + "enum_match>" + ], + [111, "struct_deconstruct>"], + [112, "u32_to_felt252"], + [113, "alloc_local>"], + [ + 114, + "function_call::deserialize>" + ], + [ + 115, + "enum_match, core::option::Option::>)>>" + ], + [ + 116, + "struct_deconstruct, core::option::Option::>>>" + ], + [ + 117, + "enum_match>>" + ], + [118, "store_local>"], + [119, "function_call"], + [120, "enum_match>"], + [121, "drop>"], + [122, "drop>>"], + [123, "alloc_local>"], + [124, "alloc_local"], + [125, "enable_ap_tracking"], + [126, "store_local>"], + [127, "array_snapshot_pop_front"], + [ + 128, + "enum_init>, 0>" + ], + [129, "store_temp>>"], + [130, "store_temp>>"], + [131, "jump"], + [132, "struct_construct"], + [ + 133, + "enum_init>, 1>" + ], + [134, "enum_match>>"], + [135, "unbox"], + [136, "rename"], + [137, "function_call"], + [138, "enum_match>"], + [139, "disable_ap_tracking"], + [140, "store_local"], + [ + 141, + "function_call, core::integer::u8Drop>::deserialize>" + ], + [ + 142, + "enum_match, core::option::Option::>)>>" + ], + [ + 143, + "struct_deconstruct, core::option::Option::>>>" + ], + [ + 144, + "enum_match>>" + ], + [145, "drop>"], + [146, "drop"], + [147, "store_temp"], + [148, "store_temp"], + [149, "store_temp"], + [150, "store_temp>"], + [151, "function_call"], + [ + 152, + "felt252_const<485748461484230571791265682659113160264223489397539653310998840191492917>" + ], + [153, "drop>>"], + [ + 154, + "felt252_const<485748461484230571791265682659113160264223489397539653310998840191492916>" + ], + [155, "drop>>"], + [156, "drop>"], + [ + 157, + "felt252_const<485748461484230571791265682659113160264223489397539653310998840191492915>" + ], + [158, "function_call"], + [159, "alloc_local"], + [160, "alloc_local"], + [161, "alloc_local"], + [162, "function_call"], + [163, "enum_match>"], + [164, "store_local"], + [165, "drop>"], + [166, "drop>"], + [167, "function_call"], + [168, "store_local"], + [169, "store_local"], + [170, "drop>"], + [171, "alloc_local>"], + [ + 172, + "function_call>::deserialize>" + ], + [ + 173, + "enum_match>>" + ], + [174, "store_local>"], + [175, "drop>"], + [176, "store_temp>"], + [177, "function_call"], + [ + 178, + "enum_match, core::array::Span::>),)>>" + ], + [ + 179, + "struct_deconstruct, core::array::Span::>>>>" + ], + [ + 180, + "snapshot_take, core::array::Span::>>>" + ], + [ + 181, + "drop, core::array::Span::>>>" + ], + [ + 182, + "store_temp, core::array::Span::>>>" + ], + [ + 183, + "function_call, core::array::Span::>, core::array::SpanSerde::, core::array::SpanDrop::, core::array::SpanSerde::, core::array::SpanSerde::, core::array::SpanDrop::>, core::array::SpanDrop::>>::serialize>" + ], + [184, "drop>>"], + [185, "function_call"], + [186, "function_call"], + [187, "function_call"], + [ + 188, + "enum_match>" + ], + [189, "struct_deconstruct>"], + [190, "function_call"], + [191, "function_call"], + [192, "function_call"], + [193, "enum_init, 0>"], + [194, "enum_init, 1>"], + [195, "dup"], + [196, "struct_deconstruct"], + [197, "drop"], + [198, "drop"], + [199, "drop"], + [200, "drop"], + [201, "drop"], + [202, "drop"], + [203, "dup"], + [ + 204, + "function_call" + ], + [205, "enum_match>"], + [206, "struct_deconstruct>"], + [207, "felt252_const<1>"], + [208, "felt252_add"], + [209, "function_call"], + [ + 210, + "enum_match, core::felt252, ())>>" + ], + [211, "struct_deconstruct, felt252, Unit>>"], + [212, "dup>"], + [213, "rename>"], + [ + 214, + "function_call>" + ], + [215, "struct_construct, Unit>>"], + [ + 216, + "enum_init, ())>, 0>" + ], + [ + 217, + "store_temp, ())>>" + ], + [ + 218, + "enum_init, ())>, 1>" + ], + [ + 219, + "function_call>" + ], + [ + 220, + "enum_init>, 0>" + ], + [ + 221, + "struct_construct, core::option::Option::>>>" + ], + [ + 222, + "enum_init, core::option::Option::>)>, 0>" + ], + [ + 223, + "store_temp, core::option::Option::>)>>" + ], + [ + 224, + "enum_init>, 1>" + ], + [ + 225, + "enum_init, core::option::Option::>)>, 1>" + ], + [226, "function_call"], + [227, "enum_match"], + [228, "felt252_const<2037172927806676297074>"], + [ + 229, + "enum_init, 1>" + ], + [ + 230, + "store_temp>" + ], + [231, "u32_const<255>"], + [232, "dup"], + [233, "u32_overflowing_sub"], + [234, "felt252_const<37225634303738867606330471607143733620327>"], + [ + 235, + "function_call" + ], + [ + 236, + "enum_match>" + ], + [ + 237, + "struct_deconstruct>" + ], + [238, "struct_construct"], + [239, "store_temp"], + [ + 240, + "function_call>" + ], + [241, "struct_deconstruct>"], + [ + 242, + "function_call" + ], + [ + 243, + "enum_match, dojo::world::world::metadata_uri::ContractMemberState, core::felt252, ())>>" + ], + [ + 244, + "struct_deconstruct, dojo::world::world::metadata_uri::ContractMemberState, felt252, Unit>>" + ], + [245, "struct_construct>"], + [ + 246, + "enum_init, 0>" + ], + [247, "contract_address_try_from_felt252"], + [ + 248, + "enum_init, 0>" + ], + [ + 249, + "store_temp>" + ], + [ + 250, + "enum_init, 1>" + ], + [251, "drop"], + [252, "struct_construct>"], + [253, "store_temp>"], + [ + 254, + "function_call" + ], + [255, "struct_construct>"], + [256, "enum_init, 0>"], + [257, "store_temp>"], + [258, "enum_init, 1>"], + [259, "rename"], + [260, "felt252_const<0>"], + [261, "snapshot_take"], + [262, "function_call"], + [263, "dup"], + [264, "rename"], + [265, "enum_init"], + [ + 266, + "function_call" + ], + [ + 267, + "enum_match>" + ], + [ + 268, + "struct_deconstruct>" + ], + [269, "struct_construct"], + [270, "store_temp"], + [ + 271, + "function_call>" + ], + [272, "enum_init"], + [ + 273, + "function_call" + ], + [274, "felt252_const<2462790951445419931459807638768526940903335282>"], + [ + 275, + "function_call" + ], + [ + 276, + "enum_match>" + ], + [ + 277, + "struct_deconstruct>" + ], + [278, "struct_construct"], + [279, "store_temp"], + [ + 280, + "function_call>" + ], + [281, "class_hash_try_from_felt252"], + [ + 282, + "enum_init, 0>" + ], + [ + 283, + "store_temp>" + ], + [ + 284, + "enum_init, 1>" + ], + [285, "alloc_local>"], + [ + 286, + "felt252_const<1528802474226268325865027367859591458315299653151958663884057507666229546336>" + ], + [287, "dup"], + [288, "store_local>"], + [289, "function_call"], + [290, "rename>>"], + [291, "function_call>"], + [ + 292, + "enum_match,)>>" + ], + [293, "struct_deconstruct>>"], + [294, "class_hash_const<0>"], + [295, "snapshot_take"], + [ + 296, + "function_call" + ], + [297, "rename"], + [298, "class_hash_to_felt252"], + [299, "felt252_sub"], + [300, "felt252_is_zero"], + [301, "drop>"], + [302, "felt252_const<162857089760626085489994535756020441209910069392485>"], + [ + 303, + "function_call" + ], + [ + 304, + "enum_match>" + ], + [ + 305, + "struct_deconstruct>" + ], + [306, "struct_construct"], + [307, "store_temp"], + [ + 308, + "function_call>" + ], + [309, "drop>"], + [310, "drop>>"], + [311, "struct_construct>"], + [ + 312, + "enum_init, 0>" + ], + [ + 313, + "store_temp>" + ], + [ + 314, + "enum_init, 1>" + ], + [315, "alloc_local"], + [ + 316, + "snapshot_take" + ], + [ + 317, + "function_call" + ], + [318, "deploy_syscall"], + [ + 319, + "struct_construct>>" + ], + [ + 320, + "enum_init), core::array::Array::>, 0>" + ], + [ + 321, + "store_temp), core::array::Array::>>" + ], + [ + 322, + "enum_init), core::array::Array::>, 1>" + ], + [ + 323, + "function_call)>::unwrap_syscall>" + ], + [ + 324, + "enum_match),)>>" + ], + [ + 325, + "struct_deconstruct>>>" + ], + [ + 326, + "struct_deconstruct>>" + ], + [327, "store_local"], + [ + 328, + "struct_construct" + ], + [329, "store_temp"], + [ + 330, + "function_call" + ], + [331, "contract_address_to_felt252"], + [332, "struct_construct"], + [333, "store_temp"], + [ + 334, + "function_call>" + ], + [ + 335, + "struct_construct>" + ], + [ + 336, + "enum_init, 0>" + ], + [ + 337, + "store_temp>" + ], + [ + 338, + "enum_init, 1>" + ], + [339, "drop>"], + [340, "rename"], + [341, "function_call"], + [342, "struct_construct"], + [343, "store_temp"], + [ + 344, + "function_call>" + ], + [ + 345, + "struct_construct>" + ], + [ + 346, + "enum_init, 0>" + ], + [ + 347, + "store_temp>" + ], + [ + 348, + "enum_init, 1>" + ], + [349, "snapshot_take"], + [ + 350, + "function_call" + ], + [351, "enum_match>"], + [352, "struct_deconstruct>"], + [353, "u32_const<1>"], + [354, "function_call"], + [ + 355, + "function_call" + ], + [ + 356, + "enum_match>" + ], + [ + 357, + "struct_deconstruct>" + ], + [358, "struct_construct>"], + [ + 359, + "enum_init, 0>" + ], + [ + 360, + "store_temp>" + ], + [ + 361, + "enum_init, 1>" + ], + [ + 362, + "struct_construct, core::option::Option::>>>" + ], + [ + 363, + "enum_init, core::option::Option::>)>, 0>" + ], + [ + 364, + "store_temp, core::option::Option::>)>>" + ], + [ + 365, + "enum_init, core::option::Option::>)>, 1>" + ], + [ + 366, + "enum_init>, 1>" + ], + [367, "emit_event_syscall"], + [ + 368, + "enum_init>, 0>" + ], + [ + 369, + "store_temp>>" + ], + [ + 370, + "enum_init>, 1>" + ], + [ + 371, + "function_call::unwrap_syscall>" + ], + [372, "struct_construct>"], + [373, "enum_init, 0>"], + [374, "store_temp>"], + [375, "enum_init, 1>"], + [376, "u8_try_from_felt252"], + [377, "enum_init, 0>"], + [378, "store_temp>"], + [379, "enum_init, 1>"], + [380, "array_new"], + [381, "store_temp>"], + [ + 382, + "function_call, core::integer::u8Drop>>" + ], + [ + 383, + "enum_match, core::option::Option::>)>>" + ], + [ + 384, + "struct_deconstruct, core::option::Option::>>>" + ], + [ + 385, + "enum_match>>" + ], + [386, "snapshot_take>"], + [387, "drop>"], + [388, "struct_construct>"], + [ + 389, + "enum_init>, 0>" + ], + [ + 390, + "struct_construct, core::option::Option::>>>" + ], + [ + 391, + "enum_init, core::option::Option::>)>, 0>" + ], + [ + 392, + "store_temp, core::option::Option::>)>>" + ], + [ + 393, + "enum_init>, 1>" + ], + [ + 394, + "enum_init, core::option::Option::>)>, 1>" + ], + [395, "function_call"], + [396, "dup"], + [397, "function_call"], + [398, "struct_construct"], + [399, "store_temp"], + [ + 400, + "function_call>" + ], + [401, "alloc_local, u32, Unit>>"], + [402, "drop>"], + [403, "dup>"], + [404, "function_call"], + [ + 405, + "enum_match, core::integer::u32, ())>>" + ], + [406, "store_local, u32, Unit>>"], + [407, "struct_deconstruct, u32, Unit>>"], + [408, "u8_const<0>"], + [409, "function_call"], + [410, "struct_construct"], + [411, "store_temp"], + [ + 412, + "function_call>" + ], + [413, "drop, u32, Unit>>"], + [414, "drop, u32, Unit>>>"], + [415, "u32_try_from_felt252"], + [416, "enum_init, 0>"], + [417, "store_temp>"], + [418, "enum_init, 1>"], + [419, "function_call"], + [ + 420, + "enum_init>, 1>" + ], + [ + 421, + "store_temp>>" + ], + [422, "function_call"], + [423, "enum_match>"], + [ + 424, + "enum_init>, 0>" + ], + [ + 425, + "felt252_const<130898889959276654847086743802419577788145926319445857429975148>" + ], + [ + 426, + "enum_init, core::array::Span::>),)>, 1>" + ], + [ + 427, + "store_temp, core::array::Span::>),)>>" + ], + [ + 428, + "enum_init, 1>" + ], + [ + 429, + "store_temp>" + ], + [430, "function_call"], + [ + 431, + "struct_construct, core::array::Span::>>>>" + ], + [ + 432, + "enum_init, core::array::Span::>),)>, 0>" + ], + [ + 433, + "struct_deconstruct, core::array::Span::>>>" + ], + [434, "store_temp>>"], + [ + 435, + "function_call, core::array::SpanSerde::, core::array::SpanDrop::>::serialize>" + ], + [436, "drop>>"], + [437, "function_call"], + [ + 438, + "felt252_const<45840195547537441139197651602034552346667116566969405886212370290>" + ], + [ + 439, + "snapshot_take" + ], + [ + 440, + "function_call" + ], + [ + 441, + "enum_match>" + ], + [442, "struct_construct"], + [443, "store_temp"], + [ + 444, + "function_call" + ], + [ + 445, + "enum_match>" + ], + [446, "struct_deconstruct>"], + [447, "struct_deconstruct"], + [ + 448, + "struct_deconstruct>" + ], + [449, "struct_construct"], + [450, "store_temp"], + [ + 451, + "function_call>" + ], + [452, "drop>"], + [453, "struct_construct>"], + [ + 454, + "enum_init, 0>" + ], + [ + 455, + "store_temp>" + ], + [ + 456, + "enum_init, 1>" + ], + [457, "bool_not_impl"], + [458, "felt252_const<9184378437951277208742263346930465483354984>"], + [459, "function_call"], + [ + 460, + "enum_match,)>>" + ], + [461, "struct_deconstruct>>"], + [462, "unbox"], + [463, "struct_deconstruct"], + [464, "drop"], + [465, "drop>"], + [466, "felt252_const<41691414978720277885438601153541232949736990933345381>"], + [467, "replace_class_syscall"], + [ + 468, + "function_call>::unwrap::>>" + ], + [469, "struct_construct"], + [470, "store_temp"], + [ + 471, + "function_call>" + ], + [472, "store_temp"], + [ + 473, + "function_call" + ], + [ + 474, + "enum_match>" + ], + [475, "function_call"], + [ + 476, + "struct_deconstruct>" + ], + [477, "struct_construct"], + [478, "store_temp"], + [ + 479, + "function_call>" + ], + [ + 480, + "drop>" + ], + [ + 481, + "drop>" + ], + [482, "drop>"], + [483, "drop"], + [ + 484, + "function_call" + ], + [485, "storage_address_from_base"], + [486, "storage_read_syscall"], + [ + 487, + "enum_init>, 0>" + ], + [ + 488, + "store_temp>>" + ], + [ + 489, + "enum_init>, 1>" + ], + [ + 490, + "function_call::unwrap_syscall>" + ], + [491, "struct_construct>"], + [492, "enum_init, 0>"], + [493, "store_temp>"], + [494, "enum_init, 1>"], + [495, "struct_construct, felt252, Unit>>"], + [ + 496, + "enum_init, core::felt252, ())>, 0>" + ], + [ + 497, + "store_temp, core::felt252, ())>>" + ], + [ + 498, + "enum_init, core::felt252, ())>, 1>" + ], + [ + 499, + "enum_init>, 0>" + ], + [500, "function_call"], + [ + 501, + "enum_match,)>>" + ], + [ + 502, + "struct_deconstruct>>" + ], + [503, "unbox"], + [504, "struct_deconstruct"], + [505, "drop>"], + [506, "drop>"], + [507, "snapshot_take"], + [508, "storage_write_syscall"], + [509, "struct_deconstruct>"], + [ + 510, + "struct_construct>" + ], + [ + 511, + "enum_init, 0>" + ], + [ + 512, + "store_temp>" + ], + [ + 513, + "enum_init, 1>" + ], + [ + 514, + "function_call" + ], + [515, "snapshot_take"], + [516, "drop"], + [517, "store_temp"], + [ + 518, + "function_call" + ], + [ + 519, + "enum_match, core::array::Array::, ())>>" + ], + [520, "struct_deconstruct, Array, Unit>>"], + [ + 521, + "struct_construct, dojo::world::world::metadata_uri::ContractMemberState, felt252, Unit>>" + ], + [ + 522, + "enum_init, dojo::world::world::metadata_uri::ContractMemberState, core::felt252, ())>, 0>" + ], + [ + 523, + "store_temp, dojo::world::world::metadata_uri::ContractMemberState, core::felt252, ())>>" + ], + [ + 524, + "enum_init, dojo::world::world::metadata_uri::ContractMemberState, core::felt252, ())>, 1>" + ], + [ + 525, + "function_call" + ], + [ + 526, + "enum_init>, 0>" + ], + [ + 527, + "store_temp>>" + ], + [ + 528, + "enum_init>, 1>" + ], + [ + 529, + "function_call::unwrap_syscall>" + ], + [530, "snapshot_take"], + [531, "bool_to_felt252"], + [ + 532, + "struct_construct>" + ], + [ + 533, + "enum_init, 0>" + ], + [ + 534, + "store_temp>" + ], + [ + 535, + "enum_init, 1>" + ], + [ + 536, + "function_call" + ], + [ + 537, + "function_call" + ], + [538, "snapshot_take"], + [ + 539, + "struct_construct>" + ], + [ + 540, + "enum_init, 0>" + ], + [ + 541, + "store_temp>" + ], + [ + 542, + "enum_init, 1>" + ], + [ + 543, + "function_call" + ], + [544, "function_call"], + [545, "array_get"], + [546, "struct_construct>>"], + [ + 547, + "enum_init,)>, 0>" + ], + [ + 548, + "store_temp,)>>" + ], + [549, "felt252_const<1637570914057682275393755530660268060279989363>"], + [ + 550, + "enum_init,)>, 1>" + ], + [ + 551, + "function_call" + ], + [ + 552, + "function_call" + ], + [ + 553, + "enum_init>, 0>" + ], + [ + 554, + "store_temp>>" + ], + [555, "felt252_const<6214282646402414199069093229416>"], + [ + 556, + "enum_init>, 1>" + ], + [ + 557, + "function_call::unwrap_syscall>" + ], + [ + 558, + "struct_construct>" + ], + [ + 559, + "enum_init, 0>" + ], + [ + 560, + "store_temp>" + ], + [ + 561, + "enum_init, 1>" + ], + [ + 562, + "function_call" + ], + [ + 563, + "storage_base_address_const<1549662586524698628134511494108424664120874428270242005980325345328461123324>" + ], + [564, "store_temp"], + [ + 565, + "enum_match), core::array::Array::>>" + ], + [ + 566, + "struct_construct>>>" + ], + [ + 567, + "enum_init),)>, 0>" + ], + [ + 568, + "store_temp),)>>" + ], + [ + 569, + "enum_init),)>, 1>" + ], + [ + 570, + "struct_deconstruct" + ], + [ + 571, + "felt252_const<429286934060636239444256046255241512105662385954349596568652644383873724621>" + ], + [572, "call_contract_syscall"], + [ + 573, + "enum_init, core::array::Array::>, 0>" + ], + [ + 574, + "store_temp, core::array::Array::>>" + ], + [ + 575, + "enum_init, core::array::Array::>, 1>" + ], + [ + 576, + "function_call>::unwrap_syscall>" + ], + [577, "drop>>"], + [ + 578, + "function_call" + ], + [579, "felt252_const<521516269527283667330418>"], + [580, "rename"], + [581, "rename"], + [582, "rename"], + [583, "rename"], + [ + 584, + "function_call" + ], + [ + 585, + "storage_base_address_const<1217961213749390912347430045896694622051130986551650888538812188143166297945>" + ], + [ + 586, + "enum_init>, 0>" + ], + [ + 587, + "store_temp>>" + ], + [588, "felt252_const<7269940625183576940180048306939577043858226>"], + [589, "enum_init, 1>"], + [590, "store_temp>"], + [ + 591, + "enum_init>, 1>" + ], + [ + 592, + "function_call::unwrap_syscall>" + ], + [593, "struct_construct>"], + [594, "enum_init, 0>"], + [595, "u32_overflowing_add"], + [ + 596, + "enum_init, 0>" + ], + [ + 597, + "store_temp>" + ], + [ + 598, + "enum_init, 1>" + ], + [599, "felt252_const<155785504323917466144735657540098748279>"], + [ + 600, + "function_call::expect::>" + ], + [ + 601, + "struct_construct>" + ], + [ + 602, + "enum_init, 0>" + ], + [ + 603, + "store_temp>" + ], + [ + 604, + "enum_init, 1>" + ], + [ + 605, + "enum_match>>" + ], + [ + 606, + "enum_init>, 0>" + ], + [ + 607, + "struct_construct, core::option::Option::>>>" + ], + [ + 608, + "enum_init, core::option::Option::>)>, 0>" + ], + [ + 609, + "store_temp, core::option::Option::>)>>" + ], + [610, "array_append"], + [ + 611, + "enum_init, core::option::Option::>)>, 1>" + ], + [612, "rename"], + [ + 613, + "enum_init>, 1>" + ], + [614, "struct_construct>"], + [615, "store_temp>"], + [616, "function_call"], + [ + 617, + "enum_match, core::felt252)>>" + ], + [ + 618, + "struct_deconstruct, felt252>>" + ], + [619, "felt252_const<31083194373425476152957429605>"], + [620, "function_call"], + [ + 621, + "function_call" + ], + [622, "struct_deconstruct>"], + [623, "array_len"], + [624, "struct_construct, u32, Unit>>"], + [ + 625, + "enum_init, core::integer::u32, ())>, 0>" + ], + [ + 626, + "store_temp, core::integer::u32, ())>>" + ], + [ + 627, + "enum_init, core::integer::u32, ())>, 1>" + ], + [628, "function_call"], + [ + 629, + "function_call" + ], + [630, "function_call"], + [631, "enum_init, 0>"], + [632, "store_temp>"], + [633, "function_call"], + [ + 634, + "enum_match>,)>>" + ], + [ + 635, + "struct_deconstruct>>>" + ], + [ + 636, + "struct_construct, core::array::Span::>>>" + ], + [637, "dup>>"], + [638, "rename>>"], + [ + 639, + "struct_deconstruct>>" + ], + [640, "array_len>"], + [ + 641, + "function_call, core::array::SpanSerde::, core::array::SpanDrop::>>" + ], + [ + 642, + "enum_match>" + ], + [643, "struct_deconstruct"], + [644, "function_call"], + [ + 645, + "enum_match,)>>" + ], + [646, "struct_deconstruct>>"], + [647, "function_call"], + [ + 648, + "storage_base_address_const<850991909592324662282938535474023562280137231324620006946839595634568396994>" + ], + [649, "store_temp"], + [650, "function_call"], + [ + 651, + "enum_match>,)>>" + ], + [ + 652, + "struct_deconstruct>>>" + ], + [ + 653, + "store_temp>>" + ], + [ + 654, + "function_call::unwrap_syscall>" + ], + [655, "struct_construct>"], + [ + 656, + "enum_init, 0>" + ], + [ + 657, + "store_temp>" + ], + [ + 658, + "enum_init, 1>" + ], + [659, "function_call"], + [ + 660, + "struct_construct>" + ], + [ + 661, + "enum_init, 0>" + ], + [ + 662, + "store_temp>" + ], + [ + 663, + "enum_init, 1>" + ], + [ + 664, + "function_call" + ], + [665, "struct_construct>>"], + [ + 666, + "enum_init,)>, 0>" + ], + [ + 667, + "store_temp,)>>" + ], + [ + 668, + "enum_init,)>, 1>" + ], + [669, "felt252_const<30828113188794245257250221355944970489240709081949230>"], + [ + 670, + "function_call>::expect::>>" + ], + [ + 671, + "function_call" + ], + [ + 672, + "struct_construct>" + ], + [ + 673, + "enum_init, 0>" + ], + [ + 674, + "store_temp>" + ], + [ + 675, + "enum_init, 1>" + ], + [ + 676, + "function_call" + ], + [ + 677, + "felt252_const<815903823124453119243971298555977249487241195972064209763947138938752137060>" + ], + [678, "struct_construct"], + [679, "struct_deconstruct"], + [680, "pedersen"], + [681, "storage_base_address_from_felt252"], + [ + 682, + "enum_match>>" + ], + [683, "get_execution_info_v2_syscall"], + [ + 684, + "enum_init, core::array::Array::>, 0>" + ], + [ + 685, + "store_temp, core::array::Array::>>" + ], + [ + 686, + "enum_init, core::array::Array::>, 1>" + ], + [ + 687, + "function_call>::unwrap_syscall>" + ], + [ + 688, + "struct_construct>>" + ], + [ + 689, + "enum_init,)>, 0>" + ], + [ + 690, + "store_temp,)>>" + ], + [ + 691, + "enum_init,)>, 1>" + ], + [692, "enum_init"], + [693, "enum_match"], + [ + 694, + "felt252_const<931643411527245211529285955382961048845290794401900071034598191602821440999>" + ], + [ + 695, + "function_call" + ], + [ + 696, + "felt252_const<1093830577610461490539113735431936179703456330374563419579920790156759053133>" + ], + [ + 697, + "function_call" + ], + [ + 698, + "felt252_const<230192814789850291994621760810535338129013919481720626953423731987611497567>" + ], + [ + 699, + "function_call" + ], + [ + 700, + "felt252_const<1006403347863459601238155302934047095604124810258341309066785274196336610255>" + ], + [ + 701, + "function_call" + ], + [ + 702, + "felt252_const<1159172572126037242737035052411300333426457150519838559523379563476896254163>" + ], + [ + 703, + "function_call" + ], + [ + 704, + "enum_init, core::array::Array::, ())>, 1>" + ], + [ + 705, + "store_temp, core::array::Array::, ())>>" + ], + [ + 706, + "felt252_const<1030618457204368372767111383907020015782155770496513332882618556385908031709>" + ], + [ + 707, + "function_call" + ], + [ + 708, + "felt252_const<740220602995455187736018788443880169868965059258750296178224810252903139101>" + ], + [ + 709, + "function_call" + ], + [ + 710, + "felt252_const<181315633587806608749396046320465637423067733253841877734401949135886786816>" + ], + [ + 711, + "function_call" + ], + [ + 712, + "felt252_const<984040933967092568628707404443600392643744983021442377939648347191358829897>" + ], + [ + 713, + "function_call" + ], + [ + 714, + "felt252_const<28947657339379119812818542448122814251463852972795937477358048385548867373>" + ], + [ + 715, + "function_call" + ], + [ + 716, + "felt252_const<985944968547980417359859496386888840746920457321845064099263044834726206156>" + ], + [ + 717, + "function_call" + ], + [718, "struct_construct, Array, Unit>>"], + [ + 719, + "enum_init, core::array::Array::, ())>, 0>" + ], + [720, "struct_deconstruct>"], + [ + 721, + "felt252_const<812102049625239328877059852472148005149345189321026021198012755869152925610>" + ], + [ + 722, + "enum_match>>" + ], + [723, "enum_init"], + [ + 724, + "felt252_const<503758352667086857626778917682113906664552568267706797596983573137624924037>" + ], + [725, "enum_init"], + [ + 726, + "felt252_const<1403688409997574275391297710099088202596225268381271549688191319612926706217>" + ], + [ + 727, + "felt252_const<7891998437966260601762371672023996916393715052535837300>" + ], + [728, "drop"], + [ + 729, + "felt252_const<73818323049443828910082520332780958865486567819123588920520513298647499635>" + ], + [ + 730, + "enum_match>>" + ], + [731, "enum_init"], + [ + 732, + "enum_match, core::array::Array::>>" + ], + [733, "enum_init"], + [734, "enum_init"], + [ + 735, + "enum_match>>" + ], + [ + 736, + "enum_match>" + ], + [737, "struct_deconstruct>"], + [738, "hades_permutation"], + [739, "dup"], + [740, "struct_construct, felt252>>"], + [ + 741, + "enum_init, core::felt252)>, 0>" + ], + [ + 742, + "store_temp, core::felt252)>>" + ], + [ + 743, + "enum_init, core::felt252)>, 1>" + ], + [744, "drop"], + [745, "struct_construct"], + [746, "struct_deconstruct"], + [747, "drop>"], + [748, "alloc_local"], + [749, "store_local"], + [750, "function_call"], + [ + 751, + "enum_match, core::array::Span::, core::array::Span::, ())>>" + ], + [ + 752, + "struct_deconstruct, core::array::Span::, core::array::Span::, Unit>>" + ], + [753, "function_call"], + [ + 754, + "enum_match, core::integer::u8, ())>>" + ], + [755, "drop, u8, Unit>>"], + [756, "drop"], + [757, "drop>"], + [758, "enum_init"], + [759, "function_call"], + [760, "function_call"], + [761, "function_call"], + [762, "function_call"], + [763, "function_call"], + [764, "function_call"], + [765, "enum_init"], + [766, "function_call"], + [767, "struct_deconstruct, u8, Unit>>"], + [768, "function_call"], + [769, "array_new>"], + [770, "store_temp>>"], + [771, "function_call"], + [ + 772, + "enum_match, core::array::Array::>, core::array::Span::>)>>" + ], + [ + 773, + "struct_deconstruct, Array>, core::array::Span::>>>" + ], + [774, "drop>>"], + [ + 775, + "struct_construct>>>" + ], + [ + 776, + "enum_init>,)>, 0>" + ], + [ + 777, + "store_temp>,)>>" + ], + [ + 778, + "enum_init>,)>, 1>" + ], + [779, "alloc_local>>>"], + [780, "array_snapshot_pop_front>"], + [ + 781, + "enum_init>>, 0>" + ], + [782, "store_temp>>>"], + [ + 783, + "store_temp>>>" + ], + [ + 784, + "enum_init>>, 1>" + ], + [785, "store_local>>>"], + [ + 786, + "enum_match>>>" + ], + [787, "unbox>"], + [ + 788, + "struct_construct>>" + ], + [789, "drop>>>"], + [ + 790, + "drop>>>>" + ], + [ + 791, + "function_call" + ], + [792, "function_call"], + [793, "struct_construct>>"], + [ + 794, + "enum_init,)>, 0>" + ], + [ + 795, + "store_temp,)>>" + ], + [ + 796, + "enum_init,)>, 1>" + ], + [797, "function_call"], + [798, "function_call"], + [ + 799, + "function_call" + ], + [ + 800, + "enum_init>, 0>" + ], + [ + 801, + "struct_construct>>>" + ], + [ + 802, + "enum_init>,)>, 0>" + ], + [ + 803, + "store_temp>,)>>" + ], + [804, "felt252_const<1749165063169615148890104124711417950509560691>"], + [ + 805, + "enum_init>,)>, 1>" + ], + [ + 806, + "enum_init>, 1>" + ], + [ + 807, + "enum_match>>" + ], + [808, "enum_init"], + [809, "enum_init"], + [810, "enum_init"], + [ + 811, + "enum_match, core::array::Array::>>" + ], + [812, "dup"], + [813, "struct_deconstruct"], + [814, "dup"], + [815, "struct_deconstruct"], + [816, "dup"], + [817, "struct_deconstruct"], + [818, "struct_deconstruct"], + [819, "dup"], + [820, "struct_deconstruct"], + [821, "dup"], + [822, "struct_deconstruct"], + [823, "dup"], + [824, "struct_deconstruct"], + [825, "rename"], + [826, "u8_to_felt252"], + [827, "drop"], + [828, "dup"], + [829, "struct_deconstruct"], + [830, "dup"], + [831, "struct_deconstruct"], + [832, "dup"], + [833, "struct_deconstruct"], + [834, "dup"], + [835, "struct_deconstruct"], + [836, "felt252_const<159905696614104015011408994986197950919097459240563>"], + [ + 837, + "enum_init, core::array::Span::, core::array::Span::, ())>, 1>" + ], + [ + 838, + "store_temp, core::array::Span::, core::array::Span::, ())>>" + ], + [839, "function_call"], + [ + 840, + "enum_match, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>>" + ], + [ + 841, + "struct_deconstruct, core::array::Span::, felt252, u8, Array, Unit>>" + ], + [ + 842, + "struct_construct, core::array::Span::, core::array::Span::, Unit>>" + ], + [ + 843, + "enum_init, core::array::Span::, core::array::Span::, ())>, 0>" + ], + [844, "array_pop_front"], + [845, "dup"], + [846, "storage_address_from_base_and_offset"], + [847, "u8_const<1>"], + [848, "function_call"], + [849, "enum_match>"], + [850, "struct_deconstruct>"], + [851, "struct_construct, u8, Unit>>"], + [ + 852, + "enum_init, core::integer::u8, ())>, 0>" + ], + [ + 853, + "store_temp, core::integer::u8, ())>>" + ], + [ + 854, + "enum_init, core::integer::u8, ())>, 1>" + ], + [855, "felt252_const<521489465972896538179455241995775603>"], + [856, "felt252_const<2037068226456627102263497038849139>"], + [857, "felt252_const<31083194373422654758659319155>"], + [858, "upcast"], + [859, "u8_const<251>"], + [860, "function_call"], + [ + 861, + "enum_match, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>>" + ], + [ + 862, + "struct_deconstruct, core::array::Span::, felt252, u8, Array, Unit>>" + ], + [863, "array_append>"], + [ + 864, + "struct_construct, Array>, core::array::Span::>>>" + ], + [ + 865, + "enum_init, core::array::Array::>, core::array::Span::>)>, 0>" + ], + [ + 866, + "store_temp, core::array::Array::>, core::array::Span::>)>>" + ], + [ + 867, + "enum_init, core::array::Array::>, core::array::Span::>)>, 1>" + ], + [868, "snapshot_take>>"], + [869, "felt252_const<8749141412351935366688967357013970318550382>"], + [870, "function_call"], + [871, "alloc_local>>"], + [872, "array_snapshot_pop_front"], + [ + 873, + "enum_init>, 0>" + ], + [874, "store_temp>>"], + [ + 875, + "store_temp>>" + ], + [ + 876, + "enum_init>, 1>" + ], + [877, "store_local>>"], + [ + 878, + "enum_match>>" + ], + [879, "unbox"], + [880, "function_call"], + [ + 881, + "enum_match, ())>>" + ], + [882, "struct_deconstruct, Unit>>"], + [ + 883, + "struct_construct, core::array::Span::, felt252, u8, Array, Unit>>" + ], + [ + 884, + "enum_init, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>, 0>" + ], + [ + 885, + "store_temp, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>>" + ], + [ + 886, + "enum_init, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>, 1>" + ], + [887, "drop>>"], + [888, "felt252_const<29721761890975875353235833581453094220424382983267374>"], + [889, "drop>>>"], + [890, "u8_overflowing_add"], + [ + 891, + "enum_init, 0>" + ], + [ + 892, + "store_temp>" + ], + [ + 893, + "enum_init, 1>" + ], + [894, "felt252_const<608642104203229548495787928534675319>"], + [ + 895, + "function_call::expect::>" + ], + [896, "struct_construct>"], + [897, "enum_init, 0>"], + [898, "store_temp>"], + [899, "enum_init, 1>"], + [900, "alloc_local>"], + [901, "store_local>"], + [902, "function_call"], + [ + 903, + "enum_match, core::felt252, core::integer::u8, core::option::Option::)>>" + ], + [ + 904, + "struct_deconstruct, felt252, u8, core::option::Option::>>" + ], + [ + 905, + "struct_construct, core::array::Span::, felt252, u8, Array, Unit>>" + ], + [ + 906, + "enum_init, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>, 0>" + ], + [ + 907, + "store_temp, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>>" + ], + [ + 908, + "enum_init, core::array::Span::, core::felt252, core::integer::u8, core::array::Array::, ())>, 1>" + ], + [909, "drop>>"], + [910, "felt252_const<2037068226456627102263497038980473>"], + [911, "alloc_local"], + [912, "function_call"], + [913, "function_call::into>"], + [914, "u8_overflowing_sub"], + [915, "function_call"], + [916, "store_temp"], + [917, "store_local"], + [918, "function_call"], + [919, "enum_match>"], + [920, "struct_deconstruct"], + [921, "struct_deconstruct>"], + [922, "bitwise"], + [923, "struct_construct"], + [924, "function_call"], + [925, "struct_construct, Unit>>"], + [ + 926, + "enum_init, ())>, 0>" + ], + [ + 927, + "store_temp, ())>>" + ], + [ + 928, + "enum_init, ())>, 1>" + ], + [929, "drop"], + [930, "drop>"], + [ + 931, + "enum_match>" + ], + [932, "alloc_local>"], + [933, "u128_const<1>"], + [934, "u128_const<0>"], + [935, "function_call"], + [936, "store_local>"], + [937, "function_call"], + [938, "store_temp"], + [ + 939, + "struct_construct, felt252, u8, core::option::Option::>>" + ], + [ + 940, + "enum_init, core::felt252, core::integer::u8, core::option::Option::)>, 0>" + ], + [ + 941, + "store_temp, core::felt252, core::integer::u8, core::option::Option::)>>" + ], + [ + 942, + "enum_init, core::felt252, core::integer::u8, core::option::Option::)>, 1>" + ], + [943, "drop>"], + [944, "drop>>"], + [945, "snapshot_take"], + [946, "u8_eq"], + [947, "felt252_const<608642109794502019480482122260311927>"], + [948, "u128s_from_felt252"], + [949, "u128_const<2>"], + [950, "function_call"], + [951, "function_call"], + [952, "struct_construct>"], + [953, "enum_init, 0>"], + [954, "store_temp>"], + [955, "enum_init, 1>"], + [956, "u128_const<10633823966279327296825105735305134080>"], + [957, "dup"], + [958, "u128_overflowing_sub"], + [959, "snapshot_take"], + [960, "rename"], + [961, "u128_eq"], + [962, "u128_to_felt252"], + [963, "felt252_const<340282366920938463463374607431768211456>"], + [964, "felt252_mul"], + [965, "function_call"], + [966, "enum_match>"], + [967, "felt252_const<39879774624085075084607933104993585622903>"], + [968, "function_call"], + [969, "dup"], + [970, "u8_const<2>"], + [971, "function_call"], + [972, "function_call"], + [973, "function_call"], + [974, "felt252_const<39879774624083218221772669863277689073527>"], + [975, "function_call"], + [976, "struct_deconstruct>"], + [977, "enum_init, 0>"], + [978, "store_temp>"], + [979, "enum_init, 1>"], + [980, "function_call"], + [ + 981, + "enum_match>,)>>" + ], + [ + 982, + "struct_deconstruct>>>" + ], + [ + 983, + "enum_match>>" + ], + [984, "u256_safe_divmod"], + [985, "store_temp"], + [ + 986, + "function_call" + ], + [987, "struct_construct>"], + [988, "struct_deconstruct>"], + [989, "felt252_const<5420154128225384396790819266608>"], + [990, "function_call"], + [ + 991, + "enum_match>,)>>" + ], + [ + 992, + "struct_deconstruct>>>" + ], + [ + 993, + "enum_match>>" + ], + [994, "u8_safe_divmod"], + [995, "function_call"], + [996, "struct_construct>"], + [997, "store_temp>"], + [998, "struct_deconstruct>"], + [999, "struct_construct>"], + [1000, "store_temp>"], + [1001, "rename>"], + [1002, "function_call"], + [ + 1003, + "enum_match,)>>" + ], + [1004, "struct_deconstruct>>"], + [ + 1005, + "enum_init>, 0>" + ], + [ + 1006, + "struct_construct>>>" + ], + [ + 1007, + "enum_init>,)>, 0>" + ], + [ + 1008, + "store_temp>,)>>" + ], + [ + 1009, + "enum_init>,)>, 1>" + ], + [1010, "u128_mul_guarantee_verify"], + [1011, "function_call"], + [ + 1012, + "enum_match,)>>" + ], + [1013, "struct_deconstruct>>"], + [ + 1014, + "enum_init>, 0>" + ], + [ + 1015, + "struct_construct>>>" + ], + [ + 1016, + "enum_init>,)>, 0>" + ], + [ + 1017, + "store_temp>,)>>" + ], + [ + 1018, + "enum_init>,)>, 1>" + ], + [1019, "u128_guarantee_mul"], + [1020, "struct_construct>"], + [1021, "struct_deconstruct>"], + [1022, "u128_overflowing_add"], + [1023, "function_call"], + [1024, "struct_construct>>"], + [ + 1025, + "enum_init,)>, 0>" + ], + [ + 1026, + "store_temp,)>>" + ], + [1027, "felt252_const<2161886914012515606576>"], + [ + 1028, + "enum_init,)>, 1>" + ], + [1029, "function_call"], + [1030, "struct_construct>>"], + [ + 1031, + "enum_init,)>, 0>" + ], + [ + 1032, + "store_temp,)>>" + ], + [1033, "felt252_const<32994284134408240>"], + [ + 1034, + "enum_init,)>, 1>" + ], + [1035, "u256_is_zero"], + [ + 1036, + "enum_init>, 1>" + ], + [ + 1037, + "store_temp>>" + ], + [1038, "u8_is_zero"], + [ + 1039, + "enum_init>, 1>" + ], + [ + 1040, + "store_temp>>" + ] + ], + "user_func_names": [ + [0, "dojo::world::world::__wrapper__World__metadata_uri"], + [1, "dojo::world::world::__wrapper__World__set_metadata_uri"], + [2, "dojo::world::world::__wrapper__World__is_owner"], + [3, "dojo::world::world::__wrapper__World__grant_owner"], + [4, "dojo::world::world::__wrapper__World__revoke_owner"], + [5, "dojo::world::world::__wrapper__World__is_writer"], + [6, "dojo::world::world::__wrapper__World__grant_writer"], + [7, "dojo::world::world::__wrapper__World__revoke_writer"], + [8, "dojo::world::world::__wrapper__World__register_model"], + [9, "dojo::world::world::__wrapper__World__model"], + [10, "dojo::world::world::__wrapper__World__deploy_contract"], + [11, "dojo::world::world::__wrapper__World__upgrade_contract"], + [12, "dojo::world::world::__wrapper__World__uuid"], + [13, "dojo::world::world::__wrapper__World__emit"], + [14, "dojo::world::world::__wrapper__World__set_entity"], + [15, "dojo::world::world::__wrapper__World__delete_entity"], + [16, "dojo::world::world::__wrapper__World__entity"], + [17, "dojo::world::world::__wrapper__World__entities"], + [18, "dojo::world::world::__wrapper__World__entity_ids"], + [19, "dojo::world::world::__wrapper__World__set_executor"], + [20, "dojo::world::world::__wrapper__World__executor"], + [21, "dojo::world::world::__wrapper__World__base"], + [22, "dojo::world::world::__wrapper__UpgradeableWorld__upgrade"], + [23, "dojo::world::world::__wrapper__constructor"], + [24, "core::Felt252Serde::deserialize"], + [25, "dojo::world::world::World::metadata_uri"], + [ + 26, + "core::array::SpanSerde::::serialize" + ], + [ + 27, + "core::array::SpanSerde::::deserialize" + ], + [28, "dojo::world::world::World::set_metadata_uri"], + [29, "core::starknet::contract_address::ContractAddressSerde::deserialize"], + [30, "dojo::world::world::World::is_owner"], + [31, "core::BoolSerde::serialize"], + [32, "dojo::world::world::World::grant_owner"], + [33, "dojo::world::world::World::revoke_owner"], + [34, "dojo::world::world::World::is_writer"], + [35, "dojo::world::world::World::grant_writer"], + [36, "dojo::world::world::World::revoke_writer"], + [37, "core::starknet::class_hash::ClassHashSerde::deserialize"], + [38, "dojo::world::world::World::register_model"], + [39, "dojo::world::world::World::model"], + [40, "core::starknet::class_hash::ClassHashSerde::serialize"], + [41, "dojo::world::world::World::deploy_contract"], + [42, "core::starknet::contract_address::ContractAddressSerde::serialize"], + [43, "dojo::world::world::World::upgrade_contract"], + [44, "dojo::world::world::World::uuid"], + [ + 45, + "core::array::ArraySerde::::deserialize" + ], + [46, "dojo::world::world::World::emit"], + [47, "core::integer::Felt252TryIntoU8::try_into"], + [ + 48, + "core::array::SpanSerde::, core::integer::u8Drop>::deserialize" + ], + [49, "dojo::world::world::World::set_entity"], + [50, "dojo::world::world::World::delete_entity"], + [51, "core::integer::Felt252TryIntoU32::try_into"], + [52, "dojo::world::world::World::entity"], + [ + 53, + "core::option::OptionSerde::>::deserialize" + ], + [54, "dojo::world::world::World::entities"], + [ + 55, + "core::serde::TupleSize2Serde::, core::array::Span::>, core::array::SpanSerde::, core::array::SpanDrop::, core::array::SpanSerde::, core::array::SpanSerde::, core::array::SpanDrop::>, core::array::SpanDrop::>>::serialize" + ], + [56, "dojo::world::world::World::entity_ids"], + [57, "dojo::world::world::World::set_executor"], + [58, "dojo::world::world::World::executor"], + [59, "dojo::world::world::World::base"], + [60, "dojo::world::world::UpgradeableWorld::upgrade"], + [61, "dojo::world::world::constructor"], + [ + 62, + "dojo::world::world::metadata_uri::InternalContractMemberStateImpl::read" + ], + [63, "dojo::world::world::World::metadata_uri[expr29]"], + [ + 64, + "core::array::serialize_array_helper::" + ], + [ + 65, + "core::array::deserialize_array_helper::" + ], + [66, "core::starknet::info::get_caller_address"], + [ + 67, + "dojo::world::world::metadata_uri::InternalContractMemberStateImpl::write" + ], + [ + 68, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [69, "dojo::world::world::World::set_metadata_uri[expr42]"], + [70, "dojo::world::world::owners::InternalContractMemberStateImpl::read"], + [71, "core::Felt252Serde::serialize"], + [72, "dojo::world::world::owners::InternalContractMemberStateImpl::write"], + [ + 73, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [74, "dojo::world::world::writers::InternalContractMemberStateImpl::read"], + [75, "dojo::world::world::writers::InternalContractMemberStateImpl::write"], + [ + 76, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [77, "dojo::world::world::class_call"], + [78, "core::array::array_at::"], + [79, "dojo::world::world::models::InternalContractMemberStateImpl::read"], + [80, "dojo::world::world::models::InternalContractMemberStateImpl::write"], + [ + 81, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [ + 82, + "dojo::world::world::contract_base::InternalContractMemberStateImpl::read" + ], + [ + 83, + "core::starknet::SyscallResultTraitImpl::<(core::starknet::contract_address::ContractAddress, core::array::Span::)>::unwrap_syscall" + ], + [84, "dojo::components::upgradeable::IUpgradeableDispatcherImpl::upgrade"], + [ + 85, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [86, "dojo::world::world::assert_can_write"], + [ + 87, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [88, "dojo::world::world::nonce::InternalContractMemberStateImpl::read"], + [89, "core::integer::U32Add::add"], + [90, "dojo::world::world::nonce::InternalContractMemberStateImpl::write"], + [91, "core::starknet::SyscallResultTraitImpl::<()>::unwrap_syscall"], + [ + 92, + "core::array::deserialize_array_helper::, core::integer::u8Drop>" + ], + [93, "core::poseidon::poseidon_hash_span"], + [94, "dojo::database::set"], + [ + 95, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [96, "dojo::world::world::World::delete_entity[expr26]"], + [97, "dojo::database::del"], + [ + 98, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [99, "dojo::database::get"], + [100, "core::serde::TupleSize0Serde::deserialize"], + [101, "dojo::database::scan"], + [ + 102, + "core::array::SpanSerde::, core::array::SpanSerde::, core::array::SpanDrop::>::serialize" + ], + [103, "dojo::database::scan_ids"], + [ + 104, + "dojo::world::world::executor_dispatcher::InternalContractMemberStateImpl::read" + ], + [ + 105, + "dojo::world::world::executor_dispatcher::InternalContractMemberStateImpl::write" + ], + [ + 106, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [107, "core::starknet::info::get_tx_info"], + [ + 108, + "core::result::ResultTraitImpl::<(), core::array::Array::>::unwrap::>" + ], + [ + 109, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [ + 110, + "dojo::world::world::contract_base::InternalContractMemberStateImpl::write" + ], + [111, "core::starknet::info::get_contract_address"], + [ + 112, + "dojo::world::world::ContractStateEventEmitter::emit::" + ], + [ + 113, + "dojo::world::world::metadata_uri::InternalContractMemberStateImpl::address" + ], + [ + 114, + "core::starknet::SyscallResultTraitImpl::::unwrap_syscall" + ], + [115, "core::starknet::info::get_execution_info"], + [116, "dojo::world::world::EventMetadataUpdateIntoEvent::into"], + [117, "dojo::world::world::EventIsEvent::append_keys_and_data"], + [118, "dojo::world::world::owners::InternalContractMemberStateImpl::address"], + [119, "core::starknet::SyscallResultTraitImpl::::unwrap_syscall"], + [120, "dojo::world::world::EventOwnerUpdatedIntoEvent::into"], + [ + 121, + "dojo::world::world::writers::InternalContractMemberStateImpl::address" + ], + [122, "dojo::world::world::EventWriterUpdatedIntoEvent::into"], + [123, "dojo::executor::IExecutorDispatcherImpl::call"], + [124, "dojo::world::world::models::InternalContractMemberStateImpl::address"], + [125, "core::starknet::class_hash::Felt252TryIntoClassHash::try_into"], + [ + 126, + "core::starknet::SyscallResultTraitImpl::::unwrap_syscall" + ], + [127, "dojo::world::world::EventModelRegisteredIntoEvent::into"], + [ + 128, + "core::starknet::SyscallResultTraitImpl::>::unwrap_syscall" + ], + [129, "dojo::world::world::EventContractDeployedIntoEvent::into"], + [130, "dojo::world::world::EventContractUpgradedIntoEvent::into"], + [ + 131, + "core::starknet::SyscallResultTraitImpl::::unwrap_syscall" + ], + [ + 132, + "core::result::ResultTraitImpl::::expect::" + ], + [133, "core::poseidon::_poseidon_hash_span_inner"], + [134, "dojo::database::storage::set_many"], + [135, "dojo::world::world::EventStoreSetRecordIntoEvent::into"], + [136, "dojo::database::index::delete"], + [137, "dojo::world::world::EventStoreDelRecordIntoEvent::into"], + [138, "dojo::database::storage::get_many"], + [139, "dojo::database::get_by_ids"], + [ + 140, + "core::array::serialize_array_helper::, core::array::SpanSerde::, core::array::SpanDrop::>" + ], + [141, "dojo::database::index::get_by_key"], + [142, "dojo::database::index::query"], + [143, "dojo::executor::StoreIExecutorDispatcher::read"], + [ + 144, + "core::starknet::SyscallResultTraitImpl::::unwrap_syscall" + ], + [145, "dojo::executor::StoreIExecutorDispatcher::write"], + [146, "dojo::world::world::EventExecutorUpdatedIntoEvent::into"], + [ + 147, + "core::result::ResultTraitImpl::<(), core::array::Array::>::expect::>" + ], + [148, "dojo::world::world::EventWorldUpgradedIntoEvent::into"], + [149, "dojo::world::world::EventWorldSpawnedIntoEvent::into"], + [ + 150, + "core::starknet::SyscallResultTraitImpl::>::unwrap_syscall" + ], + [151, "dojo::world::world::WorldSpawnedIsEvent::append_keys_and_data"], + [152, "dojo::world::world::ContractDeployedIsEvent::append_keys_and_data"], + [153, "dojo::world::world::ContractUpgradedIsEvent::append_keys_and_data"], + [154, "dojo::world::world::WorldUpgradedIsEvent::append_keys_and_data"], + [155, "dojo::world::world::MetadataUpdateIsEvent::append_keys_and_data"], + [156, "dojo::world::world::ModelRegisteredIsEvent::append_keys_and_data"], + [157, "dojo::world::world::StoreSetRecordIsEvent::append_keys_and_data"], + [158, "dojo::world::world::StoreDelRecordIsEvent::append_keys_and_data"], + [159, "dojo::world::world::WriterUpdatedIsEvent::append_keys_and_data"], + [160, "dojo::world::world::OwnerUpdatedIsEvent::append_keys_and_data"], + [161, "dojo::world::world::ExecutorUpdatedIsEvent::append_keys_and_data"], + [162, "dojo::packing::pack"], + [163, "dojo::database::storage::set_many[expr24]"], + [164, "dojo::database::index::exists"], + [165, "dojo::database::index::build_index_len_key"], + [166, "dojo::database::storage::get"], + [167, "dojo::database::index::build_index_item_key"], + [168, "dojo::database::storage::set"], + [169, "dojo::database::index::build_index_key"], + [170, "dojo::database::storage::get_many[expr25]"], + [171, "dojo::packing::unpack"], + [172, "dojo::database::get_by_ids[expr32]"], + [173, "dojo::database::index::build_index_specific_key_len"], + [174, "dojo::database::index::get_by_key[expr29]"], + [175, "dojo::database::index::query[expr38]"], + [176, "dojo::database::index::query[expr65]"], + [ + 177, + "core::starknet::contract_address::Felt252TryIntoContractAddress::try_into" + ], + [178, "dojo::packing::pack[expr25]"], + [179, "core::integer::U8Add::add"], + [180, "dojo::packing::unpack[expr19]"], + [181, "dojo::database::index::build_index_specific_key"], + [182, "dojo::packing::pack_inner"], + [ + 183, + "core::result::ResultTraitImpl::::expect::" + ], + [184, "dojo::packing::unpack_inner"], + [185, "core::integer::U8Sub::sub"], + [186, "core::traits::TIntoT::::into"], + [187, "core::integer::u256_from_felt252"], + [188, "dojo::packing::shl"], + [189, "core::integer::U256TryIntoFelt252::try_into"], + [190, "core::integer::U256Sub::sub"], + [191, "dojo::packing::shr"], + [192, "dojo::packing::fpow"], + [193, "core::integer::U256Mul::mul"], + [194, "core::integer::u256_checked_sub"], + [195, "core::integer::U256Div::div"], + [196, "core::integer::U8Div::div"], + [197, "core::integer::U8Rem::rem"], + [198, "core::integer::u256_checked_mul"], + [199, "core::integer::u256_overflow_sub"], + [200, "core::integer::U256TryIntoNonZero::try_into"], + [201, "core::integer::U128MulGuaranteeDestruct::destruct"], + [202, "core::integer::U8TryIntoNonZero::try_into"], + [203, "core::integer::u256_overflow_mul"], + [204, "core::integer::u256_as_non_zero"], + [205, "core::integer::u8_as_non_zero"], + [206, "core::integer::u256_try_as_non_zero"], + [207, "core::integer::u8_try_as_non_zero"] + ] + }, + "contract_class_version": "0.1.0", + "entry_points_by_type": { + "EXTERNAL": [ + { + "selector": "0x2d15e90b6c97950698267456915f7a71eab1e2c2d9c29adda4c64351b947f6", + "function_idx": 19 + }, + { + "selector": "0x78e492aaf17f7a3f781cf5bd3bbfcc2b3eaa41d560004e97822a389e5b9938", + "function_idx": 4 + }, + { + "selector": "0x92c0700add430acfc01c011a36ab44410c9f866f45f5c6faf34816c329d815", + "function_idx": 11 + }, + { + "selector": "0x95201d0392af03939a89e3de12f33d3dba253a149135f3cf63df141304874a", + "function_idx": 1 + }, + { + "selector": "0xc9ce8ef5f69752e9f3464854b4d203757e0270e1484f07386d4858dbc90fd5", + "function_idx": 5 + }, + { + "selector": "0xd065c837ba98927ca43d2e15d8e840ca8e67646b8e00108f1b55d18a75d80f", + "function_idx": 14 + }, + { + "selector": "0xd682fdeedea6d7edc10c60b7f4feb26bba9c226f7f49d20537e0a5c8e4dca4", + "function_idx": 2 + }, + { + "selector": "0xdc49c2b31b72d9d49bdda99fca2fa95be0944a4ad731474dd3cdb1b704f9c6", + "function_idx": 20 + }, + { + "selector": "0xf2f7c15cbe06c8d94597cd91fd7f3369eae842359235712def5584f8d270cd", + "function_idx": 22 + }, + { + "selector": "0x11185346d2996711dd761c502ee7036b7bbf81137b5ba67ab29c6b6f57570a9", + "function_idx": 9 + }, + { + "selector": "0x1cdc902ca80aea4fa41f94d35654158d368cd858e75e9f2426d204944eef764", + "function_idx": 0 + }, + { + "selector": "0x1e7875674bcb09daaf984cbf77264ac98120cb39e6d17522520defcdc347476", + "function_idx": 8 + }, + { + "selector": "0x1f3eb40f5bc1ad1344716ced8b8a0431d840b5783aea1fd01786bc26f35ac0f", + "function_idx": 21 + }, + { + "selector": "0x2006b7fe6210e12240f1a27ac985955c598e7576256baac9572d2eaba0c1ec6", + "function_idx": 13 + }, + { + "selector": "0x2730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8", + "function_idx": 10 + }, + { + "selector": "0x27706e83545bc0fb130476239ec381f65fba8f92e2379d60a07869aa04b4ccc", + "function_idx": 17 + }, + { + "selector": "0x2bb4f87ddba687c0a9c116b582343d1ec88aac87d1a2b9d0d2eb97f72ec04b3", + "function_idx": 7 + }, + { + "selector": "0x2ee0e84a99e5b3cb20adcdbe548dd1ab3bb535bdd690595e43594707778be82", + "function_idx": 12 + }, + { + "selector": "0x326593e1b7478914a9c4ff6d556d1561d942e60fae7253b1eb00c19702dbf1f", + "function_idx": 16 + }, + { + "selector": "0x3271a0851c2c2e6b7ba8ba7abcb8224c6ea500835b9db9f2fb9ca5bb16e2191", + "function_idx": 3 + }, + { + "selector": "0x32f38f35a723d373631d54098023497b029ba854937c1f7d43d9a7e5d740dc2", + "function_idx": 6 + }, + { + "selector": "0x3b69340fcf8be188929519cadac84cb5deef1f1eb93ae649bc174185cabeded", + "function_idx": 18 + }, + { + "selector": "0x3fd91e956981a61ef4c1038f3b8f3aa8306728a656a6b695f280191ed6321ef", + "function_idx": 15 + } + ], + "L1_HANDLER": [], + "CONSTRUCTOR": [ + { + "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", + "function_idx": 23 + } + ] + }, + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::IWorld" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::felt252" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorld", + "items": [ + { + "type": "function", + "name": "metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "uri", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "model", + "inputs": [ + { + "name": "name", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "deploy_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit", + "inputs": [ + { + "name": "keys", + "type": "core::array::Array::" + }, + { + "name": "values", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "length", + "type": "core::integer::u32" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entities", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "index", + "type": "core::option::Option::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "values_length", + "type": "core::integer::u32" + }, + { + "name": "values_layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "(core::array::Span::, core::array::Span::>)" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity_ids", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_executor", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "executor", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "base", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "executor", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "contract_base", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractDeployed", + "kind": "struct", + "members": [ + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "uri", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "prev_class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "offset", + "type": "core::integer::u8", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "model", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ExecutorUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "ContractDeployed", + "type": "dojo::world::world::ContractDeployed", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world::OwnerUpdated", + "kind": "nested" + }, + { + "name": "ExecutorUpdated", + "type": "dojo::world::world::ExecutorUpdated", + "kind": "nested" + } + ] + } + ] +} diff --git a/crates/katana/storage/db/benches/codec.rs b/crates/katana/storage/db/benches/codec.rs new file mode 100644 index 0000000000..e74344ec92 --- /dev/null +++ b/crates/katana/storage/db/benches/codec.rs @@ -0,0 +1,47 @@ +use blockifier::execution::contract_class::ContractClassV1; +use cairo_lang_starknet::casm_contract_class::CasmContractClass; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use katana_db::codecs::{Compress, Decompress}; +use katana_db::models::contract::StoredContractClass; +use katana_primitives::contract::CompiledContractClass; + +fn compress_contract(contract: CompiledContractClass) -> Vec { + let class = StoredContractClass::from(contract); + class.compress() +} + +fn decompress_contract(compressed: &[u8]) -> CompiledContractClass { + let class = StoredContractClass::decompress(compressed).unwrap(); + CompiledContractClass::from(class) +} + +fn compress_contract_with_main_codec(c: &mut Criterion) { + let class = { + let class = + serde_json::from_slice(include_bytes!("./artifacts/dojo_world_240.json")).unwrap(); + let class = CasmContractClass::from_contract_class(class, true).unwrap(); + CompiledContractClass::V1(ContractClassV1::try_from(class).unwrap()) + }; + + c.bench_function("compress world contract", |b| { + b.iter_with_large_drop(|| compress_contract(black_box(class.clone()))) + }); +} + +fn decompress_contract_with_main_codec(c: &mut Criterion) { + let class = { + let class = + serde_json::from_slice(include_bytes!("./artifacts/dojo_world_240.json")).unwrap(); + let class = CasmContractClass::from_contract_class(class, true).unwrap(); + CompiledContractClass::V1(ContractClassV1::try_from(class).unwrap()) + }; + + let compressed = compress_contract(class); + + c.bench_function("decompress world contract", |b| { + b.iter_with_large_drop(|| decompress_contract(black_box(&compressed))) + }); +} + +criterion_group!(contract, compress_contract_with_main_codec, decompress_contract_with_main_codec); +criterion_main!(contract); From dc5faa26730ffdcf574d22d3583861fad50abeb9 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 18 Dec 2023 08:22:03 -0800 Subject: [PATCH 154/192] Rename entity ids to hashed_keys in grpc (#1286) * Rename entity ids to hashed_keys in grpc protobuf * use hashed_keys more generally --- crates/torii/grpc/proto/types.proto | 11 ++--- crates/torii/grpc/proto/world.proto | 2 +- crates/torii/grpc/src/client.rs | 6 +-- crates/torii/grpc/src/server/mod.rs | 45 +++++++++++-------- .../grpc/src/server/subscriptions/entity.rs | 25 ++++++----- crates/torii/grpc/src/types/schema.rs | 5 ++- 6 files changed, 52 insertions(+), 42 deletions(-) diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto index dd27e2dc52..abb8495b9c 100644 --- a/crates/torii/grpc/proto/types.proto +++ b/crates/torii/grpc/proto/types.proto @@ -35,9 +35,10 @@ message Model { string name = 1; repeated Member members = 2; } + message Entity { - // The entity id - bytes id = 1; + // The entity's hashed keys + bytes hashed_keys = 1; // Models of the entity repeated Model models = 2; } @@ -74,15 +75,15 @@ message Query { message Clause { oneof clause_type { - IdsClause ids = 1; + HashedKeysClause hashed_keys = 1; KeysClause keys = 2; MemberClause member = 3; CompositeClause composite = 4; } } -message IdsClause { - repeated bytes ids = 1; +message HashedKeysClause { + repeated bytes hashed_keys = 1; } message KeysClause { diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 3b42be93af..0539e4e493 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -40,7 +40,7 @@ message SubscribeModelsResponse { } message SubscribeEntitiesRequest { - repeated bytes ids = 1; + repeated bytes hashed_keys = 1; } message SubscribeEntityResponse { diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index c8870bbd1c..90852a0701 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -86,12 +86,12 @@ impl WorldClient { /// Subscribe to entities updates of a World. pub async fn subscribe_entities( &mut self, - ids: Vec, + hashed_keys: Vec, ) -> Result { - let ids = ids.iter().map(|id| id.to_bytes_be().to_vec()).collect(); + let hashed_keys = hashed_keys.iter().map(|hashed| hashed.to_bytes_be().to_vec()).collect(); let stream = self .inner - .subscribe_entities(SubscribeEntitiesRequest { ids }) + .subscribe_entities(SubscribeEntitiesRequest { hashed_keys }) .await .map_err(Error::Grpc) .map(|res| res.into_inner())?; diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 87d3c9be7e..8216990e27 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -120,20 +120,20 @@ impl DojoWorld { limit: u32, offset: u32, ) -> Result, Error> { - self.entities_by_ids(None, limit, offset).await + self.entities_by_hashed_keys(None, limit, offset).await } - async fn entities_by_ids( + async fn entities_by_hashed_keys( &self, - ids_clause: Option, + hashed_keys: Option, limit: u32, offset: u32, ) -> Result, Error> { // TODO: use prepared statement for where clause - let filter_ids = match ids_clause { - Some(ids_clause) => { - let ids = ids_clause - .ids + let filter_ids = match hashed_keys { + Some(hashed_keys) => { + let ids = hashed_keys + .hashed_keys .iter() .map(|id| { Ok(FieldElement::from_byte_slice_be(id) @@ -180,8 +180,11 @@ impl DojoWorld { }) .collect::, Error>>()?; - let id = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; - entities.push(proto::types::Entity { id: id.to_bytes_be().to_vec(), models }) + let hashed_keys = FieldElement::from_str(&entity_id).map_err(ParseError::FromStr)?; + entities.push(proto::types::Entity { + hashed_keys: hashed_keys.to_bytes_be().to_vec(), + models, + }) } Ok(entities) @@ -312,9 +315,9 @@ impl DojoWorld { async fn subscribe_entities( &self, - ids: Vec, + hashed_keys: Vec, ) -> Result>, Error> { - self.entity_manager.add_subscriber(ids).await + self.entity_manager.add_subscriber(hashed_keys).await } async fn retrieve_entities( @@ -328,12 +331,13 @@ impl DojoWorld { clause.clause_type.ok_or(QueryError::MissingParam("clause_type".into()))?; match clause_type { - ClauseType::Ids(ids) => { - if ids.ids.is_empty() { + ClauseType::HashedKeys(hashed_keys) => { + if hashed_keys.hashed_keys.is_empty() { return Err(QueryError::MissingParam("ids".into()).into()); } - self.entities_by_ids(Some(ids), query.limit, query.offset).await? + self.entities_by_hashed_keys(Some(hashed_keys), query.limit, query.offset) + .await? } ClauseType::Keys(keys) => { if keys.keys.is_empty() { @@ -360,7 +364,7 @@ impl DojoWorld { } fn map_row_to_entity(row: &SqliteRow, schemas: &[Ty]) -> Result { - let id = + let hashed_keys = FieldElement::from_str(&row.get::("id")).map_err(ParseError::FromStr)?; let models = schemas .iter() @@ -372,7 +376,7 @@ impl DojoWorld { }) .collect::, Error>>()?; - Ok(proto::types::Entity { id: id.to_bytes_be().to_vec(), models }) + Ok(proto::types::Entity { hashed_keys: hashed_keys.to_bytes_be().to_vec(), models }) } } @@ -415,15 +419,18 @@ impl proto::world::world_server::World for DojoWorld { &self, request: Request, ) -> ServiceResult { - let SubscribeEntitiesRequest { ids } = request.into_inner(); - let ids = ids + let SubscribeEntitiesRequest { hashed_keys } = request.into_inner(); + let hashed_keys = hashed_keys .iter() .map(|id| { FieldElement::from_byte_slice_be(id) .map_err(|e| Status::invalid_argument(e.to_string())) }) .collect::, _>>()?; - let rx = self.subscribe_entities(ids).await.map_err(|e| Status::internal(e.to_string()))?; + let rx = self + .subscribe_entities(hashed_keys) + .await + .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) } diff --git a/crates/torii/grpc/src/server/subscriptions/entity.rs b/crates/torii/grpc/src/server/subscriptions/entity.rs index a39529e681..e4a86176c8 100644 --- a/crates/torii/grpc/src/server/subscriptions/entity.rs +++ b/crates/torii/grpc/src/server/subscriptions/entity.rs @@ -23,7 +23,7 @@ use crate::proto; pub struct EntitiesSubscriber { /// Entity ids that the subscriber is interested in - ids: HashSet, + hashed_keys: HashSet, /// The channel to send the response back to the subscriber. sender: Sender>, } @@ -36,15 +36,15 @@ pub struct EntityManager { impl EntityManager { pub async fn add_subscriber( &self, - ids: Vec, + hashed_keys: Vec, ) -> Result>, Error> { let id = rand::thread_rng().gen::(); let (sender, receiver) = channel(1); - self.subscribers - .write() - .await - .insert(id, EntitiesSubscriber { ids: ids.iter().cloned().collect(), sender }); + self.subscribers.write().await.insert( + id, + EntitiesSubscriber { hashed_keys: hashed_keys.iter().cloned().collect(), sender }, + ); Ok(receiver) } @@ -80,14 +80,14 @@ impl Service { subs: Arc, cache: Arc, pool: Pool, - id: &str, + hashed_keys: &str, ) -> Result<(), Error> { let mut closed_stream = Vec::new(); for (idx, sub) in subs.subscribers.read().await.iter() { - let felt_id = FieldElement::from_str(id).map_err(ParseError::FromStr)?; + let hashed = FieldElement::from_str(hashed_keys).map_err(ParseError::FromStr)?; // publish all updates if ids is empty or only ids that are subscribed to - if sub.ids.is_empty() || sub.ids.contains(&felt_id) { + if sub.hashed_keys.is_empty() || sub.hashed_keys.contains(&hashed) { let models_query = r#" SELECT group_concat(entity_model.model_id) as model_names FROM entities @@ -96,11 +96,12 @@ impl Service { GROUP BY entities.id "#; let (model_names,): (String,) = - sqlx::query_as(models_query).bind(id).fetch_one(&pool).await?; + sqlx::query_as(models_query).bind(hashed_keys).fetch_one(&pool).await?; let model_names: Vec<&str> = model_names.split(',').collect(); let schemas = cache.schemas(model_names).await?; + let entity_query = format!("{} WHERE entities.id = ?", build_sql_query(&schemas)?); - let row = sqlx::query(&entity_query).bind(id).fetch_one(&pool).await?; + let row = sqlx::query(&entity_query).bind(hashed_keys).fetch_one(&pool).await?; let models = schemas .iter() @@ -115,7 +116,7 @@ impl Service { let resp = proto::world::SubscribeEntityResponse { entity: Some(proto::types::Entity { - id: felt_id.to_bytes_be().to_vec(), + hashed_keys: hashed.to_bytes_be().to_vec(), models, }), }; diff --git a/crates/torii/grpc/src/types/schema.rs b/crates/torii/grpc/src/types/schema.rs index 63d6adce8d..ecb97f0a5c 100644 --- a/crates/torii/grpc/src/types/schema.rs +++ b/crates/torii/grpc/src/types/schema.rs @@ -8,7 +8,7 @@ use crate::proto::{self}; #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Entity { - pub id: FieldElement, + pub hashed_keys: FieldElement, pub models: Vec, } @@ -22,7 +22,8 @@ impl TryFrom for Entity { type Error = ClientError; fn try_from(entity: proto::types::Entity) -> Result { Ok(Self { - id: FieldElement::from_byte_slice_be(&entity.id).map_err(ClientError::SliceError)?, + hashed_keys: FieldElement::from_byte_slice_be(&entity.hashed_keys) + .map_err(ClientError::SliceError)?, models: entity .models .into_iter() From f61e50c37fd666abe2af2064bfcf97ade530a4cf Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 11:32:37 -0500 Subject: [PATCH 155/192] Improve platform specific builds --- .devcontainer/Dockerfile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f8542075ff..df398efd00 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,9 +1,8 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust/.devcontainer/base.Dockerfile # [Choice] Debian OS version (use bookworm on local arm64/Apple Silicon): buster, bullseye, bookworm -ARG VARIANT -ARG TARGETPLATFORM +ARG VARIANT FROM mcr.microsoft.com/vscode/devcontainers/rust:${VARIANT} # Install additional packages @@ -25,14 +24,17 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" RUN rustup target add x86_64-pc-windows-msvc && \ rustup target add wasm32-unknown-unknown -RUN rustup toolchain install nightly && \ - rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly - RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y + +ARG TARGETPLATFORM RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ + echo "Running arm64 branch" && \ rustup component add llvm-tools-preview --toolchain 1.70.0-aarch64-unknown-linux-gnu; \ elif [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ - rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu; \ + echo "Running amd64 branch" && \ + rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu && \ + rustup toolchain install nightly && \ + rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly; \ fi RUN curl -L https://install.dojoengine.org | bash @@ -41,4 +43,4 @@ ENV PATH=${PATH}:/root/.dojo/bin RUN dojoup RUN chown -R root:root /usr/local/cargo -RUN chmod -R 700 /usr/local/cargo \ No newline at end of file +RUN chmod -R 700 /usr/local/cargo From e2b151383c0747d0061d6d7f0c79ca3f2487bdd6 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 12:09:55 -0500 Subject: [PATCH 156/192] Update devcontainer image hash: f61e50c (#1303) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ea62ddc04c..44a84d32b9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:54f0635", + "image": "ghcr.io/dojoengine/dojo-dev:f61e50c", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e795d84e4..36b36d16b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-4-cores container: - image: ghcr.io/dojoengine/dojo-dev:54f0635 + image: ghcr.io/dojoengine/dojo-dev:f61e50c steps: - uses: actions/checkout@v3 with: From e0220426af212c6d904a54c14c0e247bf2c9b703 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 13:49:44 -0500 Subject: [PATCH 157/192] Integrate code coverage (#1296) --- .github/workflows/ci.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36b36d16b6..647d807bcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ env: jobs: test: - runs-on: ubuntu-latest-4-cores + runs-on: ubuntu-latest-16-cores container: image: ghcr.io/dojoengine/dojo-dev:f61e50c steps: @@ -20,7 +20,12 @@ jobs: with: fetch-depth: 0 - uses: Swatinem/rust-cache@v2 - - run: cargo nextest run --all-features + - run: cargo llvm-cov nextest --all-features --lcov --output-path lcov.info + - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true ensure-wasm: runs-on: ubuntu-latest From 8dfe3002a05c93233366b8f82ab202919c20232e Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 13:56:50 -0500 Subject: [PATCH 158/192] Install cargo-release in devcontainer --- .devcontainer/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index df398efd00..7afa869fce 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -24,8 +24,9 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" RUN rustup target add x86_64-pc-windows-msvc && \ rustup target add wasm32-unknown-unknown -RUN cargo binstall cargo-nextest cargo-llvm-cov --secure -y +RUN cargo binstall cargo-nextest cargo-llvm-cov cargo-release --secure -y +# Platform specific tooling ARG TARGETPLATFORM RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ echo "Running arm64 branch" && \ From 64b88aa56e544f89add6ca4ff1c1c81ae84c60fe Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 14:20:29 -0500 Subject: [PATCH 159/192] Add nightly rustfmt to devcontainer --- .devcontainer/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7afa869fce..efe6f0053e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -21,6 +21,9 @@ RUN rustup toolchain install $(cat rust-toolchain.toml | grep channel | cut -d\" rustup component add clippy && \ rustup component add rustfmt +RUN rustup toolchain install nightly && \ + rustup component add rustfmt --toolchain nightly + RUN rustup target add x86_64-pc-windows-msvc && \ rustup target add wasm32-unknown-unknown From f6a4b8a71ad668a31b987a4eda811107c5cab523 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 14:20:57 -0500 Subject: [PATCH 160/192] Add hurl to devcontainer (#1306) --- .devcontainer/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index efe6f0053e..4cbbfac4ea 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -32,10 +32,12 @@ RUN cargo binstall cargo-nextest cargo-llvm-cov cargo-release --secure -y # Platform specific tooling ARG TARGETPLATFORM RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ - echo "Running arm64 branch" && \ + curl -L https://github.com/Orange-OpenSource/hurl/releases/download/4.1.0/hurl-4.1.0-aarch64-unknown-linux-gnu.tar.gz -o hurl.tar.gz && \ + tar -xzf hurl.tar.gz -C /usr/local/bin && rm hurl.tar.gz && \ rustup component add llvm-tools-preview --toolchain 1.70.0-aarch64-unknown-linux-gnu; \ elif [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ - echo "Running amd64 branch" && \ + curl -L https://github.com/Orange-OpenSource/hurl/releases/download/4.1.0/hurl-4.1.0-x86_64-unknown-linux-gnu.tar.gz -o hurl.tar.gz && \ + tar -xzf hurl.tar.gz -C /usr/local/bin && rm hurl.tar.gz && \ rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu && \ rustup toolchain install nightly && \ rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly; \ From e1ba4a6bf4fddf0701762d07cc81feb3c7e23924 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 14:40:45 -0500 Subject: [PATCH 161/192] Update devcontainer image hash: f6a4b8a (#1305) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 44a84d32b9..595a1a5d37 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:f61e50c", + "image": "ghcr.io/dojoengine/dojo-dev:f6a4b8a", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 647d807bcc..0a670f3a19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-16-cores container: - image: ghcr.io/dojoengine/dojo-dev:f61e50c + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 with: From 2e8cd6268b111632643441757d864b6f6214a141 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 15:09:39 -0500 Subject: [PATCH 162/192] Update ci to use devcontainer (#1304) --- .devcontainer/Dockerfile | 1 - .github/workflows/ci.yml | 69 +++++++++++----------------------------- 2 files changed, 18 insertions(+), 52 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4cbbfac4ea..39ed05f851 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -39,7 +39,6 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ curl -L https://github.com/Orange-OpenSource/hurl/releases/download/4.1.0/hurl-4.1.0-x86_64-unknown-linux-gnu.tar.gz -o hurl.tar.gz && \ tar -xzf hurl.tar.gz -C /usr/local/bin && rm hurl.tar.gz && \ rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu && \ - rustup toolchain install nightly && \ rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly; \ fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a670f3a19..b4d27a45fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,17 +29,12 @@ jobs: ensure-wasm: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - - name: "Ensure `torii-client` crate is WASM-able" - run: | - cargo build -r --target wasm32-unknown-unknown -p torii-client + - run: cargo build -r --target wasm32-unknown-unknown -p torii-client ensure-windows: runs-on: windows-latest @@ -51,9 +46,7 @@ jobs: target: x86_64-pc-windows-msvc - uses: Swatinem/rust-cache@v2 - uses: arduino/setup-protoc@v2 - - name: "Ensure buildable on windows" - run: | - cargo build --target x86_64-pc-windows-msvc --bins + - run: cargo build --target x86_64-pc-windows-msvc --bins # cairofmt: # runs-on: ubuntu-latest @@ -69,92 +62,66 @@ jobs: dojo-core-test: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - run: cargo run --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test dojo-spawn-and-move-example-test: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v2 - run: cargo run --bin sozo -- --manifest-path examples/spawn-and-move/Scarb.toml test clippy: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - targets: wasm32-unknown-unknown - components: clippy - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - run: scripts/clippy.sh fmt: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly - components: rustfmt - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - run: scripts/rust_fmt.sh --check docs: runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@v2 - - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - run: > scripts/docs.sh test-hurl: runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Checkout code - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@v2 - - name: Install Hurl - run: | + - run: | curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/3.0.0/hurl_3.0.0_amd64.deb sudo apt update && sudo apt install ./hurl_3.0.0_amd64.deb - - name: Build and start Katana node - run: | + - run: | cargo build --bin katana nohup target/debug/katana --accounts 2 --disable-fee & - - name: Execute Hurl Scripts - run: hurl --test examples/rpc/**/*.hurl + - run: hurl --test examples/rpc/**/*.hurl From 76decd2b5acd3573f42dbda128d049fe72f713f6 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 16:12:01 -0500 Subject: [PATCH 163/192] Update devcontainer image hash: 2e8cd62 (#1307) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 595a1a5d37..07a8709a21 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:f6a4b8a", + "image": "ghcr.io/dojoengine/dojo-dev:2e8cd62", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4d27a45fc..0fc1e4c1e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-16-cores container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 with: @@ -30,7 +30,7 @@ jobs: ensure-wasm: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -63,7 +63,7 @@ jobs: dojo-core-test: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -72,7 +72,7 @@ jobs: dojo-spawn-and-move-example-test: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -81,7 +81,7 @@ jobs: clippy: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 with: @@ -92,7 +92,7 @@ jobs: fmt: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -101,7 +101,7 @@ jobs: docs: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:2e8cd62 steps: - uses: actions/checkout@v3 with: From ed6e29e243e3834696fba005f229de1dc6e2f180 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 16:23:29 -0500 Subject: [PATCH 164/192] Prepare for cargo release (#1308) * Prepare for cargo release * Add release-dispatch job * Update all devcontainers --- .cargo/config.toml | 2 ++ .github/workflows/devcontainer.yml | 7 +++++-- .github/workflows/release-dispatch.yml | 24 ++++++++++++++++++++++++ Cargo.lock | 8 ++++++++ Cargo.toml | 2 ++ crates/dojo-core/Cargo.toml | 15 +++++++++++++++ crates/dojo-core/src/lib.rs | 1 + examples/spawn-and-move/Cargo.toml | 15 +++++++++++++++ examples/spawn-and-move/src/lib.rs | 1 + scripts/prepare_release.sh | 14 -------------- 10 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/release-dispatch.yml create mode 100644 crates/dojo-core/Cargo.toml create mode 100644 crates/dojo-core/src/lib.rs create mode 100644 examples/spawn-and-move/Cargo.toml create mode 100644 examples/spawn-and-move/src/lib.rs delete mode 100755 scripts/prepare_release.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..8595a0cb48 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries.crates-io] +protocol = "git" \ No newline at end of file diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index da93787b8b..c67c99e18b 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -71,8 +71,11 @@ jobs: - name: Update devcontainer.json run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - - name: Update ci devcontainers - run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .github/workflows/ci.yml + - name: Update github action devcontainers + run: | + for file in .github/workflows/*.yml; do + sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" + done - name: Create Pull Request uses: peter-evans/create-pull-request@v3 diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml new file mode 100644 index 0000000000..2d8eee4717 --- /dev/null +++ b/.github/workflows/release-dispatch.yml @@ -0,0 +1,24 @@ +name: Test release-rust +on: + workflow_dispatch: + inputs: + version: + description: Version to release + required: true + type: string + +jobs: + release: + permissions: + id-token: write # Enable OIDC + pull-requests: write + contents: write + runs-on: ubuntu-latest + container: + image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + steps: + - uses: actions/checkout@v3 + - uses: cargo-bins/release-pr@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: ${{ inputs.version }} diff --git a/Cargo.lock b/Cargo.lock index 974617abcc..31c5320c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2621,6 +2621,14 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dojo-core" +version = "0.4.1" + +[[package]] +name = "dojo-examples-spawn-and-move" +version = "0.4.1" + [[package]] name = "dojo-lang" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 560574e454..e3789f8627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/benches", + "crates/dojo-core", "crates/dojo-lang", "crates/dojo-language-server", "crates/dojo-signers", @@ -25,6 +26,7 @@ members = [ "crates/sozo", "crates/torii/client", "crates/torii/server", + "examples/spawn-and-move", ] [workspace.package] diff --git a/crates/dojo-core/Cargo.toml b/crates/dojo-core/Cargo.toml new file mode 100644 index 0000000000..803b077f0b --- /dev/null +++ b/crates/dojo-core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "dojo-core" +repository.workspace = true +version.workspace = true + +[package.metadata.release] +pre-release-replacements = [ + { file = "Scarb.lock", search = "^name = \"dojo\"\nversion = \".*\"$", replace = "name = \"dojo\"\nversion = \"{{version}}\"", min = 1 }, + { file = "Scarb.toml", search = "^version = \".*\"$", replace = "version = \"{{version}}\"", min = 1 }, +] + +[lib] +path = "src/lib.rs" diff --git a/crates/dojo-core/src/lib.rs b/crates/dojo-core/src/lib.rs new file mode 100644 index 0000000000..4199cceb5f --- /dev/null +++ b/crates/dojo-core/src/lib.rs @@ -0,0 +1 @@ +// Placeholder so we can use `cargo release` to update Scarb.toml diff --git a/examples/spawn-and-move/Cargo.toml b/examples/spawn-and-move/Cargo.toml new file mode 100644 index 0000000000..1e257978ef --- /dev/null +++ b/examples/spawn-and-move/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "dojo-examples-spawn-and-move" +repository.workspace = true +version.workspace = true + +[package.metadata.release] +pre-release-replacements = [ + { file = "Scarb.lock", search = "^name = \"dojo\"\nversion = \".*\"$", replace = "name = \"dojo\"\nversion = \"{{version}}\"", min = 1 }, + { file = "Scarb.toml", search = "^version = \".*\"$", replace = "version = \"{{version}}\"", min = 1 }, +] + +[lib] +path = "src/lib.rs" diff --git a/examples/spawn-and-move/src/lib.rs b/examples/spawn-and-move/src/lib.rs new file mode 100644 index 0000000000..4199cceb5f --- /dev/null +++ b/examples/spawn-and-move/src/lib.rs @@ -0,0 +1 @@ +// Placeholder so we can use `cargo release` to update Scarb.toml diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh deleted file mode 100755 index def15d4949..0000000000 --- a/scripts/prepare_release.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -euxo pipefail - -prev_version=$(cargo get workspace.package.version) -next_version=$1 - -find . -type f -name "*.toml" -exec sed -i "" "s/version = \"$prev_version\"/version = \"$next_version\"/g" {} \; -find . -type f -name "*.toml" -exec sed -i "" "s/dojo_plugin = \"$prev_version\"/dojo_plugin = \"$next_version\"/g" {} \; - -scripts/clippy.sh - -git commit -am "Prepare v$1" -git tag -a "v$1" -m "Version $1" -# git push origin --tags \ No newline at end of file From 5cc0589a4a6755e068d9bddb0fa32b1926709839 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 16:24:48 -0500 Subject: [PATCH 165/192] Fix action names for consistency --- .github/workflows/cargo-udeps.yml | 2 +- .github/workflows/release-dispatch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cargo-udeps.yml b/.github/workflows/cargo-udeps.yml index 0e0ebdd7a3..656b097b03 100644 --- a/.github/workflows/cargo-udeps.yml +++ b/.github/workflows/cargo-udeps.yml @@ -1,4 +1,4 @@ -name: Unused dependencies +name: cargo-udeps on: schedule: diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 2d8eee4717..94e45b3606 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -1,4 +1,4 @@ -name: Test release-rust +name: release-dispatch on: workflow_dispatch: inputs: From 67cfbfb4cd3bbc3c8239cf7444b1f35e9fc7aaf5 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 16:37:01 -0500 Subject: [PATCH 166/192] Fix release dispatch --- .github/workflows/release-dispatch.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 94e45b3606..ae20767efd 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -22,3 +22,4 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.version }} + crate-release-all: true From 890bbced1ac8745cbcfe6c67a30730fa03808b40 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 16:42:32 -0500 Subject: [PATCH 167/192] Sync crate versions with workspace --- Cargo.lock | 10 +++++----- crates/benches/Cargo.toml | 2 +- crates/katana/runner/Cargo.toml | 4 ++-- crates/metrics/Cargo.toml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31c5320c5d..c0d2ef86ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,7 +801,7 @@ dependencies = [ [[package]] name = "benches" -version = "0.1.0" +version = "0.4.1" dependencies = [ "anyhow", "clap_builder", @@ -5401,7 +5401,7 @@ dependencies = [ "console", "katana-core", "katana-rpc", - "metrics 0.1.0", + "metrics 0.4.1", "metrics-process", "serde_json", "starknet_api", @@ -5598,7 +5598,7 @@ dependencies = [ [[package]] name = "katana-runner" -version = "0.1.0" +version = "0.4.1" dependencies = [ "anyhow", "home", @@ -5889,7 +5889,7 @@ dependencies = [ [[package]] name = "metrics" -version = "0.1.0" +version = "0.4.1" dependencies = [ "anyhow", "hyper", @@ -9910,7 +9910,7 @@ dependencies = [ "hyper-reverse-proxy", "indexmap 1.9.3", "lazy_static", - "metrics 0.1.0", + "metrics 0.4.1", "metrics-process", "scarb", "serde", diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml index 010ab448f7..4a8720e2bd 100644 --- a/crates/benches/Cargo.toml +++ b/crates/benches/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "benches" -version = "0.1.0" +version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/katana/runner/Cargo.toml b/crates/katana/runner/Cargo.toml index e74d612b43..a17bb83616 100644 --- a/crates/katana/runner/Cargo.toml +++ b/crates/katana/runner/Cargo.toml @@ -1,7 +1,7 @@ [package] +edition.workspace = true name = "katana-runner" -version = "0.1.0" -edition = "2021" +version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index 4e740cd611..d351b39341 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -1,7 +1,7 @@ [package] -edition = "2021" +edition.workspace = true name = "metrics" -version = "0.1.0" +version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 54a949910aeda279c835685c24e2b14d00908a19 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 17:19:05 -0500 Subject: [PATCH 168/192] Simplify release dispatch --- .cargo/config.toml | 2 -- .github/workflows/release-dispatch.yml | 13 ++++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 8595a0cb48..0000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[registries.crates-io] -protocol = "git" \ No newline at end of file diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index ae20767efd..5e605a9b6f 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -10,7 +10,6 @@ on: jobs: release: permissions: - id-token: write # Enable OIDC pull-requests: write contents: write runs-on: ubuntu-latest @@ -18,8 +17,12 @@ jobs: image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: - uses: actions/checkout@v3 - - uses: cargo-bins/release-pr@v2 + - run: cargo release ${{ inputs.version }} --no-publish --no-verify --execute + - uses: peter-evans/create-pull-request@v3 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ inputs.version }} - crate-release-all: true + token: ${{ secrets.GITHUB_TOKEN }} + title: "Prepare release: ${{ inputs.version }}" + commit-message: "Prepare release: ${{ inputs.version }}" + branch: prepare-release + base: main + delete-branch: true From a5cd5537e06e74145dd01a8acfff71c73839f640 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 17:22:08 -0500 Subject: [PATCH 169/192] Update devcontainer ci to not match self --- .github/workflows/devcontainer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index c67c99e18b..83269f822a 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -69,12 +69,12 @@ jobs: uses: actions/checkout@v2 - name: Update devcontainer.json - run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json + run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - name: Update github action devcontainers run: | for file in .github/workflows/*.yml; do - sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" + sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" done - name: Create Pull Request From 32e6bb0ea91be6f32c79587d1c0f468571c0ddba Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 17:24:38 -0500 Subject: [PATCH 170/192] Set safe repo for release dispatcher --- .github/workflows/release-dispatch.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 5e605a9b6f..6f06d3c61c 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -16,6 +16,8 @@ jobs: container: image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: + # Workaround described here: https://github.com/actions/checkout/issues/760 + - run: git config --global --add safe.directory /__w/dojo/dojo/ - uses: actions/checkout@v3 - run: cargo release ${{ inputs.version }} --no-publish --no-verify --execute - uses: peter-evans/create-pull-request@v3 From 9a0421fde06ef348a88de29418bebd30bb13cc3f Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 17:43:13 -0500 Subject: [PATCH 171/192] Use version + replace rather then release --- .github/workflows/release-dispatch.yml | 6 +-- Cargo.lock | 60 +++++++++++++------------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 6f06d3c61c..bcaef6e148 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -17,10 +17,10 @@ jobs: image: ghcr.io/dojoengine/dojo-dev:f6a4b8a steps: # Workaround described here: https://github.com/actions/checkout/issues/760 - - run: git config --global --add safe.directory /__w/dojo/dojo/ - uses: actions/checkout@v3 - - run: cargo release ${{ inputs.version }} --no-publish --no-verify --execute - - uses: peter-evans/create-pull-request@v3 + - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm + - uses: peter-evans/create-pull-request@v5 with: token: ${{ secrets.GITHUB_TOKEN }} title: "Prepare release: ${{ inputs.version }}" diff --git a/Cargo.lock b/Cargo.lock index c0d2ef86ef..2a8b652c6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,7 +801,7 @@ dependencies = [ [[package]] name = "benches" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "clap_builder", @@ -2623,15 +2623,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-core" -version = "0.4.1" +version = "0.4.2" [[package]] name = "dojo-examples-spawn-and-move" -version = "0.4.1" +version = "0.4.2" [[package]] name = "dojo-lang" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2679,7 +2679,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "starknet", @@ -2709,7 +2709,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -2740,7 +2740,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.1" +version = "0.4.2" dependencies = [ "crypto-bigint", "hex", @@ -2755,7 +2755,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -5393,7 +5393,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.1" +version = "0.4.2" dependencies = [ "assert_matches", "clap", @@ -5401,7 +5401,7 @@ dependencies = [ "console", "katana-core", "katana-rpc", - "metrics 0.4.1", + "metrics 0.4.2", "metrics-process", "serde_json", "starknet_api", @@ -5413,7 +5413,7 @@ dependencies = [ [[package]] name = "katana-codecs" -version = "0.4.1" +version = "0.4.2" dependencies = [ "bytes", "katana-primitives", @@ -5421,7 +5421,7 @@ dependencies = [ [[package]] name = "katana-codecs-derive" -version = "0.4.1" +version = "0.4.2" dependencies = [ "proc-macro2", "quote", @@ -5431,7 +5431,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_matches", @@ -5464,7 +5464,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5487,7 +5487,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "blockifier", @@ -5503,7 +5503,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "blockifier", @@ -5521,7 +5521,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "auto_impl", @@ -5541,7 +5541,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_matches", @@ -5573,7 +5573,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "derive_more", @@ -5586,7 +5586,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "katana-executor", @@ -5598,7 +5598,7 @@ dependencies = [ [[package]] name = "katana-runner" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "home", @@ -5889,7 +5889,7 @@ dependencies = [ [[package]] name = "metrics" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "hyper", @@ -8558,7 +8558,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -9752,7 +9752,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "camino", @@ -9778,7 +9778,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-trait", @@ -9814,7 +9814,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-graphql", @@ -9853,7 +9853,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.1" +version = "0.4.2" dependencies = [ "bytes", "crypto-bigint", @@ -9891,7 +9891,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-trait", @@ -9910,7 +9910,7 @@ dependencies = [ "hyper-reverse-proxy", "indexmap 1.9.3", "lazy_static", - "metrics 0.4.1", + "metrics 0.4.2", "metrics-process", "scarb", "serde", diff --git a/Cargo.toml b/Cargo.toml index e3789f8627..bedca48a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.1" +version = "0.4.2" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index ee17c212ea..14d33d1dbb 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index a8809ef181..ef1080e43a 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.1" +version = "0.4.2" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 9474cc125c..36fbad6d54 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index ec78dec2d8..2383af1a8e 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "dojo_examples" -version = "0.4.0-rc0" +version = "0.4.2" # Use the prelude with the less imports as possible # from corelib. edition = "2023_10" From 9c6be5b6a5fb7a7f86d98e62f7c489b41e0d2a3f Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 20:34:40 -0500 Subject: [PATCH 172/192] Install cargo-get in devcontainer --- .devcontainer/Dockerfile | 2 +- .github/workflows/devcontainer.yml | 10 ++++++---- .github/workflows/release-dispatch.yml | 5 +++-- .github/workflows/release.yml | 3 --- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 39ed05f851..4b3c3e20c4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -27,7 +27,7 @@ RUN rustup toolchain install nightly && \ RUN rustup target add x86_64-pc-windows-msvc && \ rustup target add wasm32-unknown-unknown -RUN cargo binstall cargo-nextest cargo-llvm-cov cargo-release --secure -y +RUN cargo binstall cargo-get cargo-nextest cargo-llvm-cov cargo-release --secure -y # Platform specific tooling ARG TARGETPLATFORM diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 83269f822a..85bc551e9c 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -63,10 +63,12 @@ jobs: needs: build-and-push runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + pull-requests: write + contents: write steps: - - name: Checkout repository - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: Update devcontainer.json run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json @@ -77,9 +79,9 @@ jobs: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" done - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 + - uses: peter-evans/create-pull-request@v5 with: + # We have to use a PAT in order to trigger ci token: ${{ secrets.CREATE_PR_TOKEN }} title: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" commit-message: "Update devcontainer image hash: ${{ needs.build-and-push.outputs.tag_name }}" diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index bcaef6e148..0f1eafc57d 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -8,7 +8,7 @@ on: type: string jobs: - release: + propose-release: permissions: pull-requests: write contents: write @@ -22,7 +22,8 @@ jobs: - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm - uses: peter-evans/create-pull-request@v5 with: - token: ${{ secrets.GITHUB_TOKEN }} + # We have to use a PAT in order to trigger ci + token: ${{ secrets.CREATE_PR_TOKEN }} title: "Prepare release: ${{ inputs.version }}" commit-message: "Prepare release: ${{ inputs.version }}" branch: prepare-release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef0597e95e..a1af6f7d2f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -252,9 +252,6 @@ jobs: name: binaries path: artifacts/linux - - name: Display structure of downloaded files - run: ls -R - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 From 5ab298bd681dbea453040908def9a22212d5f602 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 20:41:44 -0500 Subject: [PATCH 173/192] Install gh and fix hurl in devcontainer --- .devcontainer/Dockerfile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4b3c3e20c4..29720815d3 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/rust:${VARIANT} RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends protobuf-compiler libprotobuf-dev libclang-dev -RUN apt install -y libgmp3-dev +RUN apt install -y gh libgmp3-dev COPY rust-toolchain.toml . @@ -33,11 +33,17 @@ RUN cargo binstall cargo-get cargo-nextest cargo-llvm-cov cargo-release --secure ARG TARGETPLATFORM RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ curl -L https://github.com/Orange-OpenSource/hurl/releases/download/4.1.0/hurl-4.1.0-aarch64-unknown-linux-gnu.tar.gz -o hurl.tar.gz && \ - tar -xzf hurl.tar.gz -C /usr/local/bin && rm hurl.tar.gz && \ + tar -xzf hurl.tar.gz && \ + mv hurl-4.1.0-aarch64-unknown-linux-gnu/hurl /usr/local/bin/ && \ + rm -r hurl-4.1.0-aarch64-unknown-linux-gnu && \ + rm hurl.tar.gz && \ rustup component add llvm-tools-preview --toolchain 1.70.0-aarch64-unknown-linux-gnu; \ elif [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ curl -L https://github.com/Orange-OpenSource/hurl/releases/download/4.1.0/hurl-4.1.0-x86_64-unknown-linux-gnu.tar.gz -o hurl.tar.gz && \ - tar -xzf hurl.tar.gz -C /usr/local/bin && rm hurl.tar.gz && \ + tar -xzf hurl.tar.gz && \ + mv hurl-4.1.0-x86_64-unknown-linux-gnu/hurl /usr/local/bin/ && \ + rm -r hurl-4.1.0-x86_64-unknown-linux-gnu && \ + rm hurl.tar.gz && \ rustup component add llvm-tools-preview --toolchain 1.70.0-x86_64-unknown-linux-gnu && \ rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly; \ fi From 448ffdad3aa43c4b013f7a26b4251dcc6c3d1c65 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 21:15:02 -0500 Subject: [PATCH 174/192] Fix devcontainer update regex --- .devcontainer/devcontainer.json | 6 +----- .github/workflows/devcontainer.yml | 6 ++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 07a8709a21..7f3c1db4b2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,13 +34,9 @@ }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "rustc --version", - // "postCreateCommand": ". scripts/startup.sh", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "vscode", "remoteEnv": { "PATH": "${containerEnv:PATH}:/workspace/dojo/target/release:/home/vscode/.dojo/bin" - }, - "postCreateCommand": "dojoup/dojoup && cargo install cargo-nextest" + } } \ No newline at end of file diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 85bc551e9c..2f238d2789 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -71,12 +71,14 @@ jobs: - uses: actions/checkout@v2 - name: Update devcontainer.json - run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json + run: sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" .devcontainer/devcontainer.json - name: Update github action devcontainers run: | for file in .github/workflows/*.yml; do - sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]+|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" + if [[ $file != ".github/workflows/devcontainer.yml" ]]; then + sed -i "s|ghcr.io/dojoengine/dojo-dev:[a-zA-Z0-9._-]*|ghcr.io/dojoengine/dojo-dev:${{ needs.build-and-push.outputs.tag_name }}|" "$file" + fi done - uses: peter-evans/create-pull-request@v5 From e8a556f267206f3f46d5894d79bb93c783b3309b Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 21:48:50 -0500 Subject: [PATCH 175/192] Update devcontainer image hash: 448ffda (#1313) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/release-dispatch.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7f3c1db4b2..08adc14f5d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", - "image": "ghcr.io/dojoengine/dojo-dev:2e8cd62", + "image": "ghcr.io/dojoengine/dojo-dev:448ffda", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fc1e4c1e5..f1d949da2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: test: runs-on: ubuntu-latest-16-cores container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 with: @@ -30,7 +30,7 @@ jobs: ensure-wasm: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -63,7 +63,7 @@ jobs: dojo-core-test: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -72,7 +72,7 @@ jobs: dojo-spawn-and-move-example-test: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -81,7 +81,7 @@ jobs: clippy: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 with: @@ -92,7 +92,7 @@ jobs: fmt: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -101,7 +101,7 @@ jobs: docs: runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:2e8cd62 + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 0f1eafc57d..60585445d5 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -14,7 +14,7 @@ jobs: contents: write runs-on: ubuntu-latest container: - image: ghcr.io/dojoengine/dojo-dev:f6a4b8a + image: ghcr.io/dojoengine/dojo-dev:448ffda steps: # Workaround described here: https://github.com/actions/checkout/issues/760 - uses: actions/checkout@v3 From 065dcc961c12523f92e88d76976554a87d179da6 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 18 Dec 2023 18:51:52 -0800 Subject: [PATCH 176/192] Move types-test to torii root (#1311) --- crates/dojo-test-utils/build.rs | 2 +- crates/torii/graphql/src/tests/mod.rs | 14 +++++++++----- .../{graphql/src/tests => }/types-test/Scarb.lock | 2 +- .../{graphql/src/tests => }/types-test/Scarb.toml | 2 +- .../src/tests => }/types-test/assets/cover.png | Bin .../src/tests => }/types-test/assets/icon.png | Bin .../src/tests => }/types-test/src/contracts.cairo | 0 .../src/tests => }/types-test/src/lib.cairo | 0 .../src/tests => }/types-test/src/models.cairo | 0 9 files changed, 12 insertions(+), 8 deletions(-) rename crates/torii/{graphql/src/tests => }/types-test/Scarb.lock (94%) rename crates/torii/{graphql/src/tests => }/types-test/Scarb.toml (93%) rename crates/torii/{graphql/src/tests => }/types-test/assets/cover.png (100%) rename crates/torii/{graphql/src/tests => }/types-test/assets/icon.png (100%) rename crates/torii/{graphql/src/tests => }/types-test/src/contracts.cairo (100%) rename crates/torii/{graphql/src/tests => }/types-test/src/lib.cairo (100%) rename crates/torii/{graphql/src/tests => }/types-test/src/models.cairo (100%) diff --git a/crates/dojo-test-utils/build.rs b/crates/dojo-test-utils/build.rs index 3f6b9592f8..f80c57027d 100644 --- a/crates/dojo-test-utils/build.rs +++ b/crates/dojo-test-utils/build.rs @@ -11,7 +11,7 @@ fn main() { use scarb::ops::CompileOpts; use scarb_ui::Verbosity; - let project_paths = ["../../examples/spawn-and-move", "../torii/graphql/src/tests/types-test"]; + let project_paths = ["../../examples/spawn-and-move", "../torii/types-test"]; project_paths.iter().for_each(|path| compile(path)); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index d6abd80668..793ece1473 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -10,6 +10,7 @@ use dojo_test_utils::sequencer::{ use dojo_types::primitive::Primitive; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; use dojo_world::contracts::WorldContractReader; +use dojo_world::manifest::Manifest; use dojo_world::utils::TransactionWaiter; use scarb::ops; use serde::Deserialize; @@ -236,8 +237,8 @@ pub async fn spinup_types_test() -> Result { let pool = SqlitePoolOptions::new().max_connections(5).connect_with(options).await.unwrap(); sqlx::migrate!("../migrations").run(&pool).await.unwrap(); - let migration = prepare_migration("./src/tests/types-test/target/dev".into()).unwrap(); - let config = build_test_config("./src/tests/types-test/Scarb.toml").unwrap(); + let migration = prepare_migration("../types-test/target/dev".into()).unwrap(); + let config = build_test_config("../types-test/Scarb.toml").unwrap(); let mut db = Sql::new(pool.clone(), migration.world_address().unwrap()).await.unwrap(); let sequencer = @@ -253,13 +254,16 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); - // Execute `create` and insert 10 records into storage + let manifest = + Manifest::load_from_remote(&provider, migration.world_address().unwrap()).await.unwrap(); - let records_contract = "0x7b44a597f4027588f226293105c77c99c436ab4016bbcb51f6711ab1ccfeeb0"; + // Execute `create` and insert 10 records into storage + let records_contract = + manifest.contracts.iter().find(|contract| contract.name.eq("records")).unwrap(); let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], - to: FieldElement::from_str(records_contract).unwrap(), + to: records_contract.address.unwrap(), selector: selector!("create"), }]) .send() diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock similarity index 94% rename from crates/torii/graphql/src/tests/types-test/Scarb.lock rename to crates/torii/types-test/Scarb.lock index a61afd0460..4f2d7aa41c 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.0-rc0" +version = "0.4.1" dependencies = [ "dojo_plugin", ] diff --git a/crates/torii/graphql/src/tests/types-test/Scarb.toml b/crates/torii/types-test/Scarb.toml similarity index 93% rename from crates/torii/graphql/src/tests/types-test/Scarb.toml rename to crates/torii/types-test/Scarb.toml index 2b149d7306..28eb076fb8 100644 --- a/crates/torii/graphql/src/tests/types-test/Scarb.toml +++ b/crates/torii/types-test/Scarb.toml @@ -7,7 +7,7 @@ version = "0.1.0" sierra-replace-ids = true [dependencies] -dojo = { path = "../../../../../dojo-core" } +dojo = { path = "../../dojo-core" } [[target.dojo]] build-external-contracts = [] diff --git a/crates/torii/graphql/src/tests/types-test/assets/cover.png b/crates/torii/types-test/assets/cover.png similarity index 100% rename from crates/torii/graphql/src/tests/types-test/assets/cover.png rename to crates/torii/types-test/assets/cover.png diff --git a/crates/torii/graphql/src/tests/types-test/assets/icon.png b/crates/torii/types-test/assets/icon.png similarity index 100% rename from crates/torii/graphql/src/tests/types-test/assets/icon.png rename to crates/torii/types-test/assets/icon.png diff --git a/crates/torii/graphql/src/tests/types-test/src/contracts.cairo b/crates/torii/types-test/src/contracts.cairo similarity index 100% rename from crates/torii/graphql/src/tests/types-test/src/contracts.cairo rename to crates/torii/types-test/src/contracts.cairo diff --git a/crates/torii/graphql/src/tests/types-test/src/lib.cairo b/crates/torii/types-test/src/lib.cairo similarity index 100% rename from crates/torii/graphql/src/tests/types-test/src/lib.cairo rename to crates/torii/types-test/src/lib.cairo diff --git a/crates/torii/graphql/src/tests/types-test/src/models.cairo b/crates/torii/types-test/src/models.cairo similarity index 100% rename from crates/torii/graphql/src/tests/types-test/src/models.cairo rename to crates/torii/types-test/src/models.cairo From 85e92c3bc04d29b98fac62bcb3feeef51589cecc Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 18 Dec 2023 23:19:04 -0500 Subject: [PATCH 177/192] Add types-test to cargo release (#1314) --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/torii/types-test/Cargo.toml | 15 +++++++++++++++ crates/torii/types-test/Scarb.lock | 2 +- crates/torii/types-test/Scarb.toml | 2 +- crates/torii/types-test/src/lib.rs | 1 + 6 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 crates/torii/types-test/Cargo.toml create mode 100644 crates/torii/types-test/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2a8b652c6c..05748eb8b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10176,6 +10176,10 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "types-test" +version = "0.4.2" + [[package]] name = "ucd-trie" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index bedca48a7d..1ece9cfe98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/sozo", "crates/torii/client", "crates/torii/server", + "crates/torii/types-test", "examples/spawn-and-move", ] diff --git a/crates/torii/types-test/Cargo.toml b/crates/torii/types-test/Cargo.toml new file mode 100644 index 0000000000..5f67ac8803 --- /dev/null +++ b/crates/torii/types-test/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "types-test" +repository.workspace = true +version.workspace = true + +[package.metadata.release] +pre-release-replacements = [ + { file = "Scarb.lock", search = "^name = \"dojo\"\nversion = \".*\"$", replace = "name = \"dojo\"\nversion = \"{{version}}\"", min = 1 }, + { file = "Scarb.toml", search = "^version = \".*\"$", replace = "version = \"{{version}}\"", min = 1 }, +] + +[lib] +path = "src/lib.rs" diff --git a/crates/torii/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock index 4f2d7aa41c..736f84366b 100644 --- a/crates/torii/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/crates/torii/types-test/Scarb.toml b/crates/torii/types-test/Scarb.toml index 28eb076fb8..ac5bfa187f 100644 --- a/crates/torii/types-test/Scarb.toml +++ b/crates/torii/types-test/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "types_test" -version = "0.1.0" +version = "0.4.2" [cairo] sierra-replace-ids = true diff --git a/crates/torii/types-test/src/lib.rs b/crates/torii/types-test/src/lib.rs new file mode 100644 index 0000000000..4199cceb5f --- /dev/null +++ b/crates/torii/types-test/src/lib.rs @@ -0,0 +1 @@ +// Placeholder so we can use `cargo release` to update Scarb.toml From bc42b8eb1220f397420feba9f6315b854289733b Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 19 Dec 2023 22:42:33 +0800 Subject: [PATCH 178/192] fix(katana-provider): handle not found err as `None` (#1316) --- .../provider/src/providers/fork/backend.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/katana/storage/provider/src/providers/fork/backend.rs b/crates/katana/storage/provider/src/providers/fork/backend.rs index 4c4d3353f0..cfba328649 100644 --- a/crates/katana/storage/provider/src/providers/fork/backend.rs +++ b/crates/katana/storage/provider/src/providers/fork/backend.rs @@ -415,10 +415,18 @@ impl ContractClassProvider for SharedStateProvider { return Ok(hash.cloned()); } - let compiled_hash = self.0.do_get_compiled_class_hash(hash)?; - self.0.compiled_class_hashes.write().insert(hash, compiled_hash); - - Ok(Some(hash)) + if let Some(hash) = + handle_contract_or_class_not_found_err(self.0.do_get_compiled_class_hash(hash)) + .map_err(|e| { + error!(target: "forked_backend", "error while fetching compiled class hash for class hash {hash:#x}: {e}"); + e + })? + { + self.0.compiled_class_hashes.write().insert(hash, hash); + Ok(Some(hash)) + } else { + Ok(None) + } } fn class(&self, hash: ClassHash) -> Result> { From 90fa4f32a564fa33d2ff825a7cbd2e8da443c9c7 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 19 Dec 2023 14:50:08 -0500 Subject: [PATCH 179/192] Remove nightly releases (#1315) --- .github/scripts/create-tag.js | 14 --- .github/scripts/move-tag.js | 15 ---- .github/scripts/prune-prereleases.js | 39 --------- .github/workflows/ci.yml | 6 -- .github/workflows/release-dispatch.yml | 3 + .github/workflows/release.yml | 115 +++---------------------- dojoup/dojoup | 7 +- 7 files changed, 18 insertions(+), 181 deletions(-) delete mode 100644 .github/scripts/create-tag.js delete mode 100644 .github/scripts/move-tag.js delete mode 100644 .github/scripts/prune-prereleases.js diff --git a/.github/scripts/create-tag.js b/.github/scripts/create-tag.js deleted file mode 100644 index 6ecd0bc9a8..0000000000 --- a/.github/scripts/create-tag.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = async ({ github, context }, tagName) => { - try { - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/tags/${tagName}`, - sha: context.sha, - force: true, - }); - } catch (err) { - console.error(`Failed to create tag: ${tagName}`); - console.error(err); - } -}; \ No newline at end of file diff --git a/.github/scripts/move-tag.js b/.github/scripts/move-tag.js deleted file mode 100644 index 580fa58e75..0000000000 --- a/.github/scripts/move-tag.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = async ({ github, context }, tagName) => { - try { - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${tagName}`, - sha: context.sha, - force: true, - }); - } catch (err) { - console.error(`Failed to move nightly tag.`); - console.error(`This should only happen the first time.`); - console.error(err); - } -}; \ No newline at end of file diff --git a/.github/scripts/prune-prereleases.js b/.github/scripts/prune-prereleases.js deleted file mode 100644 index 0537fbf138..0000000000 --- a/.github/scripts/prune-prereleases.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = async ({ github, context }) => { - console.log("Pruning old prereleases"); - - // doc: https://docs.github.com/en/rest/releases/releases - const { data: releases } = await github.rest.repos.listReleases({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - - let nightlies = releases.filter( - release => - // Only consider releases tagged `nightly-${SHA}` for deletion - release.tag_name.includes("nightly") && - release.tag_name !== "nightly" && - // ref: https://github.com/foundry-rs/foundry/issues/3881 - // Skipping pruning the build on 1st day of each month - !release.created_at.includes("-01T") - ); - - // Keep newest 3 nightlies - nightlies = nightlies.slice(3); - - for (const nightly of nightlies) { - console.log(`Deleting nightly: ${nightly.tag_name}`); - await github.rest.repos.deleteRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: nightly.id, - }); - console.log(`Deleting nightly tag: ${nightly.tag_name}`); - await github.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${nightly.tag_name}`, - }); - } - - console.log("Done."); -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1d949da2a..abd5c036bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,6 @@ jobs: image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: Swatinem/rust-cache@v2 - run: cargo llvm-cov nextest --all-features --lcov --output-path lcov.info - uses: codecov/codecov-action@v3 @@ -84,8 +82,6 @@ jobs: image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: Swatinem/rust-cache@v2 - run: scripts/clippy.sh @@ -104,8 +100,6 @@ jobs: image: ghcr.io/dojoengine/dojo-dev:448ffda steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: Swatinem/rust-cache@v2 - run: > scripts/docs.sh diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 60585445d5..b0917f9f44 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -20,6 +20,8 @@ jobs: - uses: actions/checkout@v3 - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm + - id: changelog + uses: mikepenz/release-changelog-builder-action@v4.1.0 - uses: peter-evans/create-pull-request@v5 with: # We have to use a PAT in order to trigger ci @@ -29,3 +31,4 @@ jobs: branch: prepare-release base: main delete-branch: true + body: ${{steps.changelog.outputs.changelog}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1af6f7d2f..fe9d0d75a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,81 +1,29 @@ name: release on: - push: - tags: - - "*" - schedule: - - cron: "0 0 * * *" - workflow_dispatch: + pull_request: + types: [closed] + branches: + - main env: - IS_NIGHTLY: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'true' || 'false' }} CARGO_TERM_COLOR: always RUST_VERSION: 1.70.0 REGISTRY_IMAGE: ghcr.io/${{ github.repository }} jobs: prepare: - name: Prepare release + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release' runs-on: ubuntu-latest - outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} - release_name: ${{ steps.release_info.outputs.release_name }} - changelog: ${{ steps.build_changelog.outputs.changelog }} - steps: - - name: Checkout sources - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Compute release name and tag + - uses: actions/checkout@v3 + - name: Get version id: release_info run: | - if [[ $IS_NIGHTLY == "true" ]]; then - echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT - echo "release_name=Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT - else - echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT - echo "release_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT - fi - - - name: Check version - run: | - if [[ $IS_NIGHTLY != "true" ]]; then - cargo install cargo-get - VERSION=$(cargo get workspace.package.version) - TAG=${{ steps.release_info.outputs.tag_name }} - if [[ "v$VERSION" != "$TAG" ]]; then - echo "Version in Cargo.toml ($VERSION) does not match release tag ($TAG)" - exit 1 - fi - fi - - # Creates a `nightly-SHA` tag for this specific nightly - # This tag is used for this specific nightly version's release - # which allows users to roll back. It is also used to build - # the changelog. - - name: Create build-specific nightly tag - if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v5 - env: - TAG_NAME: ${{ steps.release_info.outputs.tag_name }} - with: - script: | - const createTag = require('./.github/scripts/create-tag.js') - await createTag({ github, context }, process.env.TAG_NAME) - - - name: Build changelog - id: build_changelog - uses: mikepenz/release-changelog-builder-action@v2 - with: - configuration: "./.github/changelog.json" - fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || '' }} - toTag: ${{ steps.release_info.outputs.tag_name }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + cargo install cargo-get + echo "tag_name=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT release: name: ${{ matrix.job.target }} (${{ matrix.job.os }}) @@ -152,7 +100,7 @@ jobs: - name: Archive binaries id: artifacts env: - VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + VERSION_NAME: ${{ needs.prepare.outputs.tag_name }} run: | if [ "$PLATFORM_NAME" == "linux" ]; then tar -czvf "dojo_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release katana sozo torii dojo-language-server @@ -174,23 +122,9 @@ jobs: - name: Create release uses: softprops/action-gh-release@v1 with: - name: ${{ needs.prepare.outputs.release_name }} + name: ${{ github.event.pull_request.title }} tag_name: ${{ needs.prepare.outputs.tag_name }} - prerelease: ${{ env.IS_NIGHTLY == 'true' }} - body: ${{ needs.prepare.outputs.changelog }} - files: | - ${{ steps.artifacts.outputs.file_name }} - - # If this is a nightly release, it also updates the release - # tagged `nightly` for compatibility with `dojoup` - - name: Update nightly release - if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@v1 - with: - name: "Nightly" - tag_name: "nightly" - prerelease: true - body: ${{ needs.prepare.outputs.changelog }} + body: ${{ github.event.pull_request.body }} files: | ${{ steps.artifacts.outputs.file_name }} @@ -212,31 +146,6 @@ jobs: path: ${{ env.PLATFORM_NAME }} retention-days: 1 - cleanup: - name: Release cleanup - runs-on: ubuntu-latest - needs: release - - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - # Moves the `nightly` tag to `HEAD` - - name: Move nightly tag - if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v5 - with: - script: | - const moveTag = require('./.github/scripts/move-tag.js') - await moveTag({ github, context }, 'nightly') - - - name: Delete old nightlies - uses: actions/github-script@v5 - with: - script: | - const prunePrereleases = require('./.github/scripts/prune-prereleases.js') - await prunePrereleases({github, context}) - docker-build-and-push: name: Build and push docker image runs-on: ubuntu-latest-4-cores diff --git a/dojoup/dojoup b/dojoup/dojoup index a0e7e2c871..f07f251028 100755 --- a/dojoup/dojoup +++ b/dojoup/dojoup @@ -88,8 +88,6 @@ main() { | tr -d '"' \ | head -n 1) DOJOUP_VERSION=$DOJOUP_TAG - elif [[ "$DOJOUP_VERSION" == nightly* ]]; then - DOJOUP_VERSION="nightly" elif [[ "$DOJOUP_VERSION" == [[:digit:]]* ]]; then # Add v prefix DOJOUP_VERSION="v${DOJOUP_VERSION}" @@ -288,8 +286,9 @@ banner() { Repo : https://github.com/dojoengine/dojo - Book : https://book.dojoengine.org/ - Chat : https://t.me/+DJxNYR3rsfJmZTg1 + Book : https://book.dojoengine.org/ + Chat : https://discord.gg/dojoengine + https://t.me/dojoengine ═════════════════════════════════════════════════════════════════════════ From c6dab678a2fc129869fceb5ab487d9d3aed56898 Mon Sep 17 00:00:00 2001 From: Yun Date: Tue, 19 Dec 2023 11:56:54 -0800 Subject: [PATCH 180/192] Torii grpc member clause query (#1312) --- Cargo.lock | 1 + crates/torii/grpc/Cargo.toml | 1 + crates/torii/grpc/src/server/mod.rs | 61 ++++++++++++++++++++++++++--- crates/torii/grpc/src/types/mod.rs | 38 +++++++++++++++++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05748eb8b5..039efe4190 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9872,6 +9872,7 @@ dependencies = [ "sqlx", "starknet", "starknet-crypto 0.6.1", + "strum 0.25.0", "strum_macros 0.25.3", "thiserror", "tokio", diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml index b1101c82fc..24987b5534 100644 --- a/crates/torii/grpc/Cargo.toml +++ b/crates/torii/grpc/Cargo.toml @@ -18,6 +18,7 @@ thiserror.workspace = true torii-core = { path = "../core", optional = true } serde.workspace = true +strum.workspace = true strum_macros.workspace = true crypto-bigint.workspace = true diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 8216990e27..a6c3625a28 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -34,6 +34,7 @@ use crate::proto::types::clause::ClauseType; use crate::proto::world::world_server::WorldServer; use crate::proto::world::{SubscribeEntitiesRequest, SubscribeEntityResponse}; use crate::proto::{self}; +use crate::types::ComparisonOperator; #[derive(Clone)] pub struct DojoWorld { @@ -242,14 +243,62 @@ impl DojoWorld { db_entities.iter().map(|row| Self::map_row_to_entity(row, &schemas)).collect() } - async fn entities_by_attribute( + async fn entities_by_member( &self, - _attribute: proto::types::MemberClause, + member_clause: proto::types::MemberClause, _limit: u32, _offset: u32, ) -> Result, Error> { - // TODO: Implement - Err(QueryError::UnsupportedQuery.into()) + let comparison_operator = ComparisonOperator::from_repr(member_clause.operator as usize) + .expect("invalid comparison operator"); + + let value_type = member_clause + .value + .ok_or(QueryError::MissingParam("value".into()))? + .value_type + .ok_or(QueryError::MissingParam("value_type".into()))?; + + let comparison_value = match value_type { + proto::types::value::ValueType::StringValue(string) => string, + proto::types::value::ValueType::IntValue(int) => int.to_string(), + proto::types::value::ValueType::UintValue(uint) => uint.to_string(), + proto::types::value::ValueType::BoolValue(bool) => { + if bool { + "1".to_string() + } else { + "0".to_string() + } + } + _ => return Err(QueryError::UnsupportedQuery.into()), + }; + + let models_query = format!( + r#" + SELECT group_concat(entity_model.model_id) as model_names + FROM entities + JOIN entity_model ON entities.id = entity_model.entity_id + GROUP BY entities.id + HAVING model_names REGEXP '(^|,){}(,|$)' + LIMIT 1 + "#, + member_clause.model + ); + let (models_str,): (String,) = sqlx::query_as(&models_query).fetch_one(&self.pool).await?; + + let model_names = models_str.split(',').collect::>(); + let schemas = self.model_cache.schemas(model_names).await?; + + let table_name = member_clause.model; + let column_name = format!("external_{}", member_clause.member); + let member_query = format!( + "{} WHERE {table_name}.{column_name} {comparison_operator} ?", + build_sql_query(&schemas)? + ); + + let db_entities = + sqlx::query(&member_query).bind(comparison_value).fetch_all(&self.pool).await?; + + db_entities.iter().map(|row| Self::map_row_to_entity(row, &schemas)).collect() } async fn entities_by_composite( @@ -350,8 +399,8 @@ impl DojoWorld { self.entities_by_keys(keys, query.limit, query.offset).await? } - ClauseType::Member(attribute) => { - self.entities_by_attribute(attribute, query.limit, query.offset).await? + ClauseType::Member(member) => { + self.entities_by_member(member, query.limit, query.offset).await? } ClauseType::Composite(composite) => { self.entities_by_composite(composite, query.limit, query.offset).await? diff --git a/crates/torii/grpc/src/types/mod.rs b/crates/torii/grpc/src/types/mod.rs index da97af938b..bc18d7f5cf 100644 --- a/crates/torii/grpc/src/types/mod.rs +++ b/crates/torii/grpc/src/types/mod.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::collections::HashMap; use std::str::FromStr; @@ -8,6 +9,7 @@ use starknet::core::types::{ ContractStorageDiffItem, FromByteSliceError, FromStrError, StateDiff, StateUpdate, StorageEntry, }; use starknet_crypto::FieldElement; +use strum_macros::{AsRefStr, EnumIter, FromRepr}; use crate::proto::{self}; @@ -48,13 +50,19 @@ pub struct CompositeClause { pub clauses: Vec, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +#[derive( + Debug, AsRefStr, Serialize, Deserialize, EnumIter, FromRepr, PartialEq, Hash, Eq, Clone, +)] +#[strum(serialize_all = "UPPERCASE")] pub enum LogicalOperator { And, Or, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] +#[derive( + Debug, AsRefStr, Serialize, Deserialize, EnumIter, FromRepr, PartialEq, Hash, Eq, Clone, +)] +#[strum(serialize_all = "UPPERCASE")] pub enum ComparisonOperator { Eq, Neq, @@ -64,6 +72,32 @@ pub enum ComparisonOperator { Lte, } +impl fmt::Display for ComparisonOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ComparisonOperator::Gt => write!(f, ">"), + ComparisonOperator::Gte => write!(f, ">="), + ComparisonOperator::Lt => write!(f, "<"), + ComparisonOperator::Lte => write!(f, "<="), + ComparisonOperator::Neq => write!(f, "!="), + ComparisonOperator::Eq => write!(f, "="), + } + } +} + +impl From for ComparisonOperator { + fn from(operator: proto::types::ComparisonOperator) -> Self { + match operator { + proto::types::ComparisonOperator::Eq => ComparisonOperator::Eq, + proto::types::ComparisonOperator::Gte => ComparisonOperator::Gte, + proto::types::ComparisonOperator::Gt => ComparisonOperator::Gt, + proto::types::ComparisonOperator::Lt => ComparisonOperator::Lt, + proto::types::ComparisonOperator::Lte => ComparisonOperator::Lte, + proto::types::ComparisonOperator::Neq => ComparisonOperator::Neq, + } + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Hash, Eq, Clone)] pub struct Value { pub primitive_type: Primitive, From e8a1c952041301ca079d7586f3bd462801aa9677 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 19 Dec 2023 15:12:08 -0500 Subject: [PATCH 181/192] Improve release dispatch content --- .github/workflows/release-dispatch.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index b0917f9f44..bb22b33d1c 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -19,15 +19,25 @@ jobs: # Workaround described here: https://github.com/actions/checkout/issues/760 - uses: actions/checkout@v3 - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - id: current_release_info + run: | + cargo install cargo-get + echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm - id: changelog uses: mikepenz/release-changelog-builder-action@v4.1.0 + with: + fromTag: v${{ steps.current_release_info.outputs.version }} + - id: next_release_info + run: | + cargo install cargo-get + echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - uses: peter-evans/create-pull-request@v5 with: # We have to use a PAT in order to trigger ci token: ${{ secrets.CREATE_PR_TOKEN }} - title: "Prepare release: ${{ inputs.version }}" - commit-message: "Prepare release: ${{ inputs.version }}" + title: "Prepare release: ${{ steps.next_release_info.outputs.version }}" + commit-message: "Prepare release: ${{ steps.next_release_info.outputs.version }}" branch: prepare-release base: main delete-branch: true From 5514ad2135bc87d80532204beb588d0a111e499f Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 19 Dec 2023 15:25:29 -0500 Subject: [PATCH 182/192] Revert Cargo version to 0.4.1 --- .github/workflows/release-dispatch.yml | 4 +- Cargo.lock | 62 +++++++++++++------------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- crates/torii/types-test/Scarb.lock | 2 +- crates/torii/types-test/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index bb22b33d1c..a2bea6eff3 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -36,8 +36,8 @@ jobs: with: # We have to use a PAT in order to trigger ci token: ${{ secrets.CREATE_PR_TOKEN }} - title: "Prepare release: ${{ steps.next_release_info.outputs.version }}" - commit-message: "Prepare release: ${{ steps.next_release_info.outputs.version }}" + title: "Prepare release: v${{ steps.next_release_info.outputs.version }}" + commit-message: "Prepare release: v${{ steps.next_release_info.outputs.version }}" branch: prepare-release base: main delete-branch: true diff --git a/Cargo.lock b/Cargo.lock index 039efe4190..c0bf341d33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,7 +801,7 @@ dependencies = [ [[package]] name = "benches" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "clap_builder", @@ -2623,15 +2623,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-core" -version = "0.4.2" +version = "0.4.1" [[package]] name = "dojo-examples-spawn-and-move" -version = "0.4.2" +version = "0.4.1" [[package]] name = "dojo-lang" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2679,7 +2679,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "starknet", @@ -2709,7 +2709,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -2740,7 +2740,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.2" +version = "0.4.1" dependencies = [ "crypto-bigint", "hex", @@ -2755,7 +2755,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -5393,7 +5393,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.2" +version = "0.4.1" dependencies = [ "assert_matches", "clap", @@ -5401,7 +5401,7 @@ dependencies = [ "console", "katana-core", "katana-rpc", - "metrics 0.4.2", + "metrics 0.4.1", "metrics-process", "serde_json", "starknet_api", @@ -5413,7 +5413,7 @@ dependencies = [ [[package]] name = "katana-codecs" -version = "0.4.2" +version = "0.4.1" dependencies = [ "bytes", "katana-primitives", @@ -5421,7 +5421,7 @@ dependencies = [ [[package]] name = "katana-codecs-derive" -version = "0.4.2" +version = "0.4.1" dependencies = [ "proc-macro2", "quote", @@ -5431,7 +5431,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "assert_matches", @@ -5464,7 +5464,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5487,7 +5487,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "blockifier", @@ -5503,7 +5503,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "blockifier", @@ -5521,7 +5521,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "auto_impl", @@ -5541,7 +5541,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "assert_matches", @@ -5573,7 +5573,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "derive_more", @@ -5586,7 +5586,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "katana-executor", @@ -5598,7 +5598,7 @@ dependencies = [ [[package]] name = "katana-runner" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "home", @@ -5889,7 +5889,7 @@ dependencies = [ [[package]] name = "metrics" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "hyper", @@ -8558,7 +8558,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "assert_fs", @@ -9752,7 +9752,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.2" +version = "0.4.1" dependencies = [ "async-trait", "camino", @@ -9778,7 +9778,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "async-trait", @@ -9814,7 +9814,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "async-graphql", @@ -9853,7 +9853,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.2" +version = "0.4.1" dependencies = [ "bytes", "crypto-bigint", @@ -9892,7 +9892,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.2" +version = "0.4.1" dependencies = [ "anyhow", "async-trait", @@ -9911,7 +9911,7 @@ dependencies = [ "hyper-reverse-proxy", "indexmap 1.9.3", "lazy_static", - "metrics 0.4.2", + "metrics 0.4.1", "metrics-process", "scarb", "serde", @@ -10179,7 +10179,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "types-test" -version = "0.4.2" +version = "0.4.1" [[package]] name = "ucd-trie" diff --git a/Cargo.toml b/Cargo.toml index 1ece9cfe98..d50ab59a7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.2" +version = "0.4.1" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index 14d33d1dbb..ee17c212ea 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.2" +version = "0.4.1" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index ef1080e43a..a8809ef181 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.2" +version = "0.4.1" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/torii/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock index 736f84366b..4f2d7aa41c 100644 --- a/crates/torii/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.2" +version = "0.4.1" dependencies = [ "dojo_plugin", ] diff --git a/crates/torii/types-test/Scarb.toml b/crates/torii/types-test/Scarb.toml index ac5bfa187f..3bb6831b41 100644 --- a/crates/torii/types-test/Scarb.toml +++ b/crates/torii/types-test/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "types_test" -version = "0.4.2" +version = "0.4.1" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 36fbad6d54..9474cc125c 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.2" +version = "0.4.1" dependencies = [ "dojo_plugin", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 2383af1a8e..35969ed10f 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "dojo_examples" -version = "0.4.2" +version = "0.4.1" # Use the prelude with the less imports as possible # from corelib. edition = "2023_10" From 3af74d5cf343da6a9500b05c674aa84939717cb1 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 19 Dec 2023 15:32:07 -0500 Subject: [PATCH 183/192] Set changelog to tag --- .github/workflows/release-dispatch.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index a2bea6eff3..c8ec5c6845 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -24,14 +24,15 @@ jobs: cargo install cargo-get echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm - - id: changelog - uses: mikepenz/release-changelog-builder-action@v4.1.0 - with: - fromTag: v${{ steps.current_release_info.outputs.version }} - id: next_release_info run: | cargo install cargo-get echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT + - id: changelog + uses: mikepenz/release-changelog-builder-action@v4.1.0 + with: + fromTag: v${{ steps.current_release_info.outputs.version }} + toTag: main - uses: peter-evans/create-pull-request@v5 with: # We have to use a PAT in order to trigger ci From 1840d7282721af874852f389abd3cbe2a3af5d7b Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 20 Dec 2023 06:08:58 +0800 Subject: [PATCH 184/192] test(katana-provider): more elaborate tests (#1317) * test(katana-provider): more elaborate tests * ignore log files from runner --- Cargo.lock | 2 + crates/katana/primitives/src/receipt.rs | 16 +- crates/katana/storage/provider/.gitignore | 1 + crates/katana/storage/provider/Cargo.toml | 8 +- .../provider/tests/{common.rs => block.rs} | 59 ++----- crates/katana/storage/provider/tests/class.rs | 146 +++++++++++++++++ .../katana/storage/provider/tests/contract.rs | 145 +++++++++++++++++ .../katana/storage/provider/tests/fixtures.rs | 135 +++++++++++++++- .../katana/storage/provider/tests/storage.rs | 153 ++++++++++++++++++ crates/katana/storage/provider/tests/utils.rs | 46 ++++++ 10 files changed, 654 insertions(+), 57 deletions(-) create mode 100644 crates/katana/storage/provider/.gitignore rename crates/katana/storage/provider/tests/{common.rs => block.rs} (51%) create mode 100644 crates/katana/storage/provider/tests/class.rs create mode 100644 crates/katana/storage/provider/tests/contract.rs create mode 100644 crates/katana/storage/provider/tests/storage.rs create mode 100644 crates/katana/storage/provider/tests/utils.rs diff --git a/Cargo.lock b/Cargo.lock index c0bf341d33..67bb5e3c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5528,6 +5528,8 @@ dependencies = [ "futures", "katana-db", "katana-primitives", + "katana-runner", + "lazy_static", "parking_lot 0.12.1", "rand", "rstest", diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index e053f4e845..34bfa888ed 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -3,7 +3,7 @@ use ethers::types::H256; use crate::contract::ContractAddress; use crate::FieldElement; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Event { /// The contract address that emitted the event. @@ -15,7 +15,7 @@ pub struct Event { } /// Represents a message sent to L1. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MessageToL1 { /// The L2 contract address that sent the message. @@ -27,7 +27,7 @@ pub struct MessageToL1 { } /// Receipt for a `Invoke` transaction. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvokeTxReceipt { /// Actual fee paid for the transaction. @@ -43,7 +43,7 @@ pub struct InvokeTxReceipt { } /// Receipt for a `Declare` transaction. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeclareTxReceipt { /// Actual fee paid for the transaction. @@ -59,7 +59,7 @@ pub struct DeclareTxReceipt { } /// Receipt for a `L1Handler` transaction. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1HandlerTxReceipt { /// Actual fee paid for the transaction. @@ -77,7 +77,7 @@ pub struct L1HandlerTxReceipt { } /// Receipt for a `DeployAccount` transaction. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeployAccountTxReceipt { /// Actual fee paid for the transaction. @@ -95,7 +95,7 @@ pub struct DeployAccountTxReceipt { } /// The receipt of a transaction containing the outputs of its execution. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Receipt { Invoke(InvokeTxReceipt), @@ -139,7 +139,7 @@ impl Receipt { /// Transaction execution resources. /// /// The resources consumed by a transaction during its execution. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxExecutionResources { /// The number of cairo steps used diff --git a/crates/katana/storage/provider/.gitignore b/crates/katana/storage/provider/.gitignore new file mode 100644 index 0000000000..b6d592eb2a --- /dev/null +++ b/crates/katana/storage/provider/.gitignore @@ -0,0 +1 @@ +/logs diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index dc59a8d1b0..af9d492a08 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -22,11 +22,13 @@ starknet.workspace = true tokio.workspace = true [features] -default = ["fork", "in-memory"] -fork = ["in-memory"] -in-memory = [] +default = [ "fork", "in-memory" ] +fork = [ "in-memory" ] +in-memory = [ ] [dev-dependencies] +katana-runner = { path = "../../runner" } +lazy_static.workspace = true rand = "0.8.5" rstest = "0.18.2" rstest_reuse = "0.6.0" diff --git a/crates/katana/storage/provider/tests/common.rs b/crates/katana/storage/provider/tests/block.rs similarity index 51% rename from crates/katana/storage/provider/tests/common.rs rename to crates/katana/storage/provider/tests/block.rs index 9fa4086298..03b8a4cecc 100644 --- a/crates/katana/storage/provider/tests/common.rs +++ b/crates/katana/storage/provider/tests/block.rs @@ -1,46 +1,16 @@ -use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, FinalityStatus, Header, SealedBlockWithStatus, -}; -use katana_primitives::transaction::{Tx, TxHash, TxWithHash}; -use katana_primitives::FieldElement; +use katana_primitives::block::BlockHashOrNumber; use katana_provider::providers::fork::ForkedProvider; use katana_provider::providers::in_memory::InMemoryProvider; use katana_provider::traits::block::{BlockProvider, BlockWriter}; -use katana_provider::traits::transaction::TransactionProvider; +use katana_provider::traits::transaction::{ReceiptProvider, TransactionProvider}; use katana_provider::BlockchainProvider; use rstest_reuse::{self, *}; mod fixtures; +mod utils; use fixtures::{fork_provider, in_memory_provider}; - -fn generate_dummy_txs(count: u64) -> Vec { - let mut txs = Vec::with_capacity(count as usize); - for _ in 0..count { - txs.push(TxWithHash { - hash: TxHash::from(rand::random::()), - transaction: Tx::Invoke(Default::default()), - }); - } - txs -} - -fn generate_dummy_blocks(count: u64) -> Vec { - let mut blocks = Vec::with_capacity(count as usize); - let mut parent_hash: BlockHash = 0u8.into(); - - for i in 0..count { - let body = generate_dummy_txs(rand::random::() % 10); - let header = Header { parent_hash, number: i, ..Default::default() }; - let block = - Block { header, body }.seal_with_hash(FieldElement::from(rand::random::())); - parent_hash = block.header.hash; - - blocks.push(SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }); - } - - blocks -} +use utils::generate_dummy_blocks_and_receipts; #[template] #[rstest::rstest] @@ -55,7 +25,7 @@ fn insert_block_with_in_memory_provider( #[from(in_memory_provider)] provider: BlockchainProvider, #[case] block_count: u64, ) -> anyhow::Result<()> { - insert_block_impl(provider, block_count) + insert_block_test_impl(provider, block_count) } #[apply(insert_block_cases)] @@ -63,24 +33,24 @@ fn insert_block_with_fork_provider( #[from(fork_provider)] provider: BlockchainProvider, #[case] block_count: u64, ) -> anyhow::Result<()> { - insert_block_impl(provider, block_count) + insert_block_test_impl(provider, block_count) } -fn insert_block_impl(provider: BlockchainProvider, count: u64) -> anyhow::Result<()> +fn insert_block_test_impl(provider: BlockchainProvider, count: u64) -> anyhow::Result<()> where - Db: BlockProvider + BlockWriter, + Db: BlockProvider + BlockWriter + ReceiptProvider, { - let blocks = generate_dummy_blocks(count); + let blocks = generate_dummy_blocks_and_receipts(count); - for block in &blocks { + for (block, receipts) in &blocks { provider.insert_block_with_states_and_receipts( block.clone(), Default::default(), - Default::default(), + receipts.clone(), )?; } - for block in blocks { + for (block, receipts) in blocks { let block_id = BlockHashOrNumber::Hash(block.block.header.hash); let expected_block = block.block.unseal(); @@ -88,6 +58,11 @@ where let actual_block_txs = provider.transactions_by_block(block_id)?; let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; + let actual_receipts = provider.receipts_by_block(block_id)?; + + assert_eq!(actual_receipts.as_ref().map(|r| r.len()), Some(expected_block.body.len())); + assert_eq!(actual_receipts, Some(receipts)); + assert_eq!(actual_block_tx_count, Some(expected_block.body.len() as u64)); assert_eq!(actual_block_txs, Some(expected_block.body.clone())); assert_eq!(actual_block, Some(expected_block)); diff --git a/crates/katana/storage/provider/tests/class.rs b/crates/katana/storage/provider/tests/class.rs new file mode 100644 index 0000000000..68a2959843 --- /dev/null +++ b/crates/katana/storage/provider/tests/class.rs @@ -0,0 +1,146 @@ +mod fixtures; + +use anyhow::Result; +use fixtures::{fork_provider_with_spawned_fork_network, in_memory_provider, provider_with_states}; +use katana_primitives::block::{BlockHashOrNumber, BlockNumber}; +use katana_primitives::contract::{ClassHash, CompiledClassHash}; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; +use katana_provider::BlockchainProvider; +use rstest_reuse::{self, *}; +use starknet::macros::felt; + +fn assert_state_provider_class( + state_provider: Box, + expected_class: Vec<(ClassHash, Option)>, +) -> Result<()> { + for (class_hash, expected_compiled_hash) in expected_class { + let actual_compiled_hash = state_provider.compiled_class_hash_of_class_hash(class_hash)?; + assert_eq!(actual_compiled_hash, expected_compiled_hash); + } + Ok(()) +} + +mod latest { + use super::*; + + fn assert_latest_class( + provider: BlockchainProvider, + expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + let state_provider = provider.latest()?; + assert_state_provider_class(state_provider, expected_class) + } + + #[template] + #[rstest::rstest] + #[case( + vec![ + (felt!("11"), Some(felt!("1000"))), + (felt!("22"), Some(felt!("2000"))), + (felt!("33"), Some(felt!("3000"))), + ] + )] + fn test_latest_class_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) { + } + + #[apply(test_latest_class_read)] + fn read_class_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_latest_class(provider, expected_class) + } + + #[apply(test_latest_class_read)] + fn read_class_from_fork_provider( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_latest_class(provider, expected_class) + } +} + +mod historical { + use super::*; + + fn assert_historical_class( + provider: BlockchainProvider, + block_num: BlockNumber, + expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + let state_provider = provider + .historical(BlockHashOrNumber::Num(block_num))? + .expect(ERROR_CREATE_HISTORICAL_PROVIDER); + assert_state_provider_class(state_provider, expected_class) + } + + const ERROR_CREATE_HISTORICAL_PROVIDER: &str = "Failed to create historical state provider."; + + #[template] + #[rstest::rstest] + #[case::class_hash_at_block_0( + 0, + vec![ + (felt!("11"), None), + (felt!("22"), None), + (felt!("33"), None) + ]) + ] + #[case::class_hash_at_block_1( + 1, + vec![ + (felt!("11"), Some(felt!("1000"))), + (felt!("22"), None), + (felt!("33"), None), + ]) + ] + #[case::class_hash_at_block_4( + 4, + vec![ + (felt!("11"), Some(felt!("1000"))), + (felt!("22"), Some(felt!("2000"))), + (felt!("33"), None), + ]) + ] + #[case::class_hash_at_block_5( + 5, + vec![ + (felt!("11"), Some(felt!("1000"))), + (felt!("22"), Some(felt!("2000"))), + (felt!("33"), Some(felt!("3000"))), + ]) + ] + fn test_historical_class_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) { + } + + #[apply(test_historical_class_read)] + fn read_class_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_historical_class(provider, block_num, expected_class) + } + + #[apply(test_historical_class_read)] + fn read_class_from_fork_provider( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] block_num: BlockNumber, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_historical_class(provider, block_num, expected_class) + } +} diff --git a/crates/katana/storage/provider/tests/contract.rs b/crates/katana/storage/provider/tests/contract.rs new file mode 100644 index 0000000000..3b54fdff78 --- /dev/null +++ b/crates/katana/storage/provider/tests/contract.rs @@ -0,0 +1,145 @@ +mod fixtures; + +use anyhow::Result; +use fixtures::{fork_provider_with_spawned_fork_network, in_memory_provider, provider_with_states}; +use katana_primitives::block::{BlockHashOrNumber, BlockNumber}; +use katana_primitives::contract::{ClassHash, ContractAddress, Nonce}; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; +use katana_provider::BlockchainProvider; +use rstest_reuse::{self, *}; +use starknet::macros::felt; + +fn assert_state_provider_contract_info( + state_provider: Box, + expected_contract_info: Vec<(ContractAddress, Option, Option)>, +) -> Result<()> { + for (address, expected_class_hash, expected_nonce) in expected_contract_info { + let actual_class_hash = state_provider.class_hash_of_contract(address)?; + let actual_nonce = state_provider.nonce(address)?; + + assert_eq!(actual_class_hash, expected_class_hash); + assert_eq!(actual_nonce, expected_nonce); + } + + Ok(()) +} + +mod latest { + use super::*; + + fn assert_latest_contract_info( + provider: BlockchainProvider, + expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + let state_provider = provider.latest()?; + assert_state_provider_contract_info(state_provider, expected_contract_info) + } + + #[template] + #[rstest::rstest] + #[case( + vec![ + (ContractAddress::from(felt!("1")), Some(felt!("22")), Some(felt!("3"))), + (ContractAddress::from(felt!("2")), Some(felt!("33")), Some(felt!("2"))), + ] + )] + fn test_latest_contract_info_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) { + } + + #[apply(test_latest_contract_info_read)] + fn read_storage_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_latest_contract_info(provider, expected_contract_info) + } + + #[apply(test_latest_contract_info_read)] + fn read_storage_from_fork_provider( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_latest_contract_info(provider, expected_contract_info) + } +} + +mod historical { + use super::*; + + fn assert_historical_contract_info( + provider: BlockchainProvider, + block_num: BlockNumber, + expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + let state_provider = provider + .historical(BlockHashOrNumber::Num(block_num))? + .expect(ERROR_CREATE_HISTORICAL_PROVIDER); + assert_state_provider_contract_info(state_provider, expected_contract_info) + } + + const ERROR_CREATE_HISTORICAL_PROVIDER: &str = "Failed to create historical state provider."; + + #[template] + #[rstest::rstest] + #[case::storage_at_block_0( + 0, + vec![ + (ContractAddress::from(felt!("1")), None, None), + (ContractAddress::from(felt!("2")), None, None) + ]) + ] + #[case::storage_at_block_1( + 1, + vec![ + (ContractAddress::from(felt!("1")), Some(felt!("11")), Some(felt!("1"))), + (ContractAddress::from(felt!("2")), Some(felt!("11")), Some(felt!("1"))), + ]) + ] + #[case::storage_at_block_4( + 4, + vec![ + (ContractAddress::from(felt!("1")), Some(felt!("11")), Some(felt!("2"))), + (ContractAddress::from(felt!("2")), Some(felt!("22")), Some(felt!("1"))), + ]) + ] + #[case::storage_at_block_5( + 5, + vec![ + (ContractAddress::from(felt!("1")), Some(felt!("22")), Some(felt!("3"))), + (ContractAddress::from(felt!("2")), Some(felt!("33")), Some(felt!("2"))), + ]) + ] + fn test_historical_storage_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) { + } + + #[apply(test_historical_storage_read)] + fn read_storage_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_historical_contract_info(provider, block_num, expected_contract_info) + } + + #[apply(test_historical_storage_read)] + fn read_storage_from_fork_provider( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] block_num: BlockNumber, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_historical_contract_info(provider, block_num, expected_contract_info) + } +} diff --git a/crates/katana/storage/provider/tests/fixtures.rs b/crates/katana/storage/provider/tests/fixtures.rs index d6c0ff6f85..4021ab3672 100644 --- a/crates/katana/storage/provider/tests/fixtures.rs +++ b/crates/katana/storage/provider/tests/fixtures.rs @@ -1,13 +1,30 @@ +use std::collections::HashMap; use std::sync::Arc; -use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::block::{ + BlockHashOrNumber, FinalityStatus, Header, SealedBlock, SealedBlockWithStatus, SealedHeader, +}; +use katana_primitives::contract::ContractAddress; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use katana_provider::providers::fork::ForkedProvider; use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::block::BlockWriter; +use katana_provider::traits::state::StateFactoryProvider; use katana_provider::BlockchainProvider; +use katana_runner::KatanaRunner; +use lazy_static::lazy_static; +use starknet::macros::felt; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use url::Url; +lazy_static! { + pub static ref FORKED_PROVIDER: (KatanaRunner, Arc>) = { + let (runner, provider) = katana_runner::KatanaRunner::new().unwrap(); + (runner, Arc::new(provider)) + }; +} + #[rstest::fixture] pub fn in_memory_provider() -> BlockchainProvider { BlockchainProvider::new(InMemoryProvider::new()) @@ -15,10 +32,120 @@ pub fn in_memory_provider() -> BlockchainProvider { #[rstest::fixture] pub fn fork_provider( - #[default("http://localhost:5050")] rpc: &str, + #[default("http://127.0.0.1:5050")] rpc: &str, #[default(0)] block_num: u64, ) -> BlockchainProvider { - let rpc_provider = JsonRpcClient::new(HttpTransport::new(Url::parse(rpc).unwrap())); - let provider = ForkedProvider::new(Arc::new(rpc_provider), BlockHashOrNumber::Num(block_num)); + let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(rpc).unwrap())); + let provider = ForkedProvider::new(Arc::new(provider), BlockHashOrNumber::Num(block_num)); BlockchainProvider::new(provider) } + +#[rstest::fixture] +pub fn fork_provider_with_spawned_fork_network( + #[default(0)] block_num: u64, +) -> BlockchainProvider { + let provider = + ForkedProvider::new(FORKED_PROVIDER.1.clone(), BlockHashOrNumber::Num(block_num)); + BlockchainProvider::new(provider) +} + +#[rstest::fixture] +#[default(BlockchainProvider)] +pub fn provider_with_states( + #[default(in_memory_provider())] provider: BlockchainProvider, +) -> BlockchainProvider +where + Db: BlockWriter + StateFactoryProvider, +{ + let address_1 = ContractAddress::from(felt!("1")); + let address_2 = ContractAddress::from(felt!("2")); + + let class_hash_1 = felt!("11"); + let compiled_class_hash_1 = felt!("1000"); + + let class_hash_2 = felt!("22"); + let compiled_class_hash_2 = felt!("2000"); + + let class_hash_3 = felt!("33"); + let compiled_class_hash_3 = felt!("3000"); + + let state_update_at_block_1 = StateUpdatesWithDeclaredClasses { + state_updates: StateUpdates { + nonce_updates: HashMap::from([(address_1, 1u8.into()), (address_2, 1u8.into())]), + storage_updates: HashMap::from([ + ( + address_1, + HashMap::from([(1u8.into(), 100u32.into()), (2u8.into(), 101u32.into())]), + ), + ( + address_2, + HashMap::from([(1u8.into(), 200u32.into()), (2u8.into(), 201u32.into())]), + ), + ]), + declared_classes: HashMap::from([(class_hash_1, compiled_class_hash_1)]), + contract_updates: HashMap::from([(address_1, class_hash_1), (address_2, class_hash_1)]), + }, + ..Default::default() + }; + + let state_update_at_block_2 = StateUpdatesWithDeclaredClasses { + state_updates: StateUpdates { + nonce_updates: HashMap::from([(address_1, 2u8.into())]), + storage_updates: HashMap::from([( + address_1, + HashMap::from([(felt!("1"), felt!("111")), (felt!("2"), felt!("222"))]), + )]), + declared_classes: HashMap::from([(class_hash_2, compiled_class_hash_2)]), + contract_updates: HashMap::from([(address_2, class_hash_2)]), + }, + ..Default::default() + }; + + let state_update_at_block_5 = StateUpdatesWithDeclaredClasses { + state_updates: StateUpdates { + nonce_updates: HashMap::from([(address_1, 3u8.into()), (address_2, 2u8.into())]), + storage_updates: HashMap::from([ + (address_1, HashMap::from([(3u8.into(), 77u32.into())])), + ( + address_2, + HashMap::from([(1u8.into(), 12u32.into()), (2u8.into(), 13u32.into())]), + ), + ]), + contract_updates: HashMap::from([(address_1, class_hash_2), (address_2, class_hash_3)]), + declared_classes: HashMap::from([(class_hash_3, compiled_class_hash_3)]), + }, + ..Default::default() + }; + + // Fill provider with states. + + for i in 0..=5 { + let block_id = BlockHashOrNumber::from(i); + + let state_update = match block_id { + BlockHashOrNumber::Num(1) => state_update_at_block_1.clone(), + BlockHashOrNumber::Num(2) => state_update_at_block_2.clone(), + BlockHashOrNumber::Num(5) => state_update_at_block_5.clone(), + _ => StateUpdatesWithDeclaredClasses::default(), + }; + + provider + .insert_block_with_states_and_receipts( + SealedBlockWithStatus { + status: FinalityStatus::AcceptedOnL2, + block: SealedBlock { + header: SealedHeader { + hash: i.into(), + header: Header { number: i, ..Default::default() }, + }, + body: Default::default(), + }, + }, + state_update, + Default::default(), + ) + .unwrap(); + } + + provider +} diff --git a/crates/katana/storage/provider/tests/storage.rs b/crates/katana/storage/provider/tests/storage.rs new file mode 100644 index 0000000000..4f12e8f5d8 --- /dev/null +++ b/crates/katana/storage/provider/tests/storage.rs @@ -0,0 +1,153 @@ +mod fixtures; + +use anyhow::Result; +use fixtures::{fork_provider_with_spawned_fork_network, in_memory_provider, provider_with_states}; +use katana_primitives::block::{BlockHashOrNumber, BlockNumber}; +use katana_primitives::contract::{ContractAddress, StorageKey, StorageValue}; +use katana_provider::providers::fork::ForkedProvider; +use katana_provider::providers::in_memory::InMemoryProvider; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; +use katana_provider::BlockchainProvider; +use rstest_reuse::{self, *}; +use starknet::macros::felt; + +fn assert_state_provider_storage( + state_provider: Box, + expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, +) -> Result<()> { + for (address, key, expected_value) in expected_storage_entry { + let actual_value = state_provider.storage(address, key)?; + assert_eq!(actual_value, expected_value); + } + Ok(()) +} + +mod latest { + use super::*; + + fn assert_latest_storage_value( + provider: BlockchainProvider, + expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + let state_provider = provider.latest()?; + assert_state_provider_storage(state_provider, expected_storage_entry) + } + + #[template] + #[rstest::rstest] + #[case( + vec![ + (ContractAddress::from(felt!("1")), felt!("1"), Some(felt!("111"))), + (ContractAddress::from(felt!("1")), felt!("2"), Some(felt!("222"))), + (ContractAddress::from(felt!("1")), felt!("3"), Some(felt!("77"))), + (ContractAddress::from(felt!("2")), felt!("1"), Some(felt!("12"))), + (ContractAddress::from(felt!("2")), felt!("2"), Some(felt!("13"))) + ] + )] + fn test_latest_storage_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) { + } + + #[apply(test_latest_storage_read)] + fn read_storage_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_latest_storage_value(provider, expected_storage_entry) + } + + #[apply(test_latest_storage_read)] + fn read_storage_from_fork_provider_with_spawned_fork_network( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_latest_storage_value(provider, expected_storage_entry) + } +} + +mod historical { + use super::*; + + fn assert_historical_storage_value( + provider: BlockchainProvider, + block_num: BlockNumber, + expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + let state_provider = provider + .historical(BlockHashOrNumber::Num(block_num))? + .expect(ERROR_CREATE_HISTORICAL_PROVIDER); + assert_state_provider_storage(state_provider, expected_storage_entry) + } + + const ERROR_CREATE_HISTORICAL_PROVIDER: &str = "Failed to create historical state provider."; + + #[template] + #[rstest::rstest] + #[case::storage_at_block_0( + 0, + vec![ + (ContractAddress::from(felt!("1")), felt!("1"), None), + (ContractAddress::from(felt!("1")), felt!("2"), None), + (ContractAddress::from(felt!("2")), felt!("1"), None), + (ContractAddress::from(felt!("2")), felt!("2"), None) + ]) + ] + #[case::storage_at_block_1( + 1, + vec![ + (ContractAddress::from(felt!("1")), felt!("1"), Some(felt!("100"))), + (ContractAddress::from(felt!("1")), felt!("2"), Some(felt!("101"))), + (ContractAddress::from(felt!("2")), felt!("1"), Some(felt!("200"))), + (ContractAddress::from(felt!("2")), felt!("2"), Some(felt!("201"))), + ]) + ] + #[case::storage_at_block_4( + 4, + vec![ + (ContractAddress::from(felt!("1")), felt!("1"), Some(felt!("111"))), + (ContractAddress::from(felt!("1")), felt!("2"), Some(felt!("222"))), + (ContractAddress::from(felt!("2")), felt!("1"), Some(felt!("200"))), + (ContractAddress::from(felt!("2")), felt!("2"), Some(felt!("201"))), + ]) + ] + #[case::storage_at_block_5( + 5, + vec![ + (ContractAddress::from(felt!("1")), felt!("1"), Some(felt!("111"))), + (ContractAddress::from(felt!("1")), felt!("2"), Some(felt!("222"))), + (ContractAddress::from(felt!("1")), felt!("3"), Some(felt!("77"))), + (ContractAddress::from(felt!("2")), felt!("1"), Some(felt!("12"))), + (ContractAddress::from(felt!("2")), felt!("2"), Some(felt!("13"))), + ]) + ] + fn test_historical_storage_read( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) { + } + + #[apply(test_historical_storage_read)] + fn read_storage_from_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_historical_storage_value(provider, block_num, expected_storage_entry) + } + + #[apply(test_historical_storage_read)] + fn read_storage_from_fork_provider_with_spawned_fork_network( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] block_num: BlockNumber, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_historical_storage_value(provider, block_num, expected_storage_entry) + } +} diff --git a/crates/katana/storage/provider/tests/utils.rs b/crates/katana/storage/provider/tests/utils.rs new file mode 100644 index 0000000000..510b80e70a --- /dev/null +++ b/crates/katana/storage/provider/tests/utils.rs @@ -0,0 +1,46 @@ +use katana_primitives::block::{Block, BlockHash, FinalityStatus, Header, SealedBlockWithStatus}; +use katana_primitives::receipt::{InvokeTxReceipt, Receipt}; +use katana_primitives::transaction::{Tx, TxHash, TxWithHash}; +use katana_primitives::FieldElement; + +pub fn generate_dummy_txs_and_receipts(count: usize) -> (Vec, Vec) { + let mut txs = Vec::with_capacity(count); + let mut receipts = Vec::with_capacity(count); + + // TODO: generate random txs and receipts variants + for _ in 0..count { + txs.push(TxWithHash { + hash: TxHash::from(rand::random::()), + transaction: Tx::Invoke(Default::default()), + }); + + receipts.push(Receipt::Invoke(InvokeTxReceipt::default())); + } + + (txs, receipts) +} + +pub fn generate_dummy_blocks_and_receipts( + count: u64, +) -> Vec<(SealedBlockWithStatus, Vec)> { + let mut blocks = Vec::with_capacity(count as usize); + let mut parent_hash: BlockHash = 0u8.into(); + + for i in 0..count { + let tx_count = (rand::random::() % 10) as usize; + let (body, receipts) = generate_dummy_txs_and_receipts(tx_count); + + let header = Header { parent_hash, number: i, ..Default::default() }; + let block = + Block { header, body }.seal_with_hash(FieldElement::from(rand::random::())); + + parent_hash = block.header.hash; + + blocks.push(( + SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }, + receipts, + )); + } + + blocks +} From 792194c1333226bbd1cec2d0aec7012d6f3a81fd Mon Sep 17 00:00:00 2001 From: glihm Date: Wed, 20 Dec 2023 10:27:32 -0600 Subject: [PATCH 185/192] feat: use of `WorldContractReader` from `abigen!` (#1010) * feat: add cainome abigen to sozo * feat: add cainome abigen to torii * fix: adjust test and fmt * fix: remove additional calldata input * feat(katana-rpc): add ContractErrorData when it applies * fix: fix tests * fix: adjust indexes for executor call results * fix: restore Scarb.lock and add .tool-versions * chore: bump cainome to 0.1.5 * fix: embed world and executor ABI in .git * chore: bump cainome to 0.1.7 * chore: update Scarb.lock * docs: update README for contract ABI * fix: fmt clippy from merge * fix: fix PR comments * fix: fix typo after test being run * fix: fix Scarb.lock version --- Cargo.lock | 48 ++ crates/dojo-core/.tool-versions | 1 + crates/dojo-world/Cargo.toml | 1 + crates/dojo-world/src/contracts/abi/README.md | 13 + .../src/contracts/abi/executor.json | 53 ++ .../dojo-world/src/contracts/abi/world.json | 775 ++++++++++++++++++ .../dojo-world/src/contracts/cairo_utils.rs | 26 + crates/dojo-world/src/contracts/mod.rs | 3 +- crates/dojo-world/src/contracts/model.rs | 72 +- crates/dojo-world/src/contracts/model_test.rs | 4 +- crates/dojo-world/src/contracts/world.rs | 334 +------- crates/dojo-world/src/contracts/world_test.rs | 18 +- crates/dojo-world/src/manifest.rs | 22 +- crates/sozo/src/ops/auth.rs | 4 +- crates/sozo/src/ops/migration/mod.rs | 20 +- crates/sozo/src/ops/model.rs | 6 +- crates/sozo/src/ops/register.rs | 13 +- crates/torii/client/src/client/mod.rs | 6 +- crates/torii/core/src/engine.rs | 2 +- crates/torii/core/src/processors/mod.rs | 2 +- .../core/src/processors/register_model.rs | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- 22 files changed, 1046 insertions(+), 381 deletions(-) create mode 100644 crates/dojo-core/.tool-versions create mode 100644 crates/dojo-world/src/contracts/abi/README.md create mode 100644 crates/dojo-world/src/contracts/abi/executor.json create mode 100644 crates/dojo-world/src/contracts/abi/world.json create mode 100644 crates/dojo-world/src/contracts/cairo_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 67bb5e3c7b..d1ecb5a4b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1117,6 +1117,53 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +[[package]] +name = "cainome" +version = "0.1.5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.1.7#2d4b52b2e79796f76fba9e3a1b1027e8e63292b8" +dependencies = [ + "cainome-cairo-serde", + "cainome-parser", + "cainome-rs", +] + +[[package]] +name = "cainome-cairo-serde" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.1.7#2d4b52b2e79796f76fba9e3a1b1027e8e63292b8" +dependencies = [ + "starknet", + "thiserror", +] + +[[package]] +name = "cainome-parser" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.1.7#2d4b52b2e79796f76fba9e3a1b1027e8e63292b8" +dependencies = [ + "quote", + "serde_json", + "starknet", + "syn 2.0.41", + "thiserror", +] + +[[package]] +name = "cainome-rs" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.1.7#2d4b52b2e79796f76fba9e3a1b1027e8e63292b8" +dependencies = [ + "anyhow", + "cainome-cairo-serde", + "cainome-parser", + "proc-macro2", + "quote", + "serde_json", + "starknet", + "syn 2.0.41", + "thiserror", +] + [[package]] name = "cairo-felt" version = "0.8.2" @@ -2761,6 +2808,7 @@ dependencies = [ "assert_fs", "assert_matches", "async-trait", + "cainome", "cairo-lang-filesystem", "cairo-lang-project", "cairo-lang-starknet", diff --git a/crates/dojo-core/.tool-versions b/crates/dojo-core/.tool-versions new file mode 100644 index 0000000000..21cfc80772 --- /dev/null +++ b/crates/dojo-core/.tool-versions @@ -0,0 +1 @@ +scarb 2.4.0 diff --git a/crates/dojo-world/Cargo.toml b/crates/dojo-world/Cargo.toml index d6da3ff3b0..cbedf2fa5e 100644 --- a/crates/dojo-world/Cargo.toml +++ b/crates/dojo-world/Cargo.toml @@ -24,6 +24,7 @@ starknet.workspace = true thiserror.workspace = true tracing.workspace = true +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.1.7", features = ["abigen-rs"] } dojo-types = { path = "../dojo-types", optional = true } http = { version = "0.2.9", optional = true } ipfs-api-backend-hyper = { git = "https://github.com/ferristseng/rust-ipfs-api", rev = "af2c17f7b19ef5b9898f458d97a90055c3605633", features = [ "with-hyper-rustls" ], optional = true } diff --git a/crates/dojo-world/src/contracts/abi/README.md b/crates/dojo-world/src/contracts/abi/README.md new file mode 100644 index 0000000000..3985ecc602 --- /dev/null +++ b/crates/dojo-world/src/contracts/abi/README.md @@ -0,0 +1,13 @@ +# Embedded ABI for contracts + +Currently, the ABIs for `world` and `executor` are embedded in the repo. +To build them, consider the following: + +1. Change directory into `examples/spawn-and-move` at the root of the workspace. +2. Build the example with `sozo`. +3. Extract the ABI key only for `world` and `executor`: +``` +sozo build +jq .abi ./target/dev/dojo\:\:world\:\:world.json > ../../crates/dojo-world/src/contracts/abi/world.json +jq .abi ./target/dev/dojo\:\:executor\:\:executor.json > ../../crates/dojo-world/src/contracts/abi/executor.json +``` diff --git a/crates/dojo-world/src/contracts/abi/executor.json b/crates/dojo-world/src/contracts/abi/executor.json new file mode 100644 index 0000000000..ad21e66317 --- /dev/null +++ b/crates/dojo-world/src/contracts/abi/executor.json @@ -0,0 +1,53 @@ +[ + { + "type": "impl", + "name": "Executor", + "interface_name": "dojo::executor::IExecutor" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "interface", + "name": "dojo::executor::IExecutor", + "items": [ + { + "type": "function", + "name": "call", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + }, + { + "name": "entrypoint", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "dojo::executor::executor::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/crates/dojo-world/src/contracts/abi/world.json b/crates/dojo-world/src/contracts/abi/world.json new file mode 100644 index 0000000000..f1f0fa062b --- /dev/null +++ b/crates/dojo-world/src/contracts/abi/world.json @@ -0,0 +1,775 @@ +[ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::IWorld" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::felt252" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorld", + "items": [ + { + "type": "function", + "name": "metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata_uri", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "uri", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "model", + "inputs": [ + { + "name": "name", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "deploy_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit", + "inputs": [ + { + "name": "keys", + "type": "core::array::Array::" + }, + { + "name": "values", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "length", + "type": "core::integer::u32" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entities", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "index", + "type": "core::option::Option::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "values_length", + "type": "core::integer::u32" + }, + { + "name": "values_layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "(core::array::Span::, core::array::Span::>)" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity_ids", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_executor", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "executor", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "base", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "resource", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "executor", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "contract_base", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractDeployed", + "kind": "struct", + "members": [ + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "uri", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "prev_class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "offset", + "type": "core::integer::u8", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "model", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "resource", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ExecutorUpdated", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "ContractDeployed", + "type": "dojo::world::world::ContractDeployed", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world::OwnerUpdated", + "kind": "nested" + }, + { + "name": "ExecutorUpdated", + "type": "dojo::world::world::ExecutorUpdated", + "kind": "nested" + } + ] + } +] diff --git a/crates/dojo-world/src/contracts/cairo_utils.rs b/crates/dojo-world/src/contracts/cairo_utils.rs new file mode 100644 index 0000000000..b43ff5be7d --- /dev/null +++ b/crates/dojo-world/src/contracts/cairo_utils.rs @@ -0,0 +1,26 @@ +use anyhow::{anyhow, Result}; +use http::uri::Uri; +use starknet::core::types::FieldElement; +use starknet::core::utils::cairo_short_string_to_felt; + +pub fn str_to_felt(string: &str) -> Result { + cairo_short_string_to_felt(string).map_err(|e| { + anyhow!(format!("Failed to convert string `{}` to cairo short string: {}", string, e)) + }) +} + +pub fn encode_uri(uri: &str) -> Result> { + let parsed: Uri = + uri.try_into().map_err(|e| anyhow!("Failed to encode URI `{}`: {}", uri, e))?; + + Ok(parsed + .to_string() + .chars() + .collect::>() + .chunks(31) + .map(|chunk| { + let s: String = chunk.iter().collect(); + cairo_short_string_to_felt(&s) + }) + .collect::, _>>()?) +} diff --git a/crates/dojo-world/src/contracts/mod.rs b/crates/dojo-world/src/contracts/mod.rs index 8d122a0dd3..3c09618fc4 100644 --- a/crates/dojo-world/src/contracts/mod.rs +++ b/crates/dojo-world/src/contracts/mod.rs @@ -1,4 +1,5 @@ +pub mod cairo_utils; pub mod model; pub mod world; -pub use world::{WorldContract, WorldContractError, WorldContractReader}; +pub use world::{WorldContract, WorldContractReader}; diff --git a/crates/dojo-world/src/contracts/model.rs b/crates/dojo-world/src/contracts/model.rs index 433f3b8cd4..b0ac6e37ed 100644 --- a/crates/dojo-world/src/contracts/model.rs +++ b/crates/dojo-world/src/contracts/model.rs @@ -1,10 +1,11 @@ use std::vec; use async_trait::async_trait; +use cainome::cairo_serde::Error as CainomeError; use dojo_types::packing::{parse_ty, unpack, PackingError, ParseError}; use dojo_types::primitive::PrimitiveError; use dojo_types::schema::Ty; -use starknet::core::types::{FieldElement, FunctionCall, StarknetError}; +use starknet::core::types::{FieldElement, StarknetError}; use starknet::core::utils::{ cairo_short_string_to_felt, get_selector_from_name, CairoShortStringToFeltError, ParseCairoShortStringError, @@ -13,9 +14,8 @@ use starknet::macros::short_string; use starknet::providers::{Provider, ProviderError}; use starknet_crypto::poseidon_hash_many; -use crate::contracts::world::{ContractReaderError, WorldContractReader}; +use crate::contracts::WorldContractReader; -const WORLD_MODEL_SELECTOR_STR: &str = "model"; const SCHEMA_SELECTOR_STR: &str = "schema"; const LAYOUT_SELECTOR_STR: &str = "layout"; const PACKED_SIZE_SELECTOR_STR: &str = "packed_size"; @@ -36,13 +36,13 @@ pub enum ModelError { #[error(transparent)] CairoShortStringToFeltError(#[from] CairoShortStringToFeltError), #[error(transparent)] - ContractReaderError(#[from] ContractReaderError), - #[error(transparent)] CairoTypeError(#[from] PrimitiveError), #[error(transparent)] Parse(#[from] ParseError), #[error(transparent)] Packing(#[from] PackingError), + #[error(transparent)] + Cainome(#[from] CainomeError), } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -55,7 +55,7 @@ pub trait ModelReader { async fn layout(&self) -> Result, E>; } -pub struct ModelRPCReader<'a, P: Sync + Send> { +pub struct ModelRPCReader<'a, P: Provider + Sync + Send> { /// The name of the model name: FieldElement, /// The class hash of the model @@ -74,26 +74,15 @@ where ) -> Result, ModelError> { let name = cairo_short_string_to_felt(name)?; - let class_hash = world - .provider() - .call( - FunctionCall { - calldata: vec![name], - contract_address: world.address(), - entry_point_selector: get_selector_from_name(WORLD_MODEL_SELECTOR_STR).unwrap(), - }, - world.block_id(), - ) - .await - .map(|res| res[0]) - .map_err(|err| match err { - ProviderError::StarknetError(StarknetError::ContractNotFound) => { - ModelError::ModelNotFound - } + let class_hash = + world.model(&name).block_id(world.block_id).call().await.map_err(|err| match err { + CainomeError::Provider(ProviderError::StarknetError( + StarknetError::ContractNotFound, + )) => ModelError::ModelNotFound, err => err.into(), })?; - Ok(Self { world_reader: world, class_hash, name }) + Ok(Self { world_reader: world, class_hash: class_hash.into(), name }) } pub async fn entity_storage( @@ -112,9 +101,9 @@ where .world_reader .provider() .get_storage_at( - self.world_reader.address(), + self.world_reader.address, key + slot.into(), - self.world_reader.block_id(), + self.world_reader.block_id, ) .await?; @@ -152,44 +141,37 @@ where async fn schema(&self) -> Result { let entrypoint = get_selector_from_name(SCHEMA_SELECTOR_STR).unwrap(); - let res = self - .world_reader - .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO]) - .await?; + let res = self.world_reader.executor_call(self.class_hash, entrypoint, vec![]).await?; - Ok(parse_ty(&res[1..])?) + Ok(parse_ty(&res)?) } async fn packed_size(&self) -> Result { let entrypoint = get_selector_from_name(PACKED_SIZE_SELECTOR_STR).unwrap(); - let res = self - .world_reader - .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO]) - .await?; + let res = self.world_reader.executor_call(self.class_hash, entrypoint, vec![]).await?; - Ok(res[1]) + Ok(res[0]) } async fn unpacked_size(&self) -> Result { let entrypoint = get_selector_from_name(UNPACKED_SIZE_SELECTOR_STR).unwrap(); - let res = self - .world_reader - .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO]) - .await?; + let res = self.world_reader.executor_call(self.class_hash, entrypoint, vec![]).await?; - Ok(res[1]) + Ok(res[0]) } async fn layout(&self) -> Result, ModelError> { let entrypoint = get_selector_from_name(LAYOUT_SELECTOR_STR).unwrap(); - let res = self - .world_reader - .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO]) - .await?; + let res = self.world_reader.executor_call(self.class_hash, entrypoint, vec![]).await?; - Ok(res[2..].into()) + // Layout entrypoint expanded by the #[model] attribute returns a + // `Span`. So cainome generated code will deserialize the result + // of `executor.call()` which is a Vec. + // So inside the vec, we skip the first element, which is the length + // of the span returned by `layout` entrypoint of the model code. + Ok(res[1..].into()) } } diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs index cd235080da..efab444182 100644 --- a/crates/dojo-world/src/contracts/model_test.rs +++ b/crates/dojo-world/src/contracts/model_test.rs @@ -24,7 +24,7 @@ async fn test_model() { .await; let world = WorldContractReader::new(world_address, provider); - let position = world.model("Position").await.unwrap(); + let position = world.model_reader("Position").await.unwrap(); let schema = position.schema().await.unwrap(); assert_eq!( @@ -68,7 +68,7 @@ async fn test_model() { .unwrap() ); - let moves = world.model("Moves").await.unwrap(); + let moves = world.model_reader("Moves").await.unwrap(); let schema = moves.schema().await.unwrap(); assert_eq!( diff --git a/crates/dojo-world/src/contracts/world.rs b/crates/dojo-world/src/contracts/world.rs index 93bd56d8c7..b8bcfe9d2d 100644 --- a/crates/dojo-world/src/contracts/world.rs +++ b/crates/dojo-world/src/contracts/world.rs @@ -1,15 +1,9 @@ use std::result::Result; -use http::uri::{InvalidUri, Uri}; -use starknet::accounts::{AccountError, Call, ConnectedAccount}; -use starknet::core::types::{ - BlockId, BlockTag, FieldElement, FunctionCall, InvokeTransactionResult, -}; -use starknet::core::utils::{ - cairo_short_string_to_felt, get_selector_from_name, CairoShortStringToFeltError, -}; -use starknet::macros::selector; -use starknet::providers::{Provider, ProviderError}; +pub use abigen::world::{WorldContract, WorldContractReader}; +use cainome::cairo_serde::Result as CainomeResult; +use starknet::core::types::FieldElement; +use starknet::providers::Provider; use super::model::{ModelError, ModelRPCReader}; @@ -17,318 +11,62 @@ use super::model::{ModelError, ModelRPCReader}; #[path = "world_test.rs"] pub(crate) mod test; -#[derive(Debug, thiserror::Error)] -pub enum WorldContractError { - #[error(transparent)] - ProviderError(#[from] ProviderError), - #[error(transparent)] - AccountError(#[from] AccountError), - #[error(transparent)] - CairoShortStringToFeltError(#[from] CairoShortStringToFeltError), - #[error(transparent)] - ContractReaderError(#[from] ContractReaderError), - #[error("Invalid metadata uri")] - InvalidMetadataUri(#[from] InvalidUri), -} - -pub struct WorldContract<'a, A> -where - A: ConnectedAccount, -{ - account: &'a A, - reader: WorldContractReader<&'a ::Provider>, -} - -impl<'a, A> WorldContract<'a, A> -where - A: ConnectedAccount, -{ - pub fn new(address: FieldElement, account: &'a A) -> Self { - Self { account, reader: WorldContractReader::new(address, account.provider()) } +#[cfg(not(doctest))] +pub mod abigen { + pub mod world { + use cainome::rs::abigen; + abigen!(WorldContract, "crates/dojo-world/src/contracts/abi/world.json"); } - pub fn account(&self) -> &A { - self.account + pub mod executor { + use cainome::rs::abigen; + abigen!(ExecutorContract, "crates/dojo-world/src/contracts/abi/executor.json"); } } -impl<'a, A> WorldContract<'a, A> -where - A: ConnectedAccount + Sync, -{ - pub async fn set_executor( - &self, - executor: FieldElement, - ) -> Result> { - self.account - .execute(vec![Call { - to: self.reader.address, - calldata: vec![executor], - selector: selector!("set_executor"), - }]) - .send() - .await - } - - pub async fn set_metadata_uri( - &self, - resource: FieldElement, - metadata_uri: String, - ) -> Result> { - let parsed: Uri = - metadata_uri.try_into().map_err(WorldContractError::InvalidMetadataUri)?; - - let mut encoded = parsed - .to_string() - .chars() - .collect::>() - .chunks(31) - .map(|chunk| { - let s: String = chunk.iter().collect(); - cairo_short_string_to_felt(&s).unwrap() - }) - .collect::>(); - - encoded.insert(0, encoded.len().into()); - encoded.insert(0, resource); - - self.account - .execute(vec![Call { - calldata: encoded, - to: self.reader.address, - selector: get_selector_from_name("set_metadata_uri").unwrap(), - }]) - .send() - .await - .map_err(WorldContractError::AccountError) - } - - pub async fn grant_writer( - &self, - model: &str, - contract: FieldElement, - ) -> Result> { - let model = cairo_short_string_to_felt(model) - .map_err(WorldContractError::CairoShortStringToFeltError)?; - - self.account - .execute(vec![Call { - calldata: vec![model, contract], - to: self.reader.address, - selector: get_selector_from_name("grant_writer").unwrap(), - }]) - .send() - .await - .map_err(WorldContractError::AccountError) - } - - pub async fn register_models( - &self, - models: &[FieldElement], - ) -> Result> { - let calls = models - .iter() - .map(|c| Call { - to: self.reader.address, - selector: selector!("register_model"), - calldata: vec![*c], - }) - .collect::>(); - - self.account.execute(calls).send().await +#[cfg(doctest)] +pub mod abigen { + pub mod world { + use cainome::rs::abigen; + abigen!(WorldContract, "src/contracts/abi/world.json"); } - pub async fn deploy_contract( - &self, - salt: &FieldElement, - class_hash: &FieldElement, - ) -> Result> { - self.account - .execute(vec![Call { - to: self.reader.address, - selector: selector!("deploy_contract"), - calldata: vec![*salt, *class_hash], - }]) - .send() - .await + pub mod executor { + use cainome::rs::abigen; + abigen!(ExecutorContract, "src/contracts/abi/executor.json"); } - - pub async fn executor(&self) -> Result { - self.reader.executor().await - } - - pub async fn base(&self) -> Result { - self.reader.base().await - } - - pub async fn model( - &'a self, - name: &str, - ) -> Result, ModelError> { - self.reader.model(name).await - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ContractReaderError { - #[error(transparent)] - ProviderError(#[from] ProviderError), - #[error(transparent)] - CairoShortStringToFeltError(#[from] CairoShortStringToFeltError), -} - -pub struct WorldContractReader

{ - provider: P, - block_id: BlockId, - address: FieldElement, } impl

WorldContractReader

where - P: Provider, + P: Provider + Sync + Send, { - pub fn new(address: FieldElement, provider: P) -> Self { - Self { address, provider, block_id: BlockId::Tag(BlockTag::Latest) } - } - - pub fn with_block(self, block: BlockId) -> Self { - Self { block_id: block, ..self } - } - - pub fn address(&self) -> FieldElement { - self.address - } - - pub fn provider(&self) -> &P { - &self.provider - } - - pub fn block_id(&self) -> BlockId { - self.block_id + pub async fn model_reader(&self, name: &str) -> Result, ModelError> { + ModelRPCReader::new(name, self).await } } impl

WorldContractReader

where - P: Provider, + P: Provider + Sync + Send, { - pub async fn is_authorized( - &self, - system: &str, - model: &str, - execution_role: &str, - ) -> Result { - let res = self - .provider - .call( - FunctionCall { - calldata: vec![ - cairo_short_string_to_felt(system)?, - cairo_short_string_to_felt(model)?, - cairo_short_string_to_felt(execution_role)?, - ], - contract_address: self.address, - entry_point_selector: selector!("is_authorized"), - }, - self.block_id, - ) - .await?; - - Ok(res[0] == FieldElement::ONE) - } - - pub async fn is_account_admin(&self) -> Result { - let res = self - .provider - .call( - FunctionCall { - calldata: vec![], - contract_address: self.address, - entry_point_selector: selector!("is_account_admin"), - }, - self.block_id, - ) - .await?; - - Ok(res[0] == FieldElement::ONE) - } - - pub async fn executor(&self) -> Result { - let res = self - .provider - .call( - FunctionCall { - calldata: vec![], - contract_address: self.address, - entry_point_selector: selector!("executor"), - }, - self.block_id, - ) - .await?; - - Ok(res[0]) - } - - pub async fn metadata_uri(&self) -> Result { - let res = self - .provider - .call( - FunctionCall { - calldata: vec![], - contract_address: self.address, - entry_point_selector: selector!("metadata_uri"), - }, - self.block_id, - ) - .await?; - - Ok(res[0]) - } - - pub async fn base(&self) -> Result { - let res = self - .provider - .call( - FunctionCall { - calldata: vec![], - contract_address: self.address, - entry_point_selector: selector!("base"), - }, - self.block_id, - ) - .await?; - - Ok(res[0]) - } - pub async fn executor_call( &self, class_hash: FieldElement, - mut calldata: Vec, - ) -> Result, ContractReaderError> { - calldata.insert(0, class_hash); - - let res = self - .provider - .call( - FunctionCall { - calldata, - contract_address: self.executor().await?, - entry_point_selector: selector!("call"), - }, - self.block_id, - ) + entry_point: FieldElement, + calldata: Vec, + ) -> CainomeResult> { + let executor_address = self.executor().block_id(self.block_id).call().await?; + + let executor = + abigen::executor::ExecutorContractReader::new(executor_address.into(), &self.provider); + + let res = executor + .call(&class_hash.into(), &entry_point, &calldata) + .block_id(self.block_id) + .call() .await?; Ok(res) } } - -impl<'a, P> WorldContractReader

-where - P: Provider + Sync + Send, -{ - pub async fn model(&'a self, name: &str) -> Result, ModelError> { - ModelRPCReader::new(name, self).await - } -} diff --git a/crates/dojo-world/src/contracts/world_test.rs b/crates/dojo-world/src/contracts/world_test.rs index 2e41ef6e2e..2028fad8b8 100644 --- a/crates/dojo-world/src/contracts/world_test.rs +++ b/crates/dojo-world/src/contracts/world_test.rs @@ -4,7 +4,7 @@ use camino::Utf8PathBuf; use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, TestSequencer, }; -use starknet::accounts::ConnectedAccount; +use starknet::accounts::{Account, ConnectedAccount}; use starknet::core::types::FieldElement; use super::{WorldContract, WorldContractReader}; @@ -26,9 +26,9 @@ async fn test_world_contract_reader() { .await; let world = WorldContractReader::new(world_address, provider); - let executor = world.executor().await.unwrap(); + let executor = world.executor().call().await.unwrap(); - assert_eq!(executor, executor_address); + assert_eq!(FieldElement::from(executor), executor_address); } pub async fn deploy_world( @@ -82,10 +82,14 @@ pub async fn deploy_world( // wait for the tx to be mined tokio::time::sleep(Duration::from_millis(250)).await; - let _ = WorldContract::new(world_address, &account) - .register_models(&declare_output.iter().map(|o| o.class_hash).collect::>()) - .await - .unwrap(); + let world = WorldContract::new(world_address, &account); + + let calls = declare_output + .iter() + .map(|o| world.register_model_getcall(&o.class_hash.into())) + .collect::>(); + + let _ = account.execute(calls).send().await.unwrap(); // wait for the tx to be mined tokio::time::sleep(Duration::from_millis(250)).await; diff --git a/crates/dojo-world/src/manifest.rs b/crates/dojo-world/src/manifest.rs index 5854b7110f..75dfac5135 100644 --- a/crates/dojo-world/src/manifest.rs +++ b/crates/dojo-world/src/manifest.rs @@ -3,6 +3,7 @@ use std::fs; use std::path::Path; use ::serde::{Deserialize, Serialize}; +use cainome::cairo_serde::Error as CainomeError; use cairo_lang_starknet::abi; use serde_with::serde_as; use smol_str::SmolStr; @@ -19,7 +20,6 @@ use starknet::providers::{Provider, ProviderError}; use thiserror::Error; use crate::contracts::model::ModelError; -use crate::contracts::world::ContractReaderError; use crate::contracts::WorldContractReader; #[cfg(test)] @@ -45,7 +45,7 @@ pub enum ManifestError { #[error(transparent)] Provider(#[from] ProviderError), #[error(transparent)] - ContractRead(#[from] ContractReaderError), + ContractRead(#[from] CainomeError), #[error(transparent)] Model(#[from] ModelError), } @@ -172,13 +172,14 @@ impl Manifest { err => err.into(), })?; - let world = WorldContractReader::new(world_address, &provider).with_block(BLOCK_ID); + let world = WorldContractReader::new(world_address, provider); - let executor_address = world.executor().await?; - let base_class_hash = world.base().await?; + let executor_address = world.executor().block_id(BLOCK_ID).call().await?; + let base_class_hash = world.base().block_id(BLOCK_ID).call().await?; - let executor_class_hash = provider - .get_class_hash_at(BLOCK_ID, executor_address) + let executor_class_hash = world + .provider() + .get_class_hash_at(BLOCK_ID, FieldElement::from(executor_address)) .await .map_err(|err| match err { ProviderError::StarknetError(StarknetError::ContractNotFound) => { @@ -187,7 +188,8 @@ impl Manifest { err => err.into(), })?; - let (models, contracts) = get_remote_models_and_contracts(world_address, provider).await?; + let (models, contracts) = + get_remote_models_and_contracts(world_address, &world.provider()).await?; Ok(Manifest { models, @@ -200,13 +202,13 @@ impl Manifest { }, executor: Contract { name: EXECUTOR_CONTRACT_NAME.into(), - address: Some(executor_address), + address: Some(executor_address.into()), class_hash: executor_class_hash, ..Default::default() }, base: Class { name: BASE_CONTRACT_NAME.into(), - class_hash: base_class_hash, + class_hash: base_class_hash.into(), ..Default::default() }, }) diff --git a/crates/sozo/src/ops/auth.rs b/crates/sozo/src/ops/auth.rs index 27125e9d6b..e2757f59e6 100644 --- a/crates/sozo/src/ops/auth.rs +++ b/crates/sozo/src/ops/auth.rs @@ -1,4 +1,5 @@ use anyhow::{Context, Result}; +use dojo_world::contracts::cairo_utils; use dojo_world::contracts::world::WorldContract; use dojo_world::metadata::Environment; @@ -14,7 +15,8 @@ pub async fn execute(command: AuthCommand, env_metadata: Option) -> let world = WorldContract::new(world_address, &account); let res = world - .grant_writer(&model, contract) + .grant_writer(&cairo_utils::str_to_felt(&model)?, &contract.into()) + .send() .await .with_context(|| "Failed to send transaction")?; diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs index 4b8fc8b851..1fbfb96b29 100644 --- a/crates/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/src/ops/migration/mod.rs @@ -1,6 +1,7 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; +use dojo_world::contracts::cairo_utils; use dojo_world::contracts::world::WorldContract; use dojo_world::manifest::{Manifest, ManifestError}; use dojo_world::metadata::dojo_metadata_from_workspace; @@ -292,7 +293,8 @@ where let addr = strategy.world_address()?; let InvokeTransactionResult { transaction_hash } = WorldContract::new(addr, &migrator) - .set_executor(executor.contract_address) + .set_executor(&executor.contract_address.into()) + .send() .await?; TransactionWaiter::new(transaction_hash, migrator.provider()).await?; @@ -343,9 +345,12 @@ where if let Some(meta) = metadata.as_ref().and_then(|inner| inner.world()) { match meta.upload().await { Ok(hash) => { + let encoded_uri = cairo_utils::encode_uri(&format!("ipfs://{hash}"))?; + let InvokeTransactionResult { transaction_hash } = WorldContract::new(world.contract_address, migrator) - .set_metadata_uri(FieldElement::ZERO, format!("ipfs://{hash}")) + .set_metadata_uri(&FieldElement::ZERO, &encoded_uri) + .send() .await .map_err(|e| anyhow!("Failed to set World metadata: {e}"))?; @@ -459,9 +464,16 @@ where } let world_address = strategy.world_address()?; + let world = WorldContract::new(world_address, migrator); + + let calls = models + .iter() + .map(|c| world.register_model_getcall(&c.diff.local.into())) + .collect::>(); - let InvokeTransactionResult { transaction_hash } = WorldContract::new(world_address, migrator) - .register_models(&models.iter().map(|c| c.diff.local).collect::>()) + let InvokeTransactionResult { transaction_hash } = migrator + .execute(calls) + .send() .await .map_err(|e| anyhow!("Failed to register models to World: {e}"))?; diff --git a/crates/sozo/src/ops/model.rs b/crates/sozo/src/ops/model.rs index c22404dcec..a6cce2444d 100644 --- a/crates/sozo/src/ops/model.rs +++ b/crates/sozo/src/ops/model.rs @@ -15,7 +15,7 @@ pub async fn execute(command: ModelCommands, env_metadata: Option) let world = WorldContractReader::new(world_address, &provider) .with_block(BlockId::Tag(BlockTag::Pending)); - let model = world.model(&name).await?; + let model = world.model_reader(&name).await?; println!("{:#x}", model.class_hash()); } @@ -27,7 +27,7 @@ pub async fn execute(command: ModelCommands, env_metadata: Option) let world = WorldContractReader::new(world_address, &provider) .with_block(BlockId::Tag(BlockTag::Pending)); - let model = world.model(&name).await?; + let model = world.model_reader(&name).await?; let schema = model.schema().await?; if to_json { @@ -44,7 +44,7 @@ pub async fn execute(command: ModelCommands, env_metadata: Option) let world = WorldContractReader::new(world_address, &provider) .with_block(BlockId::Tag(BlockTag::Pending)); - let model = world.model(&name).await?; + let model = world.model_reader(&name).await?; let entity = model.entity(&keys).await?; println!("{entity}") diff --git a/crates/sozo/src/ops/register.rs b/crates/sozo/src/ops/register.rs index e91a97a7f3..5906a90fc6 100644 --- a/crates/sozo/src/ops/register.rs +++ b/crates/sozo/src/ops/register.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; -use dojo_world::contracts::world::WorldContract; +use dojo_world::contracts::WorldContract; use dojo_world::metadata::Environment; +use starknet::accounts::Account; use crate::commands::register::RegisterCommand; @@ -13,8 +14,14 @@ pub async fn execute(command: RegisterCommand, env_metadata: Option let account = account.account(provider, env_metadata.as_ref()).await?; let world = WorldContract::new(world_address, &account); - let res = world - .register_models(&models) + let calls = models + .iter() + .map(|c| world.register_model_getcall(&(*c).into())) + .collect::>(); + + let res = account + .execute(calls) + .send() .await .with_context(|| "Failed to send transaction")?; diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs index a79ba11ac5..6f231a447a 100644 --- a/crates/torii/client/src/client/mod.rs +++ b/crates/torii/client/src/client/mod.rs @@ -71,7 +71,7 @@ impl Client { // TODO: change this to querying the gRPC url instead let subbed_models = subbed_models.models_keys.read().clone(); for keys in subbed_models { - let model_reader = world_reader.model(&keys.model).await?; + let model_reader = world_reader.model_reader(&keys.model).await?; let values = model_reader.entity_storage(&keys.keys).await?; client_storage.set_model_storage( @@ -137,7 +137,7 @@ impl Client { }; if !self.subscribed_models.is_synced(keys) { - let model = self.world_reader.model(&keys.model).await?; + let model = self.world_reader.model_reader(&keys.model).await?; return Ok(Some(model.entity(&keys.keys).await?)); } @@ -232,7 +232,7 @@ impl Client { } async fn initiate_model(&self, model: &str, keys: Vec) -> Result<(), Error> { - let model_reader = self.world_reader.model(model).await?; + let model_reader = self.world_reader.model_reader(model).await?; let values = model_reader.entity_storage(&keys).await?; self.storage.set_model_storage( cairo_short_string_to_felt(model).map_err(ParseError::CairoShortStringToFelt)?, diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index 6f4724e7c5..55e6a57114 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -178,7 +178,7 @@ impl<'db, P: Provider + Sync> Engine<'db, P> { let mut world_event = false; for (event_idx, event) in invoke_receipt.events.iter().enumerate() { - if event.from_address != self.world.address() { + if event.from_address != self.world.address { continue; } diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index e8cb64da42..d503671b7d 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -14,7 +14,7 @@ pub mod store_transaction; #[async_trait] pub trait EventProcessor

where - P: Provider, + P: Provider + Sync, { fn event_key(&self) -> String; diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/core/src/processors/register_model.rs index 54b01f946d..1771605f6c 100644 --- a/crates/torii/core/src/processors/register_model.rs +++ b/crates/torii/core/src/processors/register_model.rs @@ -45,7 +45,7 @@ where ) -> Result<(), Error> { let name = parse_cairo_short_string(&event.data[0])?; - let model = world.model(&name).await?; + let model = world.model_reader(&name).await?; let schema = model.schema().await?; let layout = model.layout().await?; diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 9474cc125c..883898be30 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -10,7 +10,7 @@ dependencies = [ [[package]] name = "dojo_examples" -version = "0.4.0-rc0" +version = "0.4.1" dependencies = [ "dojo", ] From 945db9bdd4065aeba010c7877f85c5339ee74d49 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 20 Dec 2023 12:57:41 -0700 Subject: [PATCH 186/192] Update release flow --- .github/workflows/release-dispatch.yml | 6 ----- .github/workflows/release.yml | 34 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index c8ec5c6845..cd43bbf812 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -28,11 +28,6 @@ jobs: run: | cargo install cargo-get echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - - id: changelog - uses: mikepenz/release-changelog-builder-action@v4.1.0 - with: - fromTag: v${{ steps.current_release_info.outputs.version }} - toTag: main - uses: peter-evans/create-pull-request@v5 with: # We have to use a PAT in order to trigger ci @@ -42,4 +37,3 @@ jobs: branch: prepare-release base: main delete-branch: true - body: ${{steps.changelog.outputs.changelog}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe9d0d75a0..7ddd1751b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,19 +118,8 @@ jobs: fi shell: bash - # Creates the release for this specific version - - name: Create release - uses: softprops/action-gh-release@v1 - with: - name: ${{ github.event.pull_request.title }} - tag_name: ${{ needs.prepare.outputs.tag_name }} - body: ${{ github.event.pull_request.body }} - files: | - ${{ steps.artifacts.outputs.file_name }} - # We move binaries so they match $TARGETPLATFORM in the Docker build - name: Move Binaries - if: ${{ env.PLATFORM_NAME == 'linux' }} run: | mkdir -p $PLATFORM_NAME/$ARCH mv target/$TARGET/release/katana $PLATFORM_NAME/$ARCH @@ -146,8 +135,24 @@ jobs: path: ${{ env.PLATFORM_NAME }} retention-days: 1 + create-draft-release: + runs-on: ubuntu-latest-4-cores + needs: [prepare, release] + env: + GITHUB_USER: ${{ github.repository_owner }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v3 + with: + name: binaries + path: artifacts + - name: Display structure of downloaded files + run: ls -R artifacts + - run: gh release create --generate-notes --draft ./artifacts + docker-build-and-push: - name: Build and push docker image runs-on: ubuntu-latest-4-cores needs: [prepare, release] @@ -159,7 +164,10 @@ jobs: uses: actions/download-artifact@v3 with: name: binaries - path: artifacts/linux + path: artifacts + + - name: Display structure of downloaded files + run: ls -R artifacts - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 From cb2e3be93fc5f2f94f460e738b54476c66ec5c88 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 20 Dec 2023 15:20:56 -0500 Subject: [PATCH 187/192] Prepare release: v0.4.2 (#1322) --- Cargo.lock | 62 +++++++++++++++--------------- Cargo.toml | 2 +- crates/dojo-core/Scarb.lock | 2 +- crates/dojo-core/Scarb.toml | 2 +- crates/torii/types-test/Scarb.lock | 2 +- crates/torii/types-test/Scarb.toml | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1ecb5a4b1..6d66fe6cca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,7 +801,7 @@ dependencies = [ [[package]] name = "benches" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "clap_builder", @@ -2670,15 +2670,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-core" -version = "0.4.1" +version = "0.4.2" [[package]] name = "dojo-examples-spawn-and-move" -version = "0.4.1" +version = "0.4.2" [[package]] name = "dojo-lang" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2726,7 +2726,7 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -2748,7 +2748,7 @@ dependencies = [ [[package]] name = "dojo-signers" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "starknet", @@ -2756,7 +2756,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -2787,7 +2787,7 @@ dependencies = [ [[package]] name = "dojo-types" -version = "0.4.1" +version = "0.4.2" dependencies = [ "crypto-bigint", "hex", @@ -2802,7 +2802,7 @@ dependencies = [ [[package]] name = "dojo-world" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -5441,7 +5441,7 @@ dependencies = [ [[package]] name = "katana" -version = "0.4.1" +version = "0.4.2" dependencies = [ "assert_matches", "clap", @@ -5449,7 +5449,7 @@ dependencies = [ "console", "katana-core", "katana-rpc", - "metrics 0.4.1", + "metrics 0.4.2", "metrics-process", "serde_json", "starknet_api", @@ -5461,7 +5461,7 @@ dependencies = [ [[package]] name = "katana-codecs" -version = "0.4.1" +version = "0.4.2" dependencies = [ "bytes", "katana-primitives", @@ -5469,7 +5469,7 @@ dependencies = [ [[package]] name = "katana-codecs-derive" -version = "0.4.1" +version = "0.4.2" dependencies = [ "proc-macro2", "quote", @@ -5479,7 +5479,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_matches", @@ -5512,7 +5512,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "bincode 1.3.3", @@ -5535,7 +5535,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "blockifier", @@ -5551,7 +5551,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "blockifier", @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "auto_impl", @@ -5591,7 +5591,7 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_matches", @@ -5623,7 +5623,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "derive_more", @@ -5636,7 +5636,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "katana-executor", @@ -5648,7 +5648,7 @@ dependencies = [ [[package]] name = "katana-runner" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "home", @@ -5939,7 +5939,7 @@ dependencies = [ [[package]] name = "metrics" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "hyper", @@ -8608,7 +8608,7 @@ dependencies = [ [[package]] name = "sozo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_fs", @@ -9802,7 +9802,7 @@ dependencies = [ [[package]] name = "torii-client" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "camino", @@ -9828,7 +9828,7 @@ dependencies = [ [[package]] name = "torii-core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-trait", @@ -9864,7 +9864,7 @@ dependencies = [ [[package]] name = "torii-graphql" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-graphql", @@ -9903,7 +9903,7 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "0.4.1" +version = "0.4.2" dependencies = [ "bytes", "crypto-bigint", @@ -9942,7 +9942,7 @@ dependencies = [ [[package]] name = "torii-server" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-trait", @@ -9961,7 +9961,7 @@ dependencies = [ "hyper-reverse-proxy", "indexmap 1.9.3", "lazy_static", - "metrics 0.4.1", + "metrics 0.4.2", "metrics-process", "scarb", "serde", @@ -10229,7 +10229,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "types-test" -version = "0.4.1" +version = "0.4.2" [[package]] name = "ucd-trie" diff --git a/Cargo.toml b/Cargo.toml index d50ab59a7e..1ece9cfe98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "0.4.1" +version = "0.4.2" [profile.performance] codegen-units = 1 diff --git a/crates/dojo-core/Scarb.lock b/crates/dojo-core/Scarb.lock index ee17c212ea..14d33d1dbb 100644 --- a/crates/dojo-core/Scarb.lock +++ b/crates/dojo-core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index a8809ef181..ef1080e43a 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "2.4.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "0.4.1" +version = "0.4.2" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo", tag = "v0.3.11" } diff --git a/crates/torii/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock index 4f2d7aa41c..736f84366b 100644 --- a/crates/torii/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/crates/torii/types-test/Scarb.toml b/crates/torii/types-test/Scarb.toml index 3bb6831b41..ac5bfa187f 100644 --- a/crates/torii/types-test/Scarb.toml +++ b/crates/torii/types-test/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "types_test" -version = "0.4.1" +version = "0.4.2" [cairo] sierra-replace-ids = true diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 883898be30..48750706e3 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dojo_plugin", ] diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 35969ed10f..2383af1a8e 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "2.4.0" name = "dojo_examples" -version = "0.4.1" +version = "0.4.2" # Use the prelude with the less imports as possible # from corelib. edition = "2023_10" From 490dab093672ffc52dd7aa5ee8a9251b6123cfc6 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Thu, 21 Dec 2023 04:33:18 +0800 Subject: [PATCH 188/192] feat(katana-provider): implement a DB provider (#1299) * wip * wip * add test for de/compress contact class * wip * update * update * update test * impl state update provider + tests * update * increase code coverage --- Cargo.lock | 55 +- crates/katana/primitives/src/block.rs | 2 +- crates/katana/primitives/src/state.rs | 2 +- crates/katana/storage/db/Cargo.toml | 7 +- crates/katana/storage/db/benches/codec.rs | 2 +- .../katana/storage/db/src/codecs/postcard.rs | 11 +- crates/katana/storage/db/src/mdbx/cursor.rs | 13 +- crates/katana/storage/db/src/mdbx/mod.rs | 9 +- crates/katana/storage/db/src/models/class.rs | 500 ++++++++++ .../katana/storage/db/src/models/contract.rs | 486 +--------- crates/katana/storage/db/src/models/mod.rs | 1 + .../katana/storage/db/src/models/storage.rs | 103 ++- crates/katana/storage/db/src/tables.rs | 72 +- crates/katana/storage/provider/Cargo.toml | 1 + .../storage/provider/src/providers/db/mod.rs | 864 ++++++++++++++++++ .../provider/src/providers/db/state.rs | 337 +++++++ .../storage/provider/src/providers/mod.rs | 1 + .../provider/src/traits/transaction.rs | 5 + crates/katana/storage/provider/tests/block.rs | 141 ++- crates/katana/storage/provider/tests/class.rs | 23 + .../katana/storage/provider/tests/contract.rs | 25 +- .../katana/storage/provider/tests/fixtures.rs | 101 +- .../katana/storage/provider/tests/storage.rs | 23 + 23 files changed, 2225 insertions(+), 559 deletions(-) create mode 100644 crates/katana/storage/db/src/models/class.rs create mode 100644 crates/katana/storage/provider/src/providers/db/mod.rs create mode 100644 crates/katana/storage/provider/src/providers/db/state.rs diff --git a/Cargo.lock b/Cargo.lock index 6d66fe6cca..10d5c7b210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,15 +827,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -1727,7 +1718,7 @@ version = "0.8.2" source = "git+https://github.com/dojoengine/cairo-rs.git?rev=262b7eb4b11ab165a2a936a5f914e78aa732d4a2#262b7eb4b11ab165a2a936a5f914e78aa732d4a2" dependencies = [ "anyhow", - "bincode 2.0.0-rc.3", + "bincode", "bitvec", "cairo-felt", "generic-array", @@ -1888,9 +1879,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c90e95e5bd4e8ac34fa6f37c774b0c6f8ed06ea90c79931fd448fcf941a9767" +checksum = "e5fdbb015d790cfb378aca82caf9cc52a38be96a7eecdb92f31b4366a8afc019" dependencies = [ "clap", "log", @@ -2074,9 +2065,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const_format" @@ -2969,9 +2960,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adbf0983fe06bd3a5c19c8477a637c2389feb0994eca7a59e3b961054aa7c0a" +checksum = "a3286168faae03a0e583f6fde17c02c8b8bba2dcc2061d0f7817066e5b0af706" dependencies = [ "serde", ] @@ -3344,9 +3335,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" dependencies = [ "indenter", "once_cell", @@ -4286,9 +4277,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.39.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2069adc212cf7f3317ef55f6444abd06c50f28479dbbac5a86acf3b05cbbfe" +checksum = "1ac23ed741583c792f573c028785db683496a6dfcd672ec701ee54ba6a77e1ff" dependencies = [ "gix-actor", "gix-date", @@ -4756,11 +4747,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -5515,7 +5506,6 @@ name = "katana-db" version = "0.4.2" dependencies = [ "anyhow", - "bincode 1.3.3", "blockifier", "cairo-lang-starknet", "cairo-vm", @@ -5583,6 +5573,7 @@ dependencies = [ "rstest", "rstest_reuse", "starknet", + "tempfile", "thiserror", "tokio", "tracing", @@ -9363,18 +9354,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -10975,18 +10966,18 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.31" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.31" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index b451dbc3f1..f5f762f462 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -115,7 +115,7 @@ pub struct Block { } /// A block with only the transaction hashes. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct BlockWithTxHashes { pub header: Header, pub body: Vec, diff --git a/crates/katana/primitives/src/state.rs b/crates/katana/primitives/src/state.rs index ed95f1a682..65cfa8341a 100644 --- a/crates/katana/primitives/src/state.rs +++ b/crates/katana/primitives/src/state.rs @@ -8,7 +8,7 @@ use crate::contract::{ /// State updates. /// /// Represents all the state updates after performing some executions on a state. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StateUpdates { /// A mapping of contract addresses to their updated nonces. diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 75870a70fd..87de9a249d 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true katana-primitives = { path = "../../primitives" } anyhow.workspace = true -bincode = "1.3.3" page_size = "0.6.0" parking_lot.workspace = true serde.workspace = true @@ -23,7 +22,11 @@ cairo-vm.workspace = true starknet_api.workspace = true # codecs -postcard = { version = "1.0.8", optional = true, default-features = false, features = [ "use-std" ] } +[dependencies.postcard] +default-features = false +features = [ "use-std" ] +optional = true +version = "1.0.8" [dependencies.libmdbx] git = "https://github.com/paradigmxyz/reth.git" diff --git a/crates/katana/storage/db/benches/codec.rs b/crates/katana/storage/db/benches/codec.rs index e74344ec92..183667a021 100644 --- a/crates/katana/storage/db/benches/codec.rs +++ b/crates/katana/storage/db/benches/codec.rs @@ -2,7 +2,7 @@ use blockifier::execution::contract_class::ContractClassV1; use cairo_lang_starknet::casm_contract_class::CasmContractClass; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use katana_db::codecs::{Compress, Decompress}; -use katana_db::models::contract::StoredContractClass; +use katana_db::models::class::StoredContractClass; use katana_primitives::contract::CompiledContractClass; fn compress_contract(contract: CompiledContractClass) -> Vec { diff --git a/crates/katana/storage/db/src/codecs/postcard.rs b/crates/katana/storage/db/src/codecs/postcard.rs index 0564dc3370..66a7ccddc7 100644 --- a/crates/katana/storage/db/src/codecs/postcard.rs +++ b/crates/katana/storage/db/src/codecs/postcard.rs @@ -1,4 +1,4 @@ -use katana_primitives::block::Header; +use katana_primitives::block::{BlockNumber, Header}; use katana_primitives::contract::{ContractAddress, GenericContractInfo, SierraClass}; use katana_primitives::receipt::Receipt; use katana_primitives::transaction::Tx; @@ -7,7 +7,8 @@ use katana_primitives::FieldElement; use super::{Compress, Decompress}; use crate::error::CodecError; use crate::models::block::StoredBlockBodyIndices; -use crate::models::contract::StoredContractClass; +use crate::models::class::StoredContractClass; +use crate::models::contract::ContractInfoChangeList; macro_rules! impl_compress_and_decompress_for_table_values { ($($name:ty),*) => { @@ -21,7 +22,7 @@ macro_rules! impl_compress_and_decompress_for_table_values { impl Decompress for $name { fn decompress>(bytes: B) -> Result { - postcard::from_bytes(bytes.as_ref()).map_err(|e| CodecError::Decode(e.to_string())) + postcard::from_bytes(bytes.as_ref()).map_err(|e| CodecError::Decompress(e.to_string())) } } )* @@ -36,7 +37,9 @@ impl_compress_and_decompress_for_table_values!( SierraClass, FieldElement, ContractAddress, + Vec, StoredContractClass, GenericContractInfo, - StoredBlockBodyIndices + StoredBlockBodyIndices, + ContractInfoChangeList ); diff --git a/crates/katana/storage/db/src/mdbx/cursor.rs b/crates/katana/storage/db/src/mdbx/cursor.rs index 943d92c3df..9cac3876e3 100644 --- a/crates/katana/storage/db/src/mdbx/cursor.rs +++ b/crates/katana/storage/db/src/mdbx/cursor.rs @@ -141,7 +141,7 @@ impl Cursor { &mut self, key: Option, subkey: Option, - ) -> Result, DatabaseError> { + ) -> Result>, DatabaseError> { let start = match (key, subkey) { (Some(key), Some(subkey)) => { // encode key and decode it after. @@ -154,10 +154,17 @@ impl Cursor { (Some(key), None) => { let key: Vec = key.encode().into(); - self.inner + + let Some(start) = self + .inner .set(key.as_ref()) .map_err(DatabaseError::Read)? .map(|val| decoder::((Cow::Owned(key), val))) + else { + return Ok(None); + }; + + Some(start) } (None, Some(subkey)) => { @@ -175,7 +182,7 @@ impl Cursor { (None, None) => self.first().transpose(), }; - Ok(DupWalker::new(self, start)) + Ok(Some(DupWalker::new(self, start))) } } diff --git a/crates/katana/storage/db/src/mdbx/mod.rs b/crates/katana/storage/db/src/mdbx/mod.rs index 76fc9b1cf3..3b31ba0507 100644 --- a/crates/katana/storage/db/src/mdbx/mod.rs +++ b/crates/katana/storage/db/src/mdbx/mod.rs @@ -112,18 +112,17 @@ impl DbEnv { #[cfg(any(test, feature = "test-utils"))] pub mod test_utils { use std::path::Path; - use std::sync::Arc; use super::{DbEnv, DbEnvKind}; const ERROR_DB_CREATION: &str = "Not able to create the mdbx file."; /// Create database for testing - pub fn create_test_db(kind: DbEnvKind) -> Arc { - Arc::new(create_test_db_with_path( + pub fn create_test_db(kind: DbEnvKind) -> DbEnv { + create_test_db_with_path( kind, &tempfile::TempDir::new().expect("Failed to create temp dir.").into_path(), - )) + ) } /// Create database for testing with specified path @@ -392,7 +391,7 @@ mod tests { { let tx = env.tx().expect(ERROR_INIT_TX); let mut cursor = tx.cursor::().unwrap(); - let mut walker = cursor.walk_dup(Some(key), Some(felt!("1"))).unwrap(); + let mut walker = cursor.walk_dup(Some(key), Some(felt!("1"))).unwrap().unwrap(); assert_eq!( (key, value11), diff --git a/crates/katana/storage/db/src/models/class.rs b/crates/katana/storage/db/src/models/class.rs new file mode 100644 index 0000000000..b30ae84ed6 --- /dev/null +++ b/crates/katana/storage/db/src/models/class.rs @@ -0,0 +1,500 @@ +//! Serializable without using custome functions + +use std::collections::HashMap; +use std::sync::Arc; + +use blockifier::execution::contract_class::{ + ContractClass, ContractClassV0, ContractClassV0Inner, ContractClassV1, ContractClassV1Inner, +}; +use cairo_vm::felt::Felt252; +use cairo_vm::hint_processor::hint_processor_definition::HintReference; +use cairo_vm::serde::deserialize_program::{ + ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, + InstructionLocation, Member, OffsetValue, +}; +use cairo_vm::types::program::{Program, SharedProgramData}; +use cairo_vm::types::relocatable::MaybeRelocatable; +use serde::{Deserialize, Serialize}; +use starknet_api::core::EntryPointSelector; +use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset, EntryPointType}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum StoredContractClass { + V0(StoredContractClassV0), + V1(StoredContractClassV1), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct StoredContractClassV0 { + pub program: SerializableProgram, + pub entry_points_by_type: HashMap>, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct StoredContractClassV1 { + pub program: SerializableProgram, + pub hints: HashMap>, + pub entry_points_by_type: HashMap>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableEntryPoint { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, +} + +impl From for SerializableEntryPoint { + fn from(value: EntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +impl From for EntryPoint { + fn from(value: SerializableEntryPoint) -> Self { + Self { selector: value.selector, offset: value.offset.into() } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableEntryPointOffset(pub usize); + +impl From for SerializableEntryPointOffset { + fn from(value: EntryPointOffset) -> Self { + Self(value.0) + } +} + +impl From for EntryPointOffset { + fn from(value: SerializableEntryPointOffset) -> Self { + Self(value.0) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableEntryPointV1 { + pub selector: EntryPointSelector, + pub offset: SerializableEntryPointOffset, + pub builtins: Vec, +} + +impl From for blockifier::execution::contract_class::EntryPointV1 { + fn from(value: SerializableEntryPointV1) -> Self { + blockifier::execution::contract_class::EntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +impl From for SerializableEntryPointV1 { + fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { + SerializableEntryPointV1 { + selector: value.selector, + offset: value.offset.into(), + builtins: value.builtins, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableProgram { + pub shared_program_data: SerializableSharedProgramData, + pub constants: HashMap, + pub builtins: Vec, +} + +impl From for SerializableProgram { + fn from(value: Program) -> Self { + Self { + shared_program_data: value.shared_program_data.as_ref().clone().into(), + constants: value.constants, + builtins: value.builtins, + } + } +} + +impl From for Program { + fn from(value: SerializableProgram) -> Self { + Self { + shared_program_data: Arc::new(value.shared_program_data.into()), + constants: value.constants, + builtins: value.builtins, + } + } +} + +// Fields of `SerializableProgramData` must not rely on `deserialize_any` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableSharedProgramData { + pub data: Vec, + pub hints: HashMap>, + pub main: Option, + pub start: Option, + pub end: Option, + pub error_message_attributes: Vec, + pub instruction_locations: Option>, + pub identifiers: HashMap, + pub reference_manager: Vec, +} + +impl From for SerializableSharedProgramData { + fn from(value: SharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +impl From for SharedProgramData { + fn from(value: SerializableSharedProgramData) -> Self { + Self { + data: value.data, + hints: value + .hints + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + main: value.main, + start: value.start, + end: value.end, + error_message_attributes: value + .error_message_attributes + .into_iter() + .map(|a| a.into()) + .collect(), + instruction_locations: value.instruction_locations, + identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), + reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintParams { + pub code: String, + pub accessible_scopes: Vec, + pub flow_tracking_data: SerializableFlowTrackingData, +} + +impl From for SerializableHintParams { + fn from(value: HintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +impl From for HintParams { + fn from(value: SerializableHintParams) -> Self { + Self { + code: value.code, + accessible_scopes: value.accessible_scopes, + flow_tracking_data: value.flow_tracking_data.into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableIdentifier { + pub pc: Option, + pub type_: Option, + pub value: Option, + pub full_name: Option, + pub members: Option>, + pub cairo_type: Option, +} + +impl From for SerializableIdentifier { + fn from(value: Identifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +impl From for Identifier { + fn from(value: SerializableIdentifier) -> Self { + Self { + pc: value.pc, + type_: value.type_, + value: value.value, + full_name: value.full_name, + members: value.members, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableHintReference { + pub offset1: OffsetValue, + pub offset2: OffsetValue, + pub dereference: bool, + pub ap_tracking_data: Option, + pub cairo_type: Option, +} + +impl From for SerializableHintReference { + fn from(value: HintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +impl From for HintReference { + fn from(value: SerializableHintReference) -> Self { + Self { + offset1: value.offset1, + offset2: value.offset2, + dereference: value.dereference, + ap_tracking_data: value.ap_tracking_data, + cairo_type: value.cairo_type, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableAttribute { + pub name: String, + pub start_pc: usize, + pub end_pc: usize, + pub value: String, + pub flow_tracking_data: Option, +} + +impl From for SerializableAttribute { + fn from(value: Attribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +impl From for Attribute { + fn from(value: SerializableAttribute) -> Self { + Self { + name: value.name, + start_pc: value.start_pc, + end_pc: value.end_pc, + value: value.value, + flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SerializableFlowTrackingData { + pub ap_tracking: ApTracking, + pub reference_ids: HashMap, +} + +impl From for SerializableFlowTrackingData { + fn from(value: FlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} + +impl From for FlowTrackingData { + fn from(value: SerializableFlowTrackingData) -> Self { + Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } + } +} + +impl From for ContractClass { + fn from(value: StoredContractClass) -> Self { + match value { + StoredContractClass::V0(v0) => { + ContractClass::V0(ContractClassV0(Arc::new(ContractClassV0Inner { + program: v0.program.into(), + entry_points_by_type: v0 + .entry_points_by_type + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) + .collect(), + }))) + } + StoredContractClass::V1(v1) => { + ContractClass::V1(ContractClassV1(Arc::new(ContractClassV1Inner { + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::from_slice(&v).expect("valid hint"))) + .collect(), + program: v1.program.into(), + entry_points_by_type: v1 + .entry_points_by_type + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + }))) + } + } + } +} + +impl From for StoredContractClass { + fn from(value: ContractClass) -> Self { + match value { + ContractClass::V0(v0) => { + let entry_points_by_type = v0 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(SerializableEntryPoint::from).collect())) + .collect(); + + StoredContractClass::V0(StoredContractClassV0 { + program: v0.program.clone().into(), + entry_points_by_type, + }) + } + + ContractClass::V1(v1) => StoredContractClass::V1(StoredContractClassV1 { + program: v1.program.clone().into(), + entry_points_by_type: v1 + .entry_points_by_type + .clone() + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .map(Into::into) + .collect::>(), + ) + }) + .collect::>(), + hints: v1 + .hints + .clone() + .into_iter() + .map(|(k, v)| (k, serde_json::to_vec(&v).expect("valid hint"))) + .collect(), + }), + } + } +} + +#[cfg(test)] +mod tests { + use cairo_lang_starknet::casm_contract_class::CasmContractClass; + use katana_primitives::contract::CompiledContractClass; + use starknet_api::hash::StarkFelt; + use starknet_api::stark_felt; + + use super::*; + use crate::codecs::{Compress, Decompress}; + + #[test] + fn serialize_deserialize_legacy_entry_points() { + let non_serde = vec![ + EntryPoint { + offset: EntryPointOffset(0x25f), + selector: EntryPointSelector(stark_felt!( + "0x289da278a8dc833409cabfdad1581e8e7d40e42dcaed693fa4008dcdb4963b3" + )), + }, + EntryPoint { + offset: EntryPointOffset(0x1b2), + selector: EntryPointSelector(stark_felt!( + "0x29e211664c0b63c79638fbea474206ca74016b3e9a3dc4f9ac300ffd8bdf2cd" + )), + }, + EntryPoint { + offset: EntryPointOffset(0x285), + selector: EntryPointSelector(stark_felt!( + "0x36fcbf06cd96843058359e1a75928beacfac10727dab22a3972f0af8aa92895" + )), + }, + ]; + + // convert to serde and back + let serde: Vec = + non_serde.iter().map(|e| e.clone().into()).collect(); + + // convert to json + let json = serde_json::to_vec(&serde).unwrap(); + let serde: Vec = serde_json::from_slice(&json).unwrap(); + + let same_non_serde: Vec = serde.iter().map(|e| e.clone().into()).collect(); + + assert_eq!(non_serde, same_non_serde); + } + + #[test] + fn compress_and_decompress_contract_class() { + let class = + serde_json::from_slice(include_bytes!("../../benches/artifacts/dojo_world_240.json")) + .unwrap(); + + let class = CasmContractClass::from_contract_class(class, true).unwrap(); + let class = CompiledContractClass::V1(ContractClassV1::try_from(class).unwrap()); + + let compressed = StoredContractClass::from(class.clone()).compress(); + let decompressed = ::decompress(compressed).unwrap(); + + let actual_class = CompiledContractClass::from(decompressed); + + assert_eq!(class, actual_class); + } + + #[test] + fn compress_and_decompress_legacy_contract_class() { + let class: ContractClassV0 = serde_json::from_slice(include_bytes!( + "../../../../core/contracts/compiled/account.json" + )) + .unwrap(); + + let class = CompiledContractClass::V0(class); + + let compressed = StoredContractClass::from(class.clone()).compress(); + let decompressed = ::decompress(compressed).unwrap(); + + let actual_class = CompiledContractClass::from(decompressed); + + assert_eq!(class, actual_class); + } +} diff --git a/crates/katana/storage/db/src/models/contract.rs b/crates/katana/storage/db/src/models/contract.rs index 92894bb601..25c9818671 100644 --- a/crates/katana/storage/db/src/models/contract.rs +++ b/crates/katana/storage/db/src/models/contract.rs @@ -1,463 +1,65 @@ -//! Serializable without using custome functions - -use std::collections::HashMap; -use std::sync::Arc; - -use blockifier::execution::contract_class::{ - ContractClass, ContractClassV0, ContractClassV0Inner, ContractClassV1, ContractClassV1Inner, -}; -use cairo_vm::felt::Felt252; -use cairo_vm::hint_processor::hint_processor_definition::HintReference; -use cairo_vm::serde::deserialize_program::{ - ApTracking, Attribute, BuiltinName, FlowTrackingData, HintParams, Identifier, - InstructionLocation, Member, OffsetValue, -}; -use cairo_vm::types::program::{Program, SharedProgramData}; -use cairo_vm::types::relocatable::MaybeRelocatable; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{ClassHash, ContractAddress, Nonce}; use serde::{Deserialize, Serialize}; -use starknet_api::core::EntryPointSelector; -use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointOffset, EntryPointType}; - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum StoredContractClass { - V0(StoredContractClassV0), - V1(StoredContractClassV1), -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct StoredContractClassV0 { - pub program: SerializableProgram, - pub entry_points_by_type: HashMap>, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct StoredContractClassV1 { - pub program: SerializableProgram, - pub hints: HashMap>, - pub entry_points_by_type: HashMap>, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableEntryPoint { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, -} - -impl From for SerializableEntryPoint { - fn from(value: EntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -impl From for EntryPoint { - fn from(value: SerializableEntryPoint) -> Self { - Self { selector: value.selector, offset: value.offset.into() } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableEntryPointOffset(pub usize); - -impl From for SerializableEntryPointOffset { - fn from(value: EntryPointOffset) -> Self { - Self(value.0) - } -} - -impl From for EntryPointOffset { - fn from(value: SerializableEntryPointOffset) -> Self { - Self(value.0) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableEntryPointV1 { - pub selector: EntryPointSelector, - pub offset: SerializableEntryPointOffset, - pub builtins: Vec, -} - -impl From for blockifier::execution::contract_class::EntryPointV1 { - fn from(value: SerializableEntryPointV1) -> Self { - blockifier::execution::contract_class::EntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -impl From for SerializableEntryPointV1 { - fn from(value: blockifier::execution::contract_class::EntryPointV1) -> Self { - SerializableEntryPointV1 { - selector: value.selector, - offset: value.offset.into(), - builtins: value.builtins, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableProgram { - pub shared_program_data: SerializableSharedProgramData, - pub constants: HashMap, - pub builtins: Vec, -} - -impl From for SerializableProgram { - fn from(value: Program) -> Self { - Self { - shared_program_data: value.shared_program_data.as_ref().clone().into(), - constants: value.constants, - builtins: value.builtins, - } - } -} - -impl From for Program { - fn from(value: SerializableProgram) -> Self { - Self { - shared_program_data: Arc::new(value.shared_program_data.into()), - constants: value.constants, - builtins: value.builtins, - } - } -} - -// Fields of `SerializableProgramData` must not rely on `deserialize_any` -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableSharedProgramData { - pub data: Vec, - pub hints: HashMap>, - pub main: Option, - pub start: Option, - pub end: Option, - pub error_message_attributes: Vec, - pub instruction_locations: Option>, - pub identifiers: HashMap, - pub reference_manager: Vec, -} - -impl From for SerializableSharedProgramData { - fn from(value: SharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -impl From for SharedProgramData { - fn from(value: SerializableSharedProgramData) -> Self { - Self { - data: value.data, - hints: value - .hints - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - main: value.main, - start: value.start, - end: value.end, - error_message_attributes: value - .error_message_attributes - .into_iter() - .map(|a| a.into()) - .collect(), - instruction_locations: value.instruction_locations, - identifiers: value.identifiers.into_iter().map(|(k, v)| (k, v.into())).collect(), - reference_manager: value.reference_manager.into_iter().map(|r| r.into()).collect(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintParams { - pub code: String, - pub accessible_scopes: Vec, - pub flow_tracking_data: SerializableFlowTrackingData, -} - -impl From for SerializableHintParams { - fn from(value: HintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -impl From for HintParams { - fn from(value: SerializableHintParams) -> Self { - Self { - code: value.code, - accessible_scopes: value.accessible_scopes, - flow_tracking_data: value.flow_tracking_data.into(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableIdentifier { - pub pc: Option, - pub type_: Option, - pub value: Option, - pub full_name: Option, - pub members: Option>, - pub cairo_type: Option, -} - -impl From for SerializableIdentifier { - fn from(value: Identifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} - -impl From for Identifier { - fn from(value: SerializableIdentifier) -> Self { - Self { - pc: value.pc, - type_: value.type_, - value: value.value, - full_name: value.full_name, - members: value.members, - cairo_type: value.cairo_type, - } - } -} -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableHintReference { - pub offset1: OffsetValue, - pub offset2: OffsetValue, - pub dereference: bool, - pub ap_tracking_data: Option, - pub cairo_type: Option, -} +use crate::codecs::{Compress, Decode, Decompress, Encode}; -impl From for SerializableHintReference { - fn from(value: HintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } -} +pub type BlockList = Vec; -impl From for HintReference { - fn from(value: SerializableHintReference) -> Self { - Self { - offset1: value.offset1, - offset2: value.offset2, - dereference: value.dereference, - ap_tracking_data: value.ap_tracking_data, - cairo_type: value.cairo_type, - } - } +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct ContractInfoChangeList { + pub class_change_list: BlockList, + pub nonce_change_list: BlockList, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableAttribute { - pub name: String, - pub start_pc: usize, - pub end_pc: usize, - pub value: String, - pub flow_tracking_data: Option, +#[derive(Debug)] +pub struct ContractClassChange { + pub contract_address: ContractAddress, + /// The updated class hash of `contract_address`. + pub class_hash: ClassHash, } -impl From for SerializableAttribute { - fn from(value: Attribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } +impl Compress for ContractClassChange { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut buf = Vec::new(); + buf.extend_from_slice(self.contract_address.encode().as_ref()); + buf.extend_from_slice(self.class_hash.compress().as_ref()); + buf } } -impl From for Attribute { - fn from(value: SerializableAttribute) -> Self { - Self { - name: value.name, - start_pc: value.start_pc, - end_pc: value.end_pc, - value: value.value, - flow_tracking_data: value.flow_tracking_data.map(|d| d.into()), - } +impl Decompress for ContractClassChange { + fn decompress>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let contract_address = ContractAddress::decode(&bytes[0..32])?; + let class_hash = ClassHash::decompress(&bytes[32..])?; + Ok(Self { contract_address, class_hash }) } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializableFlowTrackingData { - pub ap_tracking: ApTracking, - pub reference_ids: HashMap, +#[derive(Debug)] +pub struct ContractNonceChange { + pub contract_address: ContractAddress, + /// The updated nonce value of `contract_address`. + pub nonce: Nonce, } -impl From for SerializableFlowTrackingData { - fn from(value: FlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } +impl Compress for ContractNonceChange { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut buf = Vec::new(); + buf.extend_from_slice(&self.contract_address.encode()); + buf.extend_from_slice(&self.nonce.compress()); + buf } } -impl From for FlowTrackingData { - fn from(value: SerializableFlowTrackingData) -> Self { - Self { ap_tracking: value.ap_tracking, reference_ids: value.reference_ids } - } -} - -impl From for ContractClass { - fn from(value: StoredContractClass) -> Self { - match value { - StoredContractClass::V0(v0) => { - ContractClass::V0(ContractClassV0(Arc::new(ContractClassV0Inner { - program: v0.program.into(), - entry_points_by_type: v0 - .entry_points_by_type - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|h| h.into()).collect())) - .collect(), - }))) - } - StoredContractClass::V1(v1) => { - ContractClass::V1(ContractClassV1(Arc::new(ContractClassV1Inner { - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::from_slice(&v).expect("valid hint"))) - .collect(), - program: v1.program.into(), - entry_points_by_type: v1 - .entry_points_by_type - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - }))) - } - } - } -} - -impl From for StoredContractClass { - fn from(value: ContractClass) -> Self { - match value { - ContractClass::V0(v0) => { - let entry_points_by_type = v0 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(SerializableEntryPoint::from).collect())) - .collect(); - - StoredContractClass::V0(StoredContractClassV0 { - program: v0.program.clone().into(), - entry_points_by_type, - }) - } - - ContractClass::V1(v1) => StoredContractClass::V1(StoredContractClassV1 { - program: v1.program.clone().into(), - entry_points_by_type: v1 - .entry_points_by_type - .clone() - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(Into::into) - .collect::>(), - ) - }) - .collect::>(), - hints: v1 - .hints - .clone() - .into_iter() - .map(|(k, v)| (k, serde_json::to_vec(&v).expect("valid hint"))) - .collect(), - }), - } - } -} - -#[cfg(test)] -mod tests { - use starknet_api::hash::StarkFelt; - use starknet_api::stark_felt; - - use super::*; - - #[test] - fn serialize_deserialize_legacy_entry_points() { - let non_serde = vec![ - EntryPoint { - offset: EntryPointOffset(0x25f), - selector: EntryPointSelector(stark_felt!( - "0x289da278a8dc833409cabfdad1581e8e7d40e42dcaed693fa4008dcdb4963b3" - )), - }, - EntryPoint { - offset: EntryPointOffset(0x1b2), - selector: EntryPointSelector(stark_felt!( - "0x29e211664c0b63c79638fbea474206ca74016b3e9a3dc4f9ac300ffd8bdf2cd" - )), - }, - EntryPoint { - offset: EntryPointOffset(0x285), - selector: EntryPointSelector(stark_felt!( - "0x36fcbf06cd96843058359e1a75928beacfac10727dab22a3972f0af8aa92895" - )), - }, - ]; - - // convert to serde and back - let serde: Vec = - non_serde.iter().map(|e| e.clone().into()).collect(); - - // convert to json - let json = serde_json::to_vec(&serde).unwrap(); - let serde: Vec = serde_json::from_slice(&json).unwrap(); - - let same_non_serde: Vec = serde.iter().map(|e| e.clone().into()).collect(); - - assert_eq!(non_serde, same_non_serde); +impl Decompress for ContractNonceChange { + fn decompress>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let contract_address = ContractAddress::decode(&bytes[0..32])?; + let nonce = Nonce::decompress(&bytes[32..])?; + Ok(Self { contract_address, nonce }) } } diff --git a/crates/katana/storage/db/src/models/mod.rs b/crates/katana/storage/db/src/models/mod.rs index 656ed6c7a2..66150ed28b 100644 --- a/crates/katana/storage/db/src/models/mod.rs +++ b/crates/katana/storage/db/src/models/mod.rs @@ -1,3 +1,4 @@ pub mod block; +pub mod class; pub mod contract; pub mod storage; diff --git a/crates/katana/storage/db/src/models/storage.rs b/crates/katana/storage/db/src/models/storage.rs index c4350421b9..6b1c3da54d 100644 --- a/crates/katana/storage/db/src/models/storage.rs +++ b/crates/katana/storage/db/src/models/storage.rs @@ -1,6 +1,7 @@ -use katana_primitives::contract::{StorageKey, StorageValue}; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{ContractAddress, StorageKey, StorageValue}; -use crate::codecs::{Compress, Decompress}; +use crate::codecs::{Compress, Decode, Decompress, Encode}; use crate::error::CodecError; /// Represents a contract storage entry. @@ -33,3 +34,101 @@ impl Decompress for StorageEntry { Ok(Self { key, value }) } } + +#[derive(Debug)] +pub struct StorageEntryChangeList { + pub key: StorageKey, + pub block_list: Vec, +} + +impl Compress for StorageEntryChangeList { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut buf = Vec::new(); + buf.extend_from_slice(&self.key.encode()); + buf.extend_from_slice(&self.block_list.compress()); + buf + } +} + +impl Decompress for StorageEntryChangeList { + fn decompress>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let key = StorageKey::decode(&bytes[0..32])?; + let blocks = Vec::::decompress(&bytes[32..])?; + Ok(Self { key, block_list: blocks }) + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ContractStorageKey { + pub contract_address: ContractAddress, + pub key: StorageKey, +} + +impl Encode for ContractStorageKey { + type Encoded = [u8; 64]; + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 64]; + buf[0..32].copy_from_slice(&self.contract_address.encode()); + buf[32..64].copy_from_slice(&self.key.encode()); + buf + } +} + +impl Decode for ContractStorageKey { + fn decode>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let contract_address = ContractAddress::decode(&bytes[0..32])?; + let key = StorageKey::decode(&bytes[32..])?; + Ok(Self { contract_address, key }) + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ContractStorageEntry { + pub key: ContractStorageKey, + pub value: StorageValue, +} + +impl Compress for ContractStorageEntry { + type Compressed = Vec; + fn compress(self) -> Self::Compressed { + let mut buf = Vec::with_capacity(64); + buf.extend_from_slice(self.key.encode().as_ref()); + buf.extend_from_slice(self.value.compress().as_ref()); + buf + } +} + +impl Decompress for ContractStorageEntry { + fn decompress>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + let key = ContractStorageKey::decode(&bytes[0..64])?; + let value = StorageValue::decompress(&bytes[64..])?; + Ok(Self { key, value }) + } +} + +#[cfg(test)] +mod tests { + use starknet::macros::felt; + + use crate::codecs::{Compress, Decompress}; + + #[test] + fn compress_and_decompress_account_entry() { + let account_storage_entry = super::ContractStorageEntry { + key: super::ContractStorageKey { + contract_address: felt!("0x1234").into(), + key: felt!("0x111"), + }, + value: felt!("0x99"), + }; + + let compressed = account_storage_entry.clone().compress(); + let actual_value = super::ContractStorageEntry::decompress(compressed).unwrap(); + + assert_eq!(account_storage_entry, actual_value); + } +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index 6ec43b55c8..df6facb002 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -7,8 +7,11 @@ use katana_primitives::transaction::{Tx, TxHash, TxNumber}; use crate::codecs::{Compress, Decode, Decompress, Encode}; use crate::models::block::StoredBlockBodyIndices; -use crate::models::contract::StoredContractClass; -use crate::models::storage::StorageEntry; +use crate::models::class::StoredContractClass; +use crate::models::contract::{ContractClassChange, ContractInfoChangeList, ContractNonceChange}; +use crate::models::storage::{ + ContractStorageEntry, ContractStorageKey, StorageEntry, StorageEntryChangeList, +}; pub trait Key: Encode + Decode + Clone + std::fmt::Debug {} pub trait Value: Compress + Decompress + std::fmt::Debug {} @@ -43,7 +46,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 17; +pub const NUM_TABLES: usize = 22; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -158,9 +161,14 @@ define_tables_enum! {[ (CompiledContractClasses, TableType::Table), (SierraClasses, TableType::Table), (ContractInfo, TableType::Table), - (ContractDeployments, TableType::DupSort), + (ContractStorage, TableType::DupSort), + (ClassDeclarationBlock, TableType::Table), (ClassDeclarations, TableType::DupSort), - (ContractStorage, TableType::DupSort) + (ContractInfoChangeSet, TableType::Table), + (NonceChanges, TableType::DupSort), + (ContractClassChanges, TableType::DupSort), + (StorageChanges, TableType::DupSort), + (StorageChangeSet, TableType::DupSort) ]} tables! { @@ -195,8 +203,58 @@ tables! { ContractInfo: (ContractAddress) => GenericContractInfo, /// Store contract storage ContractStorage: (ContractAddress, StorageKey) => StorageEntry, + + + /// Stores the block number where the class hash was declared. + ClassDeclarationBlock: (ClassHash) => BlockNumber, /// Stores the list of class hashes according to the block number it was declared in. ClassDeclarations: (BlockNumber, ClassHash) => ClassHash, - /// Store the list of contracts deployed in a block according to its block number. - ContractDeployments: (BlockNumber, ContractAddress) => ContractAddress + + /// Generic contract info change set. + /// + /// Stores the list of blocks where the contract info (nonce / class hash) has changed. + ContractInfoChangeSet: (ContractAddress) => ContractInfoChangeList, + + /// Contract nonce changes by block. + NonceChanges: (BlockNumber, ContractAddress) => ContractNonceChange, + /// Contract class hash changes by block. + ContractClassChanges: (BlockNumber, ContractAddress) => ContractClassChange, + + /// storage change set + StorageChangeSet: (ContractAddress, StorageKey) => StorageEntryChangeList, + /// Account storage change set + StorageChanges: (BlockNumber, ContractStorageKey) => ContractStorageEntry + +} + +#[cfg(test)] +mod tests { + #[test] + fn test_tables() { + use super::*; + + assert_eq!(Tables::ALL.len(), NUM_TABLES); + assert_eq!(Tables::ALL[0].name(), Headers::NAME); + assert_eq!(Tables::ALL[1].name(), BlockHashes::NAME); + assert_eq!(Tables::ALL[2].name(), BlockNumbers::NAME); + assert_eq!(Tables::ALL[3].name(), BlockBodyIndices::NAME); + assert_eq!(Tables::ALL[4].name(), BlockStatusses::NAME); + assert_eq!(Tables::ALL[5].name(), TxNumbers::NAME); + assert_eq!(Tables::ALL[6].name(), TxBlocks::NAME); + assert_eq!(Tables::ALL[7].name(), TxHashes::NAME); + assert_eq!(Tables::ALL[8].name(), Transactions::NAME); + assert_eq!(Tables::ALL[9].name(), Receipts::NAME); + assert_eq!(Tables::ALL[10].name(), CompiledClassHashes::NAME); + assert_eq!(Tables::ALL[11].name(), CompiledContractClasses::NAME); + assert_eq!(Tables::ALL[12].name(), SierraClasses::NAME); + assert_eq!(Tables::ALL[13].name(), ContractInfo::NAME); + assert_eq!(Tables::ALL[14].name(), ContractStorage::NAME); + assert_eq!(Tables::ALL[15].name(), ClassDeclarationBlock::NAME); + assert_eq!(Tables::ALL[16].name(), ClassDeclarations::NAME); + assert_eq!(Tables::ALL[17].name(), ContractInfoChangeSet::NAME); + assert_eq!(Tables::ALL[18].name(), NonceChanges::NAME); + assert_eq!(Tables::ALL[19].name(), ContractClassChanges::NAME); + assert_eq!(Tables::ALL[20].name(), StorageChanges::NAME); + assert_eq!(Tables::ALL[21].name(), StorageChangeSet::NAME); + } } diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index af9d492a08..5b4e4145da 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -33,4 +33,5 @@ rand = "0.8.5" rstest = "0.18.2" rstest_reuse = "0.6.0" starknet.workspace = true +tempfile = "3.8.1" url.workspace = true diff --git a/crates/katana/storage/provider/src/providers/db/mod.rs b/crates/katana/storage/provider/src/providers/db/mod.rs new file mode 100644 index 0000000000..14568e8bd2 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/db/mod.rs @@ -0,0 +1,864 @@ +pub mod state; + +use std::collections::HashMap; +use std::fmt::Debug; +use std::ops::{Range, RangeInclusive}; + +use anyhow::Result; +use katana_db::error::DatabaseError; +use katana_db::mdbx::{self, DbEnv}; +use katana_db::models::block::StoredBlockBodyIndices; +use katana_db::models::contract::{ + ContractClassChange, ContractInfoChangeList, ContractNonceChange, +}; +use katana_db::models::storage::{ + ContractStorageEntry, ContractStorageKey, StorageEntry, StorageEntryChangeList, +}; +use katana_db::tables::{ + BlockBodyIndices, BlockHashes, BlockNumbers, BlockStatusses, ClassDeclarationBlock, + ClassDeclarations, CompiledClassHashes, CompiledContractClasses, ContractClassChanges, + ContractInfo, ContractInfoChangeSet, ContractStorage, DupSort, Headers, NonceChanges, Receipts, + SierraClasses, StorageChangeSet, StorageChanges, Table, Transactions, TxBlocks, TxHashes, + TxNumbers, +}; +use katana_db::utils::KeyValue; +use katana_primitives::block::{ + Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, Header, + SealedBlockWithStatus, +}; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, ContractAddress, GenericContractInfo, Nonce, StorageKey, + StorageValue, +}; +use katana_primitives::receipt::Receipt; +use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_primitives::transaction::{TxHash, TxNumber, TxWithHash}; +use katana_primitives::FieldElement; + +use crate::traits::block::{ + BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, + HeaderProvider, +}; +use crate::traits::state::{StateFactoryProvider, StateProvider, StateRootProvider}; +use crate::traits::state_update::StateUpdateProvider; +use crate::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionsProviderExt, +}; + +/// A provider implementation that uses a database as a backend. +#[derive(Debug)] +pub struct DbProvider(DbEnv); + +impl DbProvider { + /// Creates a new [`DbProvider`] from the given [`DbEnv`]. + pub fn new(db: DbEnv) -> Self { + Self(db) + } +} + +impl StateFactoryProvider for DbProvider { + fn latest(&self) -> Result> { + Ok(Box::new(self::state::LatestStateProvider::new(self.0.tx()?))) + } + + fn historical(&self, block_id: BlockHashOrNumber) -> Result>> { + let block_number = match block_id { + BlockHashOrNumber::Num(num) => { + let latest_num = self.latest_number()?; + + match num.cmp(&latest_num) { + std::cmp::Ordering::Less => Some(num), + std::cmp::Ordering::Greater => return Ok(None), + std::cmp::Ordering::Equal => return self.latest().map(Some), + } + } + + BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash)?, + }; + + let Some(num) = block_number else { return Ok(None) }; + + Ok(Some(Box::new(self::state::HistoricalStateProvider::new(self.0.tx()?, num)))) + } +} + +impl BlockNumberProvider for DbProvider { + fn block_number_by_hash(&self, hash: BlockHash) -> Result> { + let db_tx = self.0.tx()?; + let block_num = db_tx.get::(hash)?; + db_tx.commit()?; + Ok(block_num) + } + + fn latest_number(&self) -> Result { + let db_tx = self.0.tx()?; + let total_blocks = db_tx.entries::()? as u64; + db_tx.commit()?; + Ok(if total_blocks == 0 { 0 } else { total_blocks - 1 }) + } +} + +impl BlockHashProvider for DbProvider { + fn latest_hash(&self) -> Result { + let db_tx = self.0.tx()?; + let total_blocks = db_tx.entries::()? as u64; + let latest_block = if total_blocks == 0 { 0 } else { total_blocks - 1 }; + let latest_hash = db_tx.get::(latest_block)?.expect("block hash should exist"); + db_tx.commit()?; + Ok(latest_hash) + } + + fn block_hash_by_num(&self, num: BlockNumber) -> Result> { + let db_tx = self.0.tx()?; + let block_hash = db_tx.get::(num)?; + db_tx.commit()?; + Ok(block_hash) + } +} + +impl HeaderProvider for DbProvider { + fn header(&self, id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + let num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => db_tx.get::(hash)?, + }; + + if let Some(num) = num { + let header = db_tx.get::(num)?.expect("should exist"); + db_tx.commit()?; + Ok(Some(header)) + } else { + Ok(None) + } + } +} + +impl BlockProvider for DbProvider { + fn block_body_indices(&self, id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => db_tx.get::(hash)?, + }; + + if let Some(num) = block_num { + let indices = db_tx.get::(num)?; + db_tx.commit()?; + Ok(indices) + } else { + Ok(None) + } + } + + fn block(&self, id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + if let Some(header) = self.header(id)? { + let body = self.transactions_by_block(id)?.expect("should exist"); + db_tx.commit()?; + Ok(Some(Block { header, body })) + } else { + Ok(None) + } + } + + fn block_with_tx_hashes(&self, id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => db_tx.get::(hash)?, + }; + + let Some(block_num) = block_num else { return Ok(None) }; + + if let Some(header) = db_tx.get::(block_num)? { + let body_indices = db_tx.get::(block_num)?.expect("should exist"); + let body = self.transaction_hashes_in_range(Range::from(body_indices))?; + let block = BlockWithTxHashes { header, body }; + + db_tx.commit()?; + + Ok(Some(block)) + } else { + Ok(None) + } + } + + fn blocks_in_range(&self, range: RangeInclusive) -> Result> { + let db_tx = self.0.tx()?; + + let total = range.end() - range.start() + 1; + let mut blocks = Vec::with_capacity(total as usize); + + for num in range { + if let Some(header) = db_tx.get::(num)? { + let body_indices = db_tx.get::(num)?.expect("should exist"); + let body = self.transaction_in_range(Range::from(body_indices))?; + blocks.push(Block { header, body }) + } + } + + db_tx.commit()?; + Ok(blocks) + } +} + +impl BlockStatusProvider for DbProvider { + fn block_status(&self, id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + let block_num = match id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => self.block_number_by_hash(hash)?, + }; + + if let Some(block_num) = block_num { + let status = db_tx.get::(block_num)?.expect("should exist"); + db_tx.commit()?; + Ok(Some(status)) + } else { + Ok(None) + } + } +} + +impl StateRootProvider for DbProvider { + fn state_root(&self, block_id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + + let block_num = match block_id { + BlockHashOrNumber::Num(num) => Some(num), + BlockHashOrNumber::Hash(hash) => db_tx.get::(hash)?, + }; + + if let Some(block_num) = block_num { + let header = db_tx.get::(block_num)?; + db_tx.commit()?; + Ok(header.map(|h| h.state_root)) + } else { + Ok(None) + } + } +} + +impl StateUpdateProvider for DbProvider { + fn state_update(&self, block_id: BlockHashOrNumber) -> Result> { + // A helper function that iterates over all entries in a dupsort table and collects the + // results into `V`. If `key` is not found, `V::default()` is returned. + fn dup_entries( + db_tx: &mdbx::tx::TxRO, + key: ::Key, + f: impl FnMut(Result, DatabaseError>) -> Result, + ) -> Result + where + Tb: DupSort + Debug, + V: FromIterator + Default, + { + Ok(db_tx + .cursor::()? + .walk_dup(Some(key), None)? + .map(|walker| walker.map(f).collect::>()) + .transpose()? + .unwrap_or_default()) + } + + let db_tx = self.0.tx()?; + let block_num = self.block_number_by_id(block_id)?; + + if let Some(block_num) = block_num { + let nonce_updates = dup_entries::, _>( + &db_tx, + block_num, + |entry| { + let (_, ContractNonceChange { contract_address, nonce }) = entry?; + Ok((contract_address, nonce)) + }, + )?; + + let contract_updates = dup_entries::< + ContractClassChanges, + HashMap, + _, + >(&db_tx, block_num, |entry| { + let (_, ContractClassChange { contract_address, class_hash }) = entry?; + Ok((contract_address, class_hash)) + })?; + + let declared_classes = dup_entries::< + ClassDeclarations, + HashMap, + _, + >(&db_tx, block_num, |entry| { + let (_, class_hash) = entry?; + let compiled_hash = + db_tx.get::(class_hash)?.expect("qed; must exist"); + Ok((class_hash, compiled_hash)) + })?; + + let storage_updates = { + let entries = dup_entries::< + StorageChanges, + Vec<(ContractAddress, (StorageKey, StorageValue))>, + _, + >(&db_tx, block_num, |entry| { + let (_, ContractStorageEntry { key, value }) = entry?; + Ok::<_, DatabaseError>((key.contract_address, (key.key, value))) + })?; + + let mut map: HashMap<_, HashMap> = HashMap::new(); + + entries.into_iter().for_each(|(addr, (key, value))| { + map.entry(addr).or_default().insert(key, value); + }); + + map + }; + + db_tx.commit()?; + Ok(Some(StateUpdates { + nonce_updates, + storage_updates, + contract_updates, + declared_classes, + })) + } else { + Ok(None) + } + } +} + +impl TransactionProvider for DbProvider { + fn transaction_by_hash(&self, hash: TxHash) -> Result> { + let db_tx = self.0.tx()?; + + if let Some(num) = db_tx.get::(hash)? { + let transaction = db_tx.get::(num)?.expect("transaction should exist"); + let transaction = TxWithHash { hash, transaction }; + db_tx.commit()?; + + Ok(Some(transaction)) + } else { + Ok(None) + } + } + + fn transactions_by_block( + &self, + block_id: BlockHashOrNumber, + ) -> Result>> { + if let Some(indices) = self.block_body_indices(block_id)? { + Ok(Some(self.transaction_in_range(Range::from(indices))?)) + } else { + Ok(None) + } + } + + fn transaction_in_range(&self, range: Range) -> Result> { + let db_tx = self.0.tx()?; + + let total = range.end - range.start; + let mut transactions = Vec::with_capacity(total as usize); + + for i in range { + if let Some(transaction) = db_tx.get::(i)? { + let hash = db_tx.get::(i)?.expect("should exist"); + transactions.push(TxWithHash { hash, transaction }); + }; + } + + db_tx.commit()?; + Ok(transactions) + } + + fn transaction_block_num_and_hash( + &self, + hash: TxHash, + ) -> Result> { + let db_tx = self.0.tx()?; + if let Some(num) = db_tx.get::(hash)? { + let block_num = db_tx.get::(num)?.expect("should exist"); + let block_hash = db_tx.get::(block_num)?.expect("should exist"); + db_tx.commit()?; + Ok(Some((block_num, block_hash))) + } else { + Ok(None) + } + } + + fn transaction_by_block_and_idx( + &self, + block_id: BlockHashOrNumber, + idx: u64, + ) -> Result> { + let db_tx = self.0.tx()?; + + match self.block_body_indices(block_id)? { + // make sure the requested idx is within the range of the block tx count + Some(indices) if idx < indices.tx_count => { + let num = indices.tx_offset + idx; + let hash = db_tx.get::(num)?.expect("should exist"); + let transaction = db_tx.get::(num)?.expect("should exist"); + let transaction = TxWithHash { hash, transaction }; + db_tx.commit()?; + Ok(Some(transaction)) + } + + _ => Ok(None), + } + } + + fn transaction_count_by_block(&self, block_id: BlockHashOrNumber) -> Result> { + let db_tx = self.0.tx()?; + if let Some(indices) = self.block_body_indices(block_id)? { + db_tx.commit()?; + Ok(Some(indices.tx_count)) + } else { + Ok(None) + } + } +} + +impl TransactionsProviderExt for DbProvider { + fn transaction_hashes_in_range(&self, range: Range) -> Result> { + let db_tx = self.0.tx()?; + + let total = range.end - range.start; + let mut hashes = Vec::with_capacity(total as usize); + + for i in range { + if let Some(hash) = db_tx.get::(i)? { + hashes.push(hash); + } + } + + db_tx.commit()?; + Ok(hashes) + } +} + +impl TransactionStatusProvider for DbProvider { + fn transaction_status(&self, hash: TxHash) -> Result> { + let db_tx = self.0.tx()?; + if let Some(tx_num) = db_tx.get::(hash)? { + let block_num = db_tx.get::(tx_num)?.expect("should exist"); + let status = db_tx.get::(block_num)?.expect("should exist"); + db_tx.commit()?; + Ok(Some(status)) + } else { + Ok(None) + } + } +} + +impl ReceiptProvider for DbProvider { + fn receipt_by_hash(&self, hash: TxHash) -> Result> { + let db_tx = self.0.tx()?; + if let Some(num) = db_tx.get::(hash)? { + let receipt = db_tx.get::(num)?.expect("should exist"); + db_tx.commit()?; + Ok(Some(receipt)) + } else { + Ok(None) + } + } + + fn receipts_by_block(&self, block_id: BlockHashOrNumber) -> Result>> { + if let Some(indices) = self.block_body_indices(block_id)? { + let db_tx = self.0.tx()?; + let mut receipts = Vec::with_capacity(indices.tx_count as usize); + + let range = indices.tx_offset..indices.tx_offset + indices.tx_count; + for i in range { + if let Some(receipt) = db_tx.get::(i)? { + receipts.push(receipt); + } + } + + db_tx.commit()?; + Ok(Some(receipts)) + } else { + Ok(None) + } + } +} + +impl BlockWriter for DbProvider { + fn insert_block_with_states_and_receipts( + &self, + block: SealedBlockWithStatus, + states: StateUpdatesWithDeclaredClasses, + receipts: Vec, + ) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + let block_hash = block.block.header.hash; + let block_number = block.block.header.header.number; + + let block_header = block.block.header.header; + let transactions = block.block.body; + + let tx_count = transactions.len() as u64; + let tx_offset = db_tx.entries::()? as u64; + let block_body_indices = StoredBlockBodyIndices { tx_offset, tx_count }; + + db_tx.put::(block_number, block_hash)?; + db_tx.put::(block_hash, block_number)?; + db_tx.put::(block_number, block.status)?; + + db_tx.put::(block_number, block_header)?; + db_tx.put::(block_number, block_body_indices)?; + + for (i, (transaction, receipt)) in transactions.into_iter().zip(receipts).enumerate() { + let tx_number = tx_offset + i as u64; + let tx_hash = transaction.hash; + + db_tx.put::(tx_number, tx_hash)?; + db_tx.put::(tx_hash, tx_number)?; + db_tx.put::(tx_number, block_number)?; + db_tx.put::(tx_number, transaction.transaction)?; + db_tx.put::(tx_number, receipt)?; + } + + // insert classes + + for (class_hash, compiled_hash) in states.state_updates.declared_classes { + db_tx.put::(class_hash, compiled_hash)?; + + db_tx.put::(class_hash, block_number)?; + db_tx.put::(block_number, class_hash)? + } + + for (hash, compiled_class) in states.declared_compiled_classes { + db_tx.put::(hash, compiled_class.into())?; + } + + for (class_hash, sierra_class) in states.declared_sierra_classes { + db_tx.put::(class_hash, sierra_class)?; + } + + // insert storage changes + { + let mut storage_cursor = db_tx.cursor::()?; + for (addr, entries) in states.state_updates.storage_updates { + let entries = + entries.into_iter().map(|(key, value)| StorageEntry { key, value }); + + for entry in entries { + match storage_cursor.seek_by_key_subkey(addr, entry.key)? { + Some(current) if current.key == entry.key => { + storage_cursor.delete_current()?; + } + + _ => {} + } + + let mut change_set_cursor = db_tx.cursor::()?; + let new_block_list = + match change_set_cursor.seek_by_key_subkey(addr, entry.key)? { + Some(StorageEntryChangeList { mut block_list, key }) + if key == entry.key => + { + change_set_cursor.delete_current()?; + + block_list.push(block_number); + block_list.sort(); + block_list + } + + _ => { + vec![block_number] + } + }; + + change_set_cursor.upsert( + addr, + StorageEntryChangeList { key: entry.key, block_list: new_block_list }, + )?; + storage_cursor.upsert(addr, entry)?; + + let storage_change_sharded_key = + ContractStorageKey { contract_address: addr, key: entry.key }; + + db_tx.put::( + block_number, + ContractStorageEntry { + key: storage_change_sharded_key, + value: entry.value, + }, + )?; + } + } + } + + // update contract info + + for (addr, class_hash) in states.state_updates.contract_updates { + let value = if let Some(info) = db_tx.get::(addr)? { + GenericContractInfo { class_hash, ..info } + } else { + GenericContractInfo { class_hash, ..Default::default() } + }; + + let new_change_set = + if let Some(mut change_set) = db_tx.get::(addr)? { + change_set.class_change_list.push(block_number); + change_set.class_change_list.sort(); + change_set + } else { + ContractInfoChangeList { + class_change_list: vec![block_number], + ..Default::default() + } + }; + + db_tx.put::(addr, value)?; + + let class_change_key = ContractClassChange { contract_address: addr, class_hash }; + db_tx.put::(block_number, class_change_key)?; + db_tx.put::(addr, new_change_set)?; + } + + for (addr, nonce) in states.state_updates.nonce_updates { + let value = if let Some(info) = db_tx.get::(addr)? { + GenericContractInfo { nonce, ..info } + } else { + GenericContractInfo { nonce, ..Default::default() } + }; + + let new_change_set = + if let Some(mut change_set) = db_tx.get::(addr)? { + change_set.nonce_change_list.push(block_number); + change_set.nonce_change_list.sort(); + change_set + } else { + ContractInfoChangeList { + nonce_change_list: vec![block_number], + ..Default::default() + } + }; + + db_tx.put::(addr, value)?; + + let nonce_change_key = ContractNonceChange { contract_address: addr, nonce }; + db_tx.put::(block_number, nonce_change_key)?; + db_tx.put::(addr, new_change_set)?; + } + + Ok(()) + })? + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use katana_db::mdbx::DbEnvKind; + use katana_primitives::block::{ + Block, BlockHashOrNumber, FinalityStatus, Header, SealedBlockWithStatus, + }; + use katana_primitives::contract::ContractAddress; + use katana_primitives::receipt::Receipt; + use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; + use katana_primitives::transaction::{Tx, TxHash, TxWithHash}; + use starknet::macros::felt; + + use super::DbProvider; + use crate::traits::block::{ + BlockHashProvider, BlockNumberProvider, BlockProvider, BlockStatusProvider, BlockWriter, + }; + use crate::traits::state::StateFactoryProvider; + use crate::traits::transaction::TransactionProvider; + + fn create_dummy_block() -> SealedBlockWithStatus { + let header = Header { parent_hash: 199u8.into(), number: 0, ..Default::default() }; + let block = Block { + header, + body: vec![TxWithHash { + hash: 24u8.into(), + transaction: Tx::Invoke(Default::default()), + }], + } + .seal(); + SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 } + } + + fn create_dummy_state_updates() -> StateUpdatesWithDeclaredClasses { + StateUpdatesWithDeclaredClasses { + state_updates: StateUpdates { + nonce_updates: HashMap::from([ + (ContractAddress::from(felt!("1")), felt!("1")), + (ContractAddress::from(felt!("2")), felt!("2")), + ]), + contract_updates: HashMap::from([ + (ContractAddress::from(felt!("1")), felt!("3")), + (ContractAddress::from(felt!("2")), felt!("4")), + ]), + declared_classes: HashMap::from([ + (felt!("3"), felt!("89")), + (felt!("4"), felt!("90")), + ]), + storage_updates: HashMap::from([( + ContractAddress::from(felt!("1")), + HashMap::from([(felt!("1"), felt!("1")), (felt!("2"), felt!("2"))]), + )]), + }, + ..Default::default() + } + } + + fn create_dummy_state_updates_2() -> StateUpdatesWithDeclaredClasses { + StateUpdatesWithDeclaredClasses { + state_updates: StateUpdates { + nonce_updates: HashMap::from([ + (ContractAddress::from(felt!("1")), felt!("5")), + (ContractAddress::from(felt!("2")), felt!("6")), + ]), + contract_updates: HashMap::from([ + (ContractAddress::from(felt!("1")), felt!("77")), + (ContractAddress::from(felt!("2")), felt!("66")), + ]), + storage_updates: HashMap::from([( + ContractAddress::from(felt!("1")), + HashMap::from([(felt!("1"), felt!("100")), (felt!("2"), felt!("200"))]), + )]), + ..Default::default() + }, + ..Default::default() + } + } + + fn create_db_provider() -> DbProvider { + DbProvider(katana_db::mdbx::test_utils::create_test_db(DbEnvKind::RW)) + } + + #[test] + fn insert_block() { + let provider = create_db_provider(); + + let block = create_dummy_block(); + let state_updates = create_dummy_state_updates(); + + // insert block + BlockWriter::insert_block_with_states_and_receipts( + &provider, + block.clone(), + state_updates, + vec![Receipt::Invoke(Default::default())], + ) + .expect("failed to insert block"); + + // get values + + let block_id: BlockHashOrNumber = block.block.header.hash.into(); + + let latest_number = provider.latest_number().unwrap(); + let latest_hash = provider.latest_hash().unwrap(); + + let actual_block = provider.block(block_id).unwrap().unwrap(); + let tx_count = provider.transaction_count_by_block(block_id).unwrap().unwrap(); + let block_status = provider.block_status(block_id).unwrap().unwrap(); + let body_indices = provider.block_body_indices(block_id).unwrap().unwrap(); + + let tx_hash: TxHash = 24u8.into(); + let tx = provider.transaction_by_hash(tx_hash).unwrap().unwrap(); + + let state_prov = StateFactoryProvider::latest(&provider).unwrap(); + + let nonce1 = state_prov.nonce(ContractAddress::from(felt!("1"))).unwrap().unwrap(); + let nonce2 = state_prov.nonce(ContractAddress::from(felt!("2"))).unwrap().unwrap(); + + let class_hash1 = state_prov.class_hash_of_contract(felt!("1").into()).unwrap().unwrap(); + let class_hash2 = state_prov.class_hash_of_contract(felt!("2").into()).unwrap().unwrap(); + + let compiled_hash1 = + state_prov.compiled_class_hash_of_class_hash(class_hash1).unwrap().unwrap(); + let compiled_hash2 = + state_prov.compiled_class_hash_of_class_hash(class_hash2).unwrap().unwrap(); + + let storage1 = + state_prov.storage(ContractAddress::from(felt!("1")), felt!("1")).unwrap().unwrap(); + let storage2 = + state_prov.storage(ContractAddress::from(felt!("1")), felt!("2")).unwrap().unwrap(); + + // assert values are populated correctly + + assert_eq!(tx_hash, tx.hash); + assert_eq!(tx.transaction, Tx::Invoke(Default::default())); + + assert_eq!(tx_count, 1); + assert_eq!(body_indices.tx_offset, 0); + assert_eq!(body_indices.tx_count, tx_count); + + assert_eq!(block_status, FinalityStatus::AcceptedOnL2); + assert_eq!(block.block.header.hash, latest_hash); + assert_eq!(block.block.body.len() as u64, tx_count); + assert_eq!(block.block.header.header.number, latest_number); + assert_eq!(block.block.unseal(), actual_block); + + assert_eq!(nonce1, felt!("1")); + assert_eq!(nonce2, felt!("2")); + assert_eq!(class_hash1, felt!("3")); + assert_eq!(class_hash2, felt!("4")); + + assert_eq!(compiled_hash1, felt!("89")); + assert_eq!(compiled_hash2, felt!("90")); + + assert_eq!(storage1, felt!("1")); + assert_eq!(storage2, felt!("2")); + } + + #[test] + fn storage_updated_correctly() { + let provider = create_db_provider(); + + let block = create_dummy_block(); + let state_updates1 = create_dummy_state_updates(); + let state_updates2 = create_dummy_state_updates_2(); + + // insert block + BlockWriter::insert_block_with_states_and_receipts( + &provider, + block.clone(), + state_updates1, + vec![Receipt::Invoke(Default::default())], + ) + .expect("failed to insert block"); + + // insert another block + BlockWriter::insert_block_with_states_and_receipts( + &provider, + block, + state_updates2, + vec![Receipt::Invoke(Default::default())], + ) + .expect("failed to insert block"); + + // assert storage is updated correctly + + let state_prov = StateFactoryProvider::latest(&provider).unwrap(); + + let nonce1 = state_prov.nonce(ContractAddress::from(felt!("1"))).unwrap().unwrap(); + let nonce2 = state_prov.nonce(ContractAddress::from(felt!("2"))).unwrap().unwrap(); + + let class_hash1 = state_prov.class_hash_of_contract(felt!("1").into()).unwrap().unwrap(); + let class_hash2 = state_prov.class_hash_of_contract(felt!("2").into()).unwrap().unwrap(); + + let storage1 = + state_prov.storage(ContractAddress::from(felt!("1")), felt!("1")).unwrap().unwrap(); + let storage2 = + state_prov.storage(ContractAddress::from(felt!("1")), felt!("2")).unwrap().unwrap(); + + assert_eq!(nonce1, felt!("5")); + assert_eq!(nonce2, felt!("6")); + + assert_eq!(class_hash1, felt!("77")); + assert_eq!(class_hash2, felt!("66")); + + assert_eq!(storage1, felt!("100")); + assert_eq!(storage2, felt!("200")); + } +} diff --git a/crates/katana/storage/provider/src/providers/db/state.rs b/crates/katana/storage/provider/src/providers/db/state.rs new file mode 100644 index 0000000000..0230ade4e5 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/db/state.rs @@ -0,0 +1,337 @@ +use std::cmp::Ordering; + +use anyhow::Result; +use katana_db::mdbx::{self}; +use katana_db::models::contract::{ + ContractClassChange, ContractInfoChangeList, ContractNonceChange, +}; +use katana_db::models::storage::{ContractStorageEntry, ContractStorageKey, StorageEntry}; +use katana_db::tables::{ + ClassDeclarationBlock, CompiledClassHashes, CompiledContractClasses, ContractClassChanges, + ContractInfo, ContractInfoChangeSet, ContractStorage, NonceChanges, SierraClasses, + StorageChangeSet, StorageChanges, +}; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{ + ClassHash, CompiledClassHash, CompiledContractClass, ContractAddress, GenericContractInfo, + Nonce, SierraClass, StorageKey, StorageValue, +}; + +use super::DbProvider; +use crate::traits::contract::{ContractClassProvider, ContractClassWriter}; +use crate::traits::state::{StateProvider, StateWriter}; + +impl StateWriter for DbProvider { + fn set_nonce(&self, address: ContractAddress, nonce: Nonce) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + let value = if let Some(info) = db_tx.get::(address)? { + GenericContractInfo { nonce, ..info } + } else { + GenericContractInfo { nonce, ..Default::default() } + }; + db_tx.put::(address, value)?; + Ok(()) + })? + } + + fn set_storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + storage_value: StorageValue, + ) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + let mut cursor = db_tx.cursor::()?; + let entry = cursor.seek_by_key_subkey(address, storage_key)?; + + match entry { + Some(entry) if entry.key == storage_key => { + cursor.delete_current()?; + } + _ => {} + } + + cursor.upsert(address, StorageEntry { key: storage_key, value: storage_value })?; + Ok(()) + })? + } + + fn set_class_hash_of_contract( + &self, + address: ContractAddress, + class_hash: ClassHash, + ) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + let value = if let Some(info) = db_tx.get::(address)? { + GenericContractInfo { class_hash, ..info } + } else { + GenericContractInfo { class_hash, ..Default::default() } + }; + db_tx.put::(address, value)?; + Ok(()) + })? + } +} + +impl ContractClassWriter for DbProvider { + fn set_class(&self, hash: ClassHash, class: CompiledContractClass) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + db_tx.put::(hash, class.into())?; + Ok(()) + })? + } + + fn set_compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + compiled_hash: CompiledClassHash, + ) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + db_tx.put::(hash, compiled_hash)?; + Ok(()) + })? + } + + fn set_sierra_class(&self, hash: ClassHash, sierra: SierraClass) -> Result<()> { + self.0.update(move |db_tx| -> Result<()> { + db_tx.put::(hash, sierra)?; + Ok(()) + })? + } +} + +/// A state provider that provides the latest states from the database. +pub(super) struct LatestStateProvider(mdbx::tx::TxRO); + +impl LatestStateProvider { + pub fn new(tx: mdbx::tx::TxRO) -> Self { + Self(tx) + } +} + +impl ContractClassProvider for LatestStateProvider { + fn class(&self, hash: ClassHash) -> Result> { + let class = self.0.get::(hash)?; + Ok(class.map(CompiledContractClass::from)) + } + + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + let hash = self.0.get::(hash)?; + Ok(hash) + } + + fn sierra_class(&self, hash: ClassHash) -> Result> { + let class = self.0.get::(hash)?; + Ok(class) + } +} + +impl StateProvider for LatestStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + let info = self.0.get::(address)?; + Ok(info.map(|info| info.nonce)) + } + + fn class_hash_of_contract( + &self, + address: ContractAddress, + ) -> Result> { + let info = self.0.get::(address)?; + Ok(info.map(|info| info.class_hash)) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + let mut cursor = self.0.cursor::()?; + let entry = cursor.seek_by_key_subkey(address, storage_key)?; + match entry { + Some(entry) if entry.key == storage_key => Ok(Some(entry.value)), + _ => Ok(None), + } + } +} + +/// A historical state provider. +pub(super) struct HistoricalStateProvider { + /// The database transaction used to read the database. + tx: mdbx::tx::TxRO, + /// The block number of the state. + block_number: u64, +} + +impl HistoricalStateProvider { + pub fn new(tx: mdbx::tx::TxRO, block_number: u64) -> Self { + Self { tx, block_number } + } + + // This looks ugly but it works and I will most likely forget how it works + // if I don't document it. But im lazy. + fn recent_block_change_relative_to_pinned_block_num( + block_number: BlockNumber, + block_list: &[BlockNumber], + ) -> Option { + if block_list.first().is_some_and(|num| block_number < *num) { + return None; + } + + // if the pinned block number is smaller than the first block number in the list, + // then that means there is no change happening before the pinned block number. + let pos = { + if let Some(pos) = block_list.last().and_then(|num| { + if block_number >= *num { Some(block_list.len() - 1) } else { None } + }) { + Some(pos) + } else { + block_list.iter().enumerate().find_map(|(i, num)| match block_number.cmp(num) { + Ordering::Equal => Some(i), + Ordering::Greater => None, + Ordering::Less => { + if i == 0 || block_number == 0 { + None + } else { + Some(i - 1) + } + } + }) + } + }?; + + block_list.get(pos).copied() + } +} + +impl ContractClassProvider for HistoricalStateProvider { + fn compiled_class_hash_of_class_hash( + &self, + hash: ClassHash, + ) -> Result> { + // check that the requested class hash was declared before the pinned block number + if !self.tx.get::(hash)?.is_some_and(|num| num <= self.block_number) + { + return Ok(None); + }; + + Ok(self.tx.get::(hash)?) + } + + fn class(&self, hash: ClassHash) -> Result> { + self.compiled_class_hash_of_class_hash(hash).and_then(|_| { + let contract = self.tx.get::(hash)?; + Ok(contract.map(CompiledContractClass::from)) + }) + } + + fn sierra_class(&self, hash: ClassHash) -> Result> { + self.compiled_class_hash_of_class_hash(hash) + .and_then(|_| self.tx.get::(hash).map_err(|e| e.into())) + } +} + +impl StateProvider for HistoricalStateProvider { + fn nonce(&self, address: ContractAddress) -> Result> { + let change_list = self.tx.get::(address)?; + + if let Some(num) = change_list.and_then(|entry| { + Self::recent_block_change_relative_to_pinned_block_num( + self.block_number, + &entry.nonce_change_list, + ) + }) { + let mut cursor = self.tx.cursor::()?; + let ContractNonceChange { contract_address, nonce } = cursor + .seek_by_key_subkey(num, address)? + .expect("if block number is in the block set, change entry must exist"); + + if contract_address == address { + return Ok(Some(nonce)); + } + } + + Ok(None) + } + + fn class_hash_of_contract(&self, address: ContractAddress) -> Result> { + let change_list: Option = + self.tx.get::(address)?; + + if let Some(num) = change_list.and_then(|entry| { + Self::recent_block_change_relative_to_pinned_block_num( + self.block_number, + &entry.class_change_list, + ) + }) { + let mut cursor = self.tx.cursor::()?; + let ContractClassChange { contract_address, class_hash } = cursor + .seek_by_key_subkey(num, address)? + .expect("if block number is in the block set, change entry must exist"); + + if contract_address == address { + return Ok(Some(class_hash)); + } + } + + Ok(None) + } + + fn storage( + &self, + address: ContractAddress, + storage_key: StorageKey, + ) -> Result> { + let mut cursor = self.tx.cursor::()?; + + if let Some(num) = cursor.seek_by_key_subkey(address, storage_key)?.and_then(|entry| { + Self::recent_block_change_relative_to_pinned_block_num( + self.block_number, + &entry.block_list, + ) + }) { + let mut cursor = self.tx.cursor::()?; + let sharded_key = ContractStorageKey { contract_address: address, key: storage_key }; + + let ContractStorageEntry { key, value } = cursor + .seek_by_key_subkey(num, sharded_key)? + .expect("if block number is in the block set, change entry must exist"); + + if key.contract_address == address && key.key == storage_key { + return Ok(Some(value)); + } + } + + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use super::HistoricalStateProvider; + + const BLOCK_LIST: [u64; 5] = [1, 2, 5, 6, 10]; + + #[rstest::rstest] + #[case(0, None)] + #[case(1, Some(1))] + #[case(3, Some(2))] + #[case(5, Some(5))] + #[case(9, Some(6))] + #[case(10, Some(10))] + #[case(11, Some(10))] + fn position_of_most_recent_block_in_block_list( + #[case] block_num: u64, + #[case] expected_block_num: Option, + ) { + assert_eq!( + HistoricalStateProvider::recent_block_change_relative_to_pinned_block_num( + block_num, + &BLOCK_LIST, + ), + expected_block_num + ); + } +} diff --git a/crates/katana/storage/provider/src/providers/mod.rs b/crates/katana/storage/provider/src/providers/mod.rs index 0c2dc27d17..d565e76a9a 100644 --- a/crates/katana/storage/provider/src/providers/mod.rs +++ b/crates/katana/storage/provider/src/providers/mod.rs @@ -1,3 +1,4 @@ +pub mod db; #[cfg(feature = "fork")] pub mod fork; #[cfg(feature = "in-memory")] diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index 8023d86568..c7dbee6be1 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -29,6 +29,11 @@ pub trait TransactionProvider: Send + Sync { &self, hash: TxHash, ) -> Result>; + + /// Retrieves all the transactions at the given range. + fn transaction_in_range(&self, _range: Range) -> Result> { + todo!() + } } #[auto_impl::auto_impl(&, Box, Arc)] diff --git a/crates/katana/storage/provider/tests/block.rs b/crates/katana/storage/provider/tests/block.rs index 03b8a4cecc..e62efa89e6 100644 --- a/crates/katana/storage/provider/tests/block.rs +++ b/crates/katana/storage/provider/tests/block.rs @@ -1,15 +1,29 @@ -use katana_primitives::block::BlockHashOrNumber; +use anyhow::Result; +use katana_primitives::block::{ + Block, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, +}; +use katana_primitives::state::StateUpdates; +use katana_provider::providers::db::DbProvider; use katana_provider::providers::fork::ForkedProvider; use katana_provider::providers::in_memory::InMemoryProvider; -use katana_provider::traits::block::{BlockProvider, BlockWriter}; -use katana_provider::traits::transaction::{ReceiptProvider, TransactionProvider}; +use katana_provider::traits::block::{ + BlockHashProvider, BlockProvider, BlockStatusProvider, BlockWriter, +}; +use katana_provider::traits::state::StateRootProvider; +use katana_provider::traits::state_update::StateUpdateProvider; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, +}; use katana_provider::BlockchainProvider; use rstest_reuse::{self, *}; mod fixtures; mod utils; -use fixtures::{fork_provider, in_memory_provider}; +use fixtures::{ + db_provider, fork_provider, fork_provider_with_spawned_fork_network, in_memory_provider, + mock_state_updates, provider_with_states, +}; use utils::generate_dummy_blocks_and_receipts; #[template] @@ -24,7 +38,7 @@ fn insert_block_cases(#[case] block_count: u64) {} fn insert_block_with_in_memory_provider( #[from(in_memory_provider)] provider: BlockchainProvider, #[case] block_count: u64, -) -> anyhow::Result<()> { +) -> Result<()> { insert_block_test_impl(provider, block_count) } @@ -32,13 +46,25 @@ fn insert_block_with_in_memory_provider( fn insert_block_with_fork_provider( #[from(fork_provider)] provider: BlockchainProvider, #[case] block_count: u64, -) -> anyhow::Result<()> { +) -> Result<()> { insert_block_test_impl(provider, block_count) } -fn insert_block_test_impl(provider: BlockchainProvider, count: u64) -> anyhow::Result<()> +#[apply(insert_block_cases)] +fn insert_block_with_db_provider( + #[from(db_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> Result<()> { + insert_block_test_impl(provider, block_count) +} + +fn insert_block_test_impl(provider: BlockchainProvider, count: u64) -> Result<()> where - Db: BlockProvider + BlockWriter + ReceiptProvider, + Db: BlockProvider + + BlockWriter + + ReceiptProvider + + StateRootProvider + + TransactionStatusProvider, { let blocks = generate_dummy_blocks_and_receipts(count); @@ -50,23 +76,120 @@ where )?; } + let actual_blocks_in_range = provider.blocks_in_range(0..=count)?; + + assert_eq!(actual_blocks_in_range.len(), count as usize); + assert_eq!( + actual_blocks_in_range, + blocks.clone().into_iter().map(|b| b.0.block.unseal()).collect::>() + ); + for (block, receipts) in blocks { let block_id = BlockHashOrNumber::Hash(block.block.header.hash); + + let expected_block_num = block.block.header.header.number; + let expected_block_hash = block.block.header.hash; let expected_block = block.block.unseal(); + let actual_block_hash = provider.block_hash_by_num(expected_block_num)?; + let actual_block = provider.block(block_id)?; let actual_block_txs = provider.transactions_by_block(block_id)?; - let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; + let actual_status = provider.block_status(block_id)?; + let actual_state_root = provider.state_root(block_id)?; + let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; let actual_receipts = provider.receipts_by_block(block_id)?; + let expected_block_with_tx_hashes = BlockWithTxHashes { + header: expected_block.header.clone(), + body: expected_block.body.clone().into_iter().map(|t| t.hash).collect(), + }; + + let actual_block_with_tx_hashes = provider.block_with_tx_hashes(block_id)?; + + assert_eq!(actual_status, Some(FinalityStatus::AcceptedOnL2)); + assert_eq!(actual_block_with_tx_hashes, Some(expected_block_with_tx_hashes)); + + for (idx, tx) in expected_block.body.iter().enumerate() { + let actual_receipt = provider.receipt_by_hash(tx.hash)?; + let actual_tx = provider.transaction_by_hash(tx.hash)?; + let actual_tx_status = provider.transaction_status(tx.hash)?; + let actual_tx_block_num_hash = provider.transaction_block_num_and_hash(tx.hash)?; + let actual_tx_by_block_idx = + provider.transaction_by_block_and_idx(block_id, idx as u64)?; + + assert_eq!(actual_tx_block_num_hash, Some((expected_block_num, expected_block_hash))); + assert_eq!(actual_tx_status, Some(FinalityStatus::AcceptedOnL2)); + assert_eq!(actual_receipt, Some(receipts[idx].clone())); + assert_eq!(actual_tx_by_block_idx, Some(tx.clone())); + assert_eq!(actual_tx, Some(tx.clone())); + } + assert_eq!(actual_receipts.as_ref().map(|r| r.len()), Some(expected_block.body.len())); assert_eq!(actual_receipts, Some(receipts)); assert_eq!(actual_block_tx_count, Some(expected_block.body.len() as u64)); + assert_eq!(actual_state_root, Some(expected_block.header.state_root)); assert_eq!(actual_block_txs, Some(expected_block.body.clone())); + assert_eq!(actual_block_hash, Some(expected_block_hash)); assert_eq!(actual_block, Some(expected_block)); } Ok(()) } + +#[template] +#[rstest::rstest] +#[case::state_update_at_block_1(1, mock_state_updates().0)] +#[case::state_update_at_block_2(2, mock_state_updates().1)] +#[case::state_update_at_block_3(3, StateUpdates::default())] +#[case::state_update_at_block_5(5, mock_state_updates().2)] +fn test_read_state_update( + #[from(provider_with_states)] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_state_update: StateUpdates, +) { +} + +#[apply(test_read_state_update)] +fn test_read_state_update_with_in_memory_provider( + #[with(in_memory_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_state_update: StateUpdates, +) -> Result<()> { + test_read_state_update_impl(provider, block_num, expected_state_update) +} + +#[apply(test_read_state_update)] +fn test_read_state_update_with_fork_provider( + #[with(fork_provider_with_spawned_fork_network::default())] provider: BlockchainProvider< + ForkedProvider, + >, + #[case] block_num: BlockNumber, + #[case] expected_state_update: StateUpdates, +) -> Result<()> { + test_read_state_update_impl(provider, block_num, expected_state_update) +} + +#[apply(test_read_state_update)] +fn test_read_state_update_with_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_state_update: StateUpdates, +) -> Result<()> { + test_read_state_update_impl(provider, block_num, expected_state_update) +} + +fn test_read_state_update_impl( + provider: BlockchainProvider, + block_num: BlockNumber, + expected_state_update: StateUpdates, +) -> Result<()> +where + Db: StateUpdateProvider, +{ + let actual_state_update = provider.state_update(BlockHashOrNumber::from(block_num))?; + assert_eq!(actual_state_update, Some(expected_state_update)); + Ok(()) +} diff --git a/crates/katana/storage/provider/tests/class.rs b/crates/katana/storage/provider/tests/class.rs index 68a2959843..58f399fc0f 100644 --- a/crates/katana/storage/provider/tests/class.rs +++ b/crates/katana/storage/provider/tests/class.rs @@ -23,7 +23,10 @@ fn assert_state_provider_class( } mod latest { + use katana_provider::providers::db::DbProvider; + use super::*; + use crate::fixtures::db_provider; fn assert_latest_class( provider: BlockchainProvider, @@ -65,10 +68,21 @@ mod latest { ) -> Result<()> { assert_latest_class(provider, expected_class) } + + #[apply(test_latest_class_read)] + fn read_class_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_latest_class(provider, expected_class) + } } mod historical { + use katana_provider::providers::db::DbProvider; + use super::*; + use crate::fixtures::db_provider; fn assert_historical_class( provider: BlockchainProvider, @@ -143,4 +157,13 @@ mod historical { ) -> Result<()> { assert_historical_class(provider, block_num, expected_class) } + + #[apply(test_historical_class_read)] + fn read_class_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_class: Vec<(ClassHash, Option)>, + ) -> Result<()> { + assert_historical_class(provider, block_num, expected_class) + } } diff --git a/crates/katana/storage/provider/tests/contract.rs b/crates/katana/storage/provider/tests/contract.rs index 3b54fdff78..57a6f428fd 100644 --- a/crates/katana/storage/provider/tests/contract.rs +++ b/crates/katana/storage/provider/tests/contract.rs @@ -1,7 +1,9 @@ mod fixtures; use anyhow::Result; -use fixtures::{fork_provider_with_spawned_fork_network, in_memory_provider, provider_with_states}; +use fixtures::{ + db_provider, fork_provider_with_spawned_fork_network, in_memory_provider, provider_with_states, +}; use katana_primitives::block::{BlockHashOrNumber, BlockNumber}; use katana_primitives::contract::{ClassHash, ContractAddress, Nonce}; use katana_provider::providers::fork::ForkedProvider; @@ -27,6 +29,8 @@ fn assert_state_provider_contract_info( } mod latest { + use katana_provider::providers::db::DbProvider; + use super::*; fn assert_latest_contract_info( @@ -68,9 +72,19 @@ mod latest { ) -> Result<()> { assert_latest_contract_info(provider, expected_contract_info) } + + #[apply(test_latest_contract_info_read)] + fn read_storage_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_latest_contract_info(provider, expected_contract_info) + } } mod historical { + use katana_provider::providers::db::DbProvider; + use super::*; fn assert_historical_contract_info( @@ -142,4 +156,13 @@ mod historical { ) -> Result<()> { assert_historical_contract_info(provider, block_num, expected_contract_info) } + + #[apply(test_historical_storage_read)] + fn read_storage_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_contract_info: Vec<(ContractAddress, Option, Option)>, + ) -> Result<()> { + assert_historical_contract_info(provider, block_num, expected_contract_info) + } } diff --git a/crates/katana/storage/provider/tests/fixtures.rs b/crates/katana/storage/provider/tests/fixtures.rs index 4021ab3672..3f0a685001 100644 --- a/crates/katana/storage/provider/tests/fixtures.rs +++ b/crates/katana/storage/provider/tests/fixtures.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; use std::sync::Arc; +use katana_db::mdbx; use katana_primitives::block::{ BlockHashOrNumber, FinalityStatus, Header, SealedBlock, SealedBlockWithStatus, SealedHeader, }; use katana_primitives::contract::ContractAddress; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; +use katana_provider::providers::db::DbProvider; use katana_provider::providers::fork::ForkedProvider; use katana_provider::providers::in_memory::InMemoryProvider; use katana_provider::traits::block::BlockWriter; @@ -50,13 +52,13 @@ pub fn fork_provider_with_spawned_fork_network( } #[rstest::fixture] -#[default(BlockchainProvider)] -pub fn provider_with_states( - #[default(in_memory_provider())] provider: BlockchainProvider, -) -> BlockchainProvider -where - Db: BlockWriter + StateFactoryProvider, -{ +pub fn db_provider() -> BlockchainProvider { + let env = mdbx::test_utils::create_test_db(mdbx::DbEnvKind::RW); + BlockchainProvider::new(DbProvider::new(env)) +} + +#[rstest::fixture] +pub fn mock_state_updates() -> (StateUpdates, StateUpdates, StateUpdates) { let address_1 = ContractAddress::from(felt!("1")); let address_2 = ContractAddress::from(felt!("2")); @@ -69,54 +71,55 @@ where let class_hash_3 = felt!("33"); let compiled_class_hash_3 = felt!("3000"); - let state_update_at_block_1 = StateUpdatesWithDeclaredClasses { - state_updates: StateUpdates { - nonce_updates: HashMap::from([(address_1, 1u8.into()), (address_2, 1u8.into())]), - storage_updates: HashMap::from([ - ( - address_1, - HashMap::from([(1u8.into(), 100u32.into()), (2u8.into(), 101u32.into())]), - ), - ( - address_2, - HashMap::from([(1u8.into(), 200u32.into()), (2u8.into(), 201u32.into())]), - ), - ]), - declared_classes: HashMap::from([(class_hash_1, compiled_class_hash_1)]), - contract_updates: HashMap::from([(address_1, class_hash_1), (address_2, class_hash_1)]), - }, - ..Default::default() + let state_update_1 = StateUpdates { + nonce_updates: HashMap::from([(address_1, 1u8.into()), (address_2, 1u8.into())]), + storage_updates: HashMap::from([ + (address_1, HashMap::from([(1u8.into(), 100u32.into()), (2u8.into(), 101u32.into())])), + (address_2, HashMap::from([(1u8.into(), 200u32.into()), (2u8.into(), 201u32.into())])), + ]), + declared_classes: HashMap::from([(class_hash_1, compiled_class_hash_1)]), + contract_updates: HashMap::from([(address_1, class_hash_1), (address_2, class_hash_1)]), }; - let state_update_at_block_2 = StateUpdatesWithDeclaredClasses { - state_updates: StateUpdates { - nonce_updates: HashMap::from([(address_1, 2u8.into())]), - storage_updates: HashMap::from([( - address_1, - HashMap::from([(felt!("1"), felt!("111")), (felt!("2"), felt!("222"))]), - )]), - declared_classes: HashMap::from([(class_hash_2, compiled_class_hash_2)]), - contract_updates: HashMap::from([(address_2, class_hash_2)]), - }, - ..Default::default() + let state_update_2 = StateUpdates { + nonce_updates: HashMap::from([(address_1, 2u8.into())]), + storage_updates: HashMap::from([( + address_1, + HashMap::from([(felt!("1"), felt!("111")), (felt!("2"), felt!("222"))]), + )]), + declared_classes: HashMap::from([(class_hash_2, compiled_class_hash_2)]), + contract_updates: HashMap::from([(address_2, class_hash_2)]), }; - let state_update_at_block_5 = StateUpdatesWithDeclaredClasses { - state_updates: StateUpdates { - nonce_updates: HashMap::from([(address_1, 3u8.into()), (address_2, 2u8.into())]), - storage_updates: HashMap::from([ - (address_1, HashMap::from([(3u8.into(), 77u32.into())])), - ( - address_2, - HashMap::from([(1u8.into(), 12u32.into()), (2u8.into(), 13u32.into())]), - ), - ]), - contract_updates: HashMap::from([(address_1, class_hash_2), (address_2, class_hash_3)]), - declared_classes: HashMap::from([(class_hash_3, compiled_class_hash_3)]), - }, - ..Default::default() + let state_update_3 = StateUpdates { + nonce_updates: HashMap::from([(address_1, 3u8.into()), (address_2, 2u8.into())]), + storage_updates: HashMap::from([ + (address_1, HashMap::from([(3u8.into(), 77u32.into())])), + (address_2, HashMap::from([(1u8.into(), 12u32.into()), (2u8.into(), 13u32.into())])), + ]), + contract_updates: HashMap::from([(address_1, class_hash_2), (address_2, class_hash_3)]), + declared_classes: HashMap::from([(class_hash_3, compiled_class_hash_3)]), }; + (state_update_1, state_update_2, state_update_3) +} + +#[rstest::fixture] +#[default(BlockchainProvider)] +pub fn provider_with_states( + #[default(in_memory_provider())] provider: BlockchainProvider, + #[from(mock_state_updates)] state_updates: (StateUpdates, StateUpdates, StateUpdates), +) -> BlockchainProvider +where + Db: BlockWriter + StateFactoryProvider, +{ + let state_update_at_block_1 = + StateUpdatesWithDeclaredClasses { state_updates: state_updates.0, ..Default::default() }; + let state_update_at_block_2 = + StateUpdatesWithDeclaredClasses { state_updates: state_updates.1, ..Default::default() }; + let state_update_at_block_5 = + StateUpdatesWithDeclaredClasses { state_updates: state_updates.2, ..Default::default() }; + // Fill provider with states. for i in 0..=5 { diff --git a/crates/katana/storage/provider/tests/storage.rs b/crates/katana/storage/provider/tests/storage.rs index 4f12e8f5d8..07afc2e457 100644 --- a/crates/katana/storage/provider/tests/storage.rs +++ b/crates/katana/storage/provider/tests/storage.rs @@ -23,7 +23,10 @@ fn assert_state_provider_storage( } mod latest { + use katana_provider::providers::db::DbProvider; + use super::*; + use crate::fixtures::db_provider; fn assert_latest_storage_value( provider: BlockchainProvider, @@ -67,10 +70,21 @@ mod latest { ) -> Result<()> { assert_latest_storage_value(provider, expected_storage_entry) } + + #[apply(test_latest_storage_read)] + fn read_storage_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_latest_storage_value(provider, expected_storage_entry) + } } mod historical { + use katana_provider::providers::db::DbProvider; + use super::*; + use crate::fixtures::db_provider; fn assert_historical_storage_value( provider: BlockchainProvider, @@ -150,4 +164,13 @@ mod historical { ) -> Result<()> { assert_historical_storage_value(provider, block_num, expected_storage_entry) } + + #[apply(test_historical_storage_read)] + fn read_storage_from_db_provider( + #[with(db_provider())] provider: BlockchainProvider, + #[case] block_num: BlockNumber, + #[case] expected_storage_entry: Vec<(ContractAddress, StorageKey, Option)>, + ) -> Result<()> { + assert_historical_storage_value(provider, block_num, expected_storage_entry) + } } From 3b8252dc64c2ec2c5df17f2922c5a5abab668cf3 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 20 Dec 2023 13:58:11 -0700 Subject: [PATCH 189/192] Release job fix --- .github/workflows/release-dispatch.yml | 10 +++------- .github/workflows/release.yml | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index cd43bbf812..1306b4c466 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -19,12 +19,8 @@ jobs: # Workaround described here: https://github.com/actions/checkout/issues/760 - uses: actions/checkout@v3 - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - id: current_release_info - run: | - cargo install cargo-get - echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - run: cargo release version ${{ inputs.version }} --execute --no-confirm && cargo release replace --execute --no-confirm - - id: next_release_info + - id: version_info run: | cargo install cargo-get echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT @@ -32,8 +28,8 @@ jobs: with: # We have to use a PAT in order to trigger ci token: ${{ secrets.CREATE_PR_TOKEN }} - title: "Prepare release: v${{ steps.next_release_info.outputs.version }}" - commit-message: "Prepare release: v${{ steps.next_release_info.outputs.version }}" + title: "Prepare release: v${{ steps.version_info.outputs.version }}" + commit-message: "Prepare release: v${{ steps.version_info.outputs.version }}" branch: prepare-release base: main delete-branch: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ddd1751b4..84edcb2558 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,7 @@ name: release on: + workflow_dispatch: pull_request: types: [closed] branches: @@ -13,7 +14,7 @@ env: jobs: prepare: - if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release' + if: (github.event_name == 'workflow_dispatch') || (github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release') runs-on: ubuntu-latest outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} @@ -120,6 +121,7 @@ jobs: # We move binaries so they match $TARGETPLATFORM in the Docker build - name: Move Binaries + if: ${{ env.PLATFORM_NAME == 'linux' }} run: | mkdir -p $PLATFORM_NAME/$ARCH mv target/$TARGET/release/katana $PLATFORM_NAME/$ARCH @@ -128,13 +130,20 @@ jobs: shell: bash # Upload these for use with the Docker build later - - name: Upload binaries + - name: Upload docker binaries uses: actions/upload-artifact@v3 with: name: binaries path: ${{ env.PLATFORM_NAME }} retention-days: 1 + - name: Upload release artifacts + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: ${{ steps.artifacts.outputs.file_name }} + retention-days: 1 + create-draft-release: runs-on: ubuntu-latest-4-cores needs: [prepare, release] @@ -146,11 +155,15 @@ jobs: - uses: actions/checkout@v2 - uses: actions/download-artifact@v3 with: - name: binaries + name: artifacts path: artifacts + - id: version_info + run: | + cargo install cargo-get + echo "version=$(cargo get workspace.package.version)" >> $GITHUB_OUTPUT - name: Display structure of downloaded files run: ls -R artifacts - - run: gh release create --generate-notes --draft ./artifacts + - run: gh release create v${{ steps.version_info.outputs.version }} ./artifacts/*.gz --generate-notes --draft docker-build-and-push: runs-on: ubuntu-latest-4-cores @@ -164,10 +177,7 @@ jobs: uses: actions/download-artifact@v3 with: name: binaries - path: artifacts - - - name: Display structure of downloaded files - run: ls -R artifacts + path: artifacts/linux - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 From 3e63ce03246b644d595ab2709df0670b562ad93b Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 20 Dec 2023 14:47:44 -0700 Subject: [PATCH 190/192] Document release flow --- .github/workflows/release.yml | 3 +-- README.md | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84edcb2558..c15be5138a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,6 @@ name: release on: - workflow_dispatch: pull_request: types: [closed] branches: @@ -14,7 +13,7 @@ env: jobs: prepare: - if: (github.event_name == 'workflow_dispatch') || (github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release') + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release' runs-on: ubuntu-latest outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} diff --git a/README.md b/README.md index e310d4ab83..354ff142ef 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,10 @@ We welcome contributions of all kinds from anyone. See our [Contribution Guide]( ## ✏️ Enviroment -See our [Enviroment setup](https://book.dojoengine.org/getting-started/setup.html) for more information. \ No newline at end of file +See our [Enviroment setup](https://book.dojoengine.org/getting-started/setup.html) for more information. + +## Releasing + +Propose a new release by manually triggering the `release-dispatch` github action. The version value can be an semver or a level: `[patch, minor, major]`. + +Once run, the workflow will create a PR with the versioned repo which will trigger the release flow and the creation of a draft release on merge. From 90aeb26c1e6cd686c0b059a0463fafc8349ca8fb Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Wed, 20 Dec 2023 14:53:26 -0700 Subject: [PATCH 191/192] Fix tag prefix --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c15be5138a..d5b8d295ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,7 +100,7 @@ jobs: - name: Archive binaries id: artifacts env: - VERSION_NAME: ${{ needs.prepare.outputs.tag_name }} + VERSION_NAME: v${{ needs.prepare.outputs.tag_name }} run: | if [ "$PLATFORM_NAME" == "linux" ]; then tar -czvf "dojo_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release katana sozo torii dojo-language-server @@ -192,7 +192,7 @@ jobs: uses: docker/build-push-action@v3 with: push: true - tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} + tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:v${{ needs.prepare.outputs.tag_name }} platforms: linux/amd64,linux/arm64 build-contexts: | artifacts=artifacts From e351f0f568ecc0717c62b68b8f01505669a0d95c Mon Sep 17 00:00:00 2001 From: glihm Date: Wed, 20 Dec 2023 15:54:36 -0600 Subject: [PATCH 192/192] fix(dojo-lang): ensure a model has at least one attribute that is not a key (#1323) fix: ensure a model has at least one attribute that is not a key --- crates/dojo-lang/src/introspect.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/dojo-lang/src/introspect.rs b/crates/dojo-lang/src/introspect.rs index 662d8d4fbc..1370bf7470 100644 --- a/crates/dojo-lang/src/introspect.rs +++ b/crates/dojo-lang/src/introspect.rs @@ -279,6 +279,14 @@ fn handle_introspect_internal( size.push(format!("{}", size_precompute)); } + if size.is_empty() { + panic!( + "The model `{}` has only keys, ensure you have at least one field without the #[key] \ + attribute.", + name + ); + } + RewriteNode::interpolate_patched( " impl $name$Introspect<$generics$> of \