diff --git a/Cargo.lock b/Cargo.lock index a9668f0709..a4ff27151a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2434,32 +2434,6 @@ dependencies = [ "url", ] -[[package]] -name = "cainome" -version = "0.4.11" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "anyhow", - "async-trait", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-cairo-serde-derive 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "camino", - "clap", - "clap_complete", - "convert_case 0.6.0", - "serde", - "serde_json", - "starknet", - "starknet-types-core", - "thiserror 1.0.63", - "tracing", - "tracing-subscriber", - "url", -] - [[package]] name = "cainome" version = "0.4.11" @@ -2468,7 +2442,7 @@ dependencies = [ "anyhow", "async-trait", "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", - "cainome-cairo-serde-derive 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome-cairo-serde-derive", "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", @@ -2486,18 +2460,6 @@ dependencies = [ "url", ] -[[package]] -name = "cainome-cairo-serde" -version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "num-bigint", - "serde", - "serde_with", - "starknet", - "thiserror 1.0.63", -] - [[package]] name = "cainome-cairo-serde" version = "0.1.0" @@ -2520,17 +2482,6 @@ dependencies = [ "thiserror 1.0.63", ] -[[package]] -name = "cainome-cairo-serde-derive" -version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", - "unzip-n", -] - [[package]] name = "cainome-cairo-serde-derive" version = "0.1.0" @@ -2542,19 +2493,6 @@ dependencies = [ "unzip-n", ] -[[package]] -name = "cainome-parser" -version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "convert_case 0.6.0", - "quote", - "serde_json", - "starknet", - "syn 2.0.90", - "thiserror 1.0.63", -] - [[package]] name = "cainome-parser" version = "0.1.0" @@ -2581,24 +2519,6 @@ dependencies = [ "thiserror 1.0.63", ] -[[package]] -name = "cainome-rs" -version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "camino", - "prettyplease", - "proc-macro2", - "quote", - "serde_json", - "starknet", - "syn 2.0.90", - "thiserror 1.0.63", -] - [[package]] name = "cainome-rs" version = "0.1.0" @@ -2635,24 +2555,6 @@ dependencies = [ "thiserror 1.0.63", ] -[[package]] -name = "cainome-rs-macro" -version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" -dependencies = [ - "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "proc-macro-error", - "proc-macro2", - "quote", - "serde_json", - "starknet", - "syn 2.0.90", - "thiserror 1.0.63", -] - [[package]] name = "cainome-rs-macro" version = "0.1.0" @@ -3934,7 +3836,6 @@ dependencies = [ [[package]] name = "create-output-dir" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" dependencies = [ "anyhow", "core-foundation 0.10.0", @@ -4611,7 +4512,7 @@ dependencies = [ "anyhow", "assert_matches", "async-trait", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "camino", "chrono", "convert_case 0.6.0", @@ -4635,28 +4536,6 @@ version = "1.1.2" name = "dojo-examples-spawn-and-move" version = "1.1.2" -[[package]] -name = "dojo-lang" -version = "1.1.1" -source = "git+https://github.com/dojoengine/dojo?rev=17cda07a333e8c157c38dc455ad27472bbd75740#17cda07a333e8c157c38dc455ad27472bbd75740" -dependencies = [ - "anyhow", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-plugins", - "cairo-lang-semantic", - "cairo-lang-syntax", - "cairo-lang-utils", - "dojo-types 1.1.1", - "itertools 0.12.1", - "serde", - "smol_str", - "starknet", - "starknet-crypto 0.7.2", - "tracing", -] - [[package]] name = "dojo-lang" version = "1.1.2" @@ -4669,7 +4548,7 @@ dependencies = [ "cairo-lang-semantic", "cairo-lang-syntax", "cairo-lang-utils", - "dojo-types 1.1.2", + "dojo-types", "itertools 0.12.1", "serde", "smol_str", @@ -4684,7 +4563,7 @@ version = "1.1.2" dependencies = [ "cairo-lang-language-server", "clap", - "dojo-lang 1.1.2", + "dojo-lang", ] [[package]] @@ -4727,34 +4606,12 @@ dependencies = [ "url", ] -[[package]] -name = "dojo-types" -version = "1.1.1" -source = "git+https://github.com/dojoengine/dojo?rev=17cda07a333e8c157c38dc455ad27472bbd75740#17cda07a333e8c157c38dc455ad27472bbd75740" -dependencies = [ - "anyhow", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", - "crypto-bigint", - "hex", - "indexmap 2.5.0", - "itertools 0.12.1", - "num-traits 0.2.19", - "regex", - "serde", - "serde_json", - "starknet", - "starknet-crypto 0.7.2", - "strum 0.25.0", - "strum_macros 0.25.3", - "thiserror 1.0.63", -] - [[package]] name = "dojo-types" version = "1.1.2" dependencies = [ "anyhow", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "crypto-bigint", "hex", "indexmap 2.5.0", @@ -4794,9 +4651,9 @@ version = "1.1.2" dependencies = [ "anyhow", "async-trait", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "cairo-lang-starknet-classes", - "dojo-types 1.1.2", + "dojo-types", "futures", "hex", "hex-literal", @@ -4820,7 +4677,7 @@ name = "dojo-world-abigen" version = "1.1.2" dependencies = [ "anyhow", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "cairo-lang-starknet", "cairo-lang-starknet-classes", "camino", @@ -8029,7 +7886,7 @@ dependencies = [ "anyhow", "assert_matches", "byte-unit", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "clap", "clap_complete", "comfy-table", @@ -8448,7 +8305,7 @@ dependencies = [ "alloy-primitives", "anyhow", "assert_matches", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "dojo-metrics", "dojo-test-utils", "dojo-utils", @@ -10758,7 +10615,7 @@ name = "piltover" version = "0.1.0" source = "git+https://github.com/keep-starknet-strange/piltover.git?rev=fb9d988#fb9d9889100f9c88eba1ff91f619c1a67afc2a71" dependencies = [ - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "starknet", ] @@ -12528,7 +12385,6 @@ dependencies = [ [[package]] name = "scarb" version = "2.9.2" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" dependencies = [ "anyhow", "async-trait", @@ -12560,7 +12416,7 @@ dependencies = [ "derive_builder", "dialoguer", "directories", - "dojo-lang 1.1.1", + "dojo-lang", "dunce", "flate2", "fs4", @@ -12581,9 +12437,9 @@ dependencies = [ "redb", "reqwest 0.11.27", "scarb-build-metadata", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1)", - "scarb-proc-macro-server-types 0.1.0 (git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1)", - "scarb-stable-hash 1.0.0 (git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1)", + "scarb-metadata 1.13.0", + "scarb-proc-macro-server-types 0.1.0", + "scarb-stable-hash 1.0.0", "scarb-ui", "semver 1.0.23", "serde", @@ -12613,7 +12469,6 @@ dependencies = [ [[package]] name = "scarb-build-metadata" version = "2.9.2" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" dependencies = [ "cargo_metadata", ] @@ -12621,34 +12476,31 @@ dependencies = [ [[package]] name = "scarb-metadata" version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8b71f63999dbb6d269fbc6fd61310016ab3a160fb13e52a6511a2b904359f0" dependencies = [ "camino", + "derive_builder", "semver 1.0.23", "serde", "serde_json", - "thiserror 1.0.63", + "thiserror 2.0.11", ] [[package]] name = "scarb-metadata" version = "1.13.0" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8b71f63999dbb6d269fbc6fd61310016ab3a160fb13e52a6511a2b904359f0" dependencies = [ "camino", - "derive_builder", "semver 1.0.23", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 1.0.63", ] [[package]] name = "scarb-proc-macro-server-types" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb73a7fb2681885d05a1d918f95b179c034fb5d5a57adfbb718f36946fe5ade" dependencies = [ "cairo-lang-macro", "serde", @@ -12658,7 +12510,8 @@ dependencies = [ [[package]] name = "scarb-proc-macro-server-types" version = "0.1.0" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb73a7fb2681885d05a1d918f95b179c034fb5d5a57adfbb718f36946fe5ade" dependencies = [ "cairo-lang-macro", "serde", @@ -12668,8 +12521,6 @@ dependencies = [ [[package]] name = "scarb-stable-hash" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1902536b23a05dd165d3992865870aaf1b0650317767cbf171ed2ca5903732a9" dependencies = [ "data-encoding", "xxhash-rust", @@ -12678,7 +12529,8 @@ dependencies = [ [[package]] name = "scarb-stable-hash" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1902536b23a05dd165d3992865870aaf1b0650317767cbf171ed2ca5903732a9" dependencies = [ "data-encoding", "xxhash-rust", @@ -12687,14 +12539,13 @@ dependencies = [ [[package]] name = "scarb-ui" version = "0.1.5" -source = "git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1#da5f683adbd25361d080c2d9f624924aa9f112d1" dependencies = [ "anyhow", "camino", "clap", "console", "indicatif", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1)", + "scarb-metadata 1.13.0", "serde", "serde_json", "tracing-core", @@ -13388,7 +13239,7 @@ version = "1.1.2" dependencies = [ "anyhow", "async-trait", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "cairo-lang-sierra", "cairo-lang-test-plugin", "cairo-lang-test-runner", @@ -13398,7 +13249,7 @@ dependencies = [ "colored", "dojo-bindgen", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "katana-rpc-api", @@ -13406,7 +13257,7 @@ dependencies = [ "notify", "reqwest 0.11.27", "scarb", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=da5f683adbd25361d080c2d9f624924aa9f112d1)", + "scarb-metadata 1.13.0", "scarb-ui", "semver 1.0.23", "serde", @@ -13435,11 +13286,11 @@ dependencies = [ "anyhow", "assert_fs", "async-trait", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "colored", "colored_json", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "futures", @@ -14971,7 +14822,7 @@ dependencies = [ "async-trait", "camino", "crypto-bigint", - "dojo-types 1.1.2", + "dojo-types", "dojo-world", "futures", "futures-util", @@ -15005,7 +14856,7 @@ dependencies = [ "chrono", "convert_case 0.6.0", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "katana-runner", @@ -15037,11 +14888,11 @@ dependencies = [ name = "torii-grpc" version = "1.1.2" dependencies = [ - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "camino", "crypto-bigint", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "futures", @@ -15089,12 +14940,12 @@ dependencies = [ "async-trait", "base64 0.21.7", "bitflags 2.6.0", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "chrono", "crypto-bigint", "data-url", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "futures-channel", @@ -15128,7 +14979,7 @@ version = "1.1.2" dependencies = [ "anyhow", "chrono", - "dojo-types 1.1.2", + "dojo-types", "dojo-world", "futures", "indexmap 2.5.0", @@ -15168,7 +15019,7 @@ dependencies = [ "chrono", "ctrlc", "dojo-metrics", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "either", @@ -15246,12 +15097,12 @@ dependencies = [ "async-trait", "base64 0.21.7", "bitflags 2.6.0", - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "chrono", "crypto-bigint", "data-url", "dojo-test-utils", - "dojo-types 1.1.2", + "dojo-types", "dojo-utils", "dojo-world", "futures-channel", @@ -15281,9 +15132,9 @@ dependencies = [ name = "torii-typed-data" version = "1.1.2" dependencies = [ - "cainome 0.4.11 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.12)", + "cainome 0.4.11", "crypto-bigint", - "dojo-types 1.1.2", + "dojo-types", "indexmap 2.5.0", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 517291653e..856d53d9c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -208,9 +208,12 @@ rpassword = "7.2.0" rstest = "0.18.2" rstest_reuse = "0.6.0" salsa = "0.16.1" -scarb = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } -scarb-metadata = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } -scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } +scarb = { path = "../../swm/scarb/scarb" } +scarb-metadata = { path = "../../swm/scarb/scarb-metadata" } +scarb-ui = { path = "../../swm/scarb/utils/scarb-ui" } +#scarb = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } +#scarb-metadata = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } +#scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "da5f683adbd25361d080c2d9f624924aa9f112d1" } semver = "1.0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } @@ -240,6 +243,7 @@ walkdir = "2.5.0" ipfs-api-backend-hyper = { git = "https://github.com/ferristseng/rust-ipfs-api", rev = "af2c17f7b19ef5b9898f458d97a90055c3605633", features = [ "with-hyper-rustls", "with-send-sync" ] } mime_guess = "2.0" + # server hyper = "0.14.27" warp = "0.3" diff --git a/bin/sozo/src/commands/hash.rs b/bin/sozo/src/commands/hash.rs index dca9619213..2283389bd4 100644 --- a/bin/sozo/src/commands/hash.rs +++ b/bin/sozo/src/commands/hash.rs @@ -132,6 +132,10 @@ impl HashArgs { ns_from_config.extend(contracts.iter().map(|c| get_namespace_from_tag(&c.tag))); } + if let Some(libraries) = &profile_config.libraries { + ns_from_config.extend(libraries.iter().map(|c| get_namespace_from_tag(&c.tag))); + } + if let Some(events) = &profile_config.events { ns_from_config.extend(events.iter().map(|e| get_namespace_from_tag(&e.tag))); } @@ -163,6 +167,10 @@ impl HashArgs { res_from_config.extend(contracts.iter().map(|c| get_name_from_tag(&c.tag))); } + if let Some(libraries) = &profile_config.libraries { + res_from_config.extend(libraries.iter().map(|c| get_name_from_tag(&c.tag))); + } + if let Some(events) = &profile_config.events { res_from_config.extend(events.iter().map(|e| get_name_from_tag(&e.tag))); } diff --git a/bin/sozo/src/commands/inspect.rs b/bin/sozo/src/commands/inspect.rs index d9a660cc65..7ce1af9fe9 100644 --- a/bin/sozo/src/commands/inspect.rs +++ b/bin/sozo/src/commands/inspect.rs @@ -77,6 +77,7 @@ enum ResourceInspect { Contract(ContractInspect), Model(ModelInspect), Event(EventInspect), + Library(LibraryInspect), } #[derive(Debug, Tabled, Serialize)] @@ -115,6 +116,20 @@ struct ContractInspect { current_class_hash: String, } +#[derive(Debug, Tabled, Serialize)] +struct LibraryInspect { + #[tabled(rename = "Libraries")] + tag: String, + #[tabled(rename = "Version")] + version: String, + #[tabled(rename = "Status")] + status: ResourceStatus, + #[tabled(rename = "Dojo Selector")] + selector: String, + #[tabled(rename = "Class Hash")] + current_class_hash: String, +} + #[derive(Debug, Tabled, Serialize)] struct ModelInspect { #[tabled(rename = "Models")] @@ -298,6 +313,7 @@ fn inspect_world(world_diff: &WorldDiff) { let mut external_contracts_disp = vec![]; let mut models_disp = vec![]; let mut events_disp = vec![]; + let mut libraries_disp = vec![]; for resource in world_diff.resources.values() { match resource.resource_type() { @@ -317,6 +333,10 @@ fn inspect_world(world_diff: &WorldDiff) { ResourceInspect::Event(e) => events_disp.push(e), _ => unreachable!(), }, + ResourceType::Library => match resource_diff_display(world_diff, resource) { + ResourceInspect::Library(l) => libraries_disp.push(l), + _ => unreachable!(), + }, _ => {} } } @@ -329,12 +349,15 @@ fn inspect_world(world_diff: &WorldDiff) { contracts_disp.sort_by_key(|m| m.tag.to_string()); models_disp.sort_by_key(|m| m.tag.to_string()); events_disp.sort_by_key(|m| m.tag.to_string()); + libraries_disp.sort_by_key(|m| m.tag.to_string()); external_contracts_disp.sort_by_key(|c| format!("{}-{}", c.contract_name, c.instance_name)); print_table(&namespaces_disp, Some(Color::FG_BRIGHT_BLACK), None); print_table(&contracts_disp, Some(Color::FG_BRIGHT_BLACK), None); + print_table(&libraries_disp, Some(Color::FG_BRIGHT_BLACK), None); print_table(&models_disp, Some(Color::FG_BRIGHT_BLACK), None); print_table(&events_disp, Some(Color::FG_BRIGHT_BLACK), None); + print_table(&libraries_disp, Some(Color::FG_BRIGHT_BLACK), None); print_table(&external_contracts_disp, Some(Color::FG_BRIGHT_BLACK), None); } @@ -409,6 +432,46 @@ fn resource_diff_display(world_diff: &WorldDiff, resource: &ResourceDiff) -> Res selector: format!("{:#066x}", resource.dojo_selector()), }) } + ResourceType::Library => { + let (_current_class_hash, status) = match resource { + ResourceDiff::Created(_) => { + (resource.current_class_hash(), ResourceStatus::Created) + } + ResourceDiff::Updated(_, _remote) => { + (resource.current_class_hash(), ResourceStatus::Updated) + } + ResourceDiff::Synced(_, remote) => ( + remote.current_class_hash(), + if has_dirty_perms { + ResourceStatus::DirtyLocalPerms + } else { + ResourceStatus::Synced + }, + ), + }; + + let status = if world_diff.profile_config.is_skipped(&resource.tag()) { + ResourceStatus::MigrationSkipped + } else { + status + }; + + let version = world_diff + .profile_config + .lib_versions + .as_ref() + .expect("expected lib_versions") + .get(&resource.tag()) + .expect("lib_version not found"); + + ResourceInspect::Library(LibraryInspect { + tag: resource.tag(), + status, + current_class_hash: format!("{:#066x}", resource.current_class_hash()), + selector: format!("{:#066x}", resource.dojo_selector()), + version: version.to_string(), + }) + } ResourceType::Model => { let status = match resource { ResourceDiff::Created(_) => ResourceStatus::Created, diff --git a/bin/sozo/tests/test_data/policies.json b/bin/sozo/tests/test_data/policies.json index 2fde9c26c5..5aee329459 100644 --- a/bin/sozo/tests/test_data/policies.json +++ b/bin/sozo/tests/test_data/policies.json @@ -1,134 +1,138 @@ [ { - "target": "0xca72f1cd782b614fa12c8b54afa895a169a4de1792738d4e3f09d0929f7834", + "target": "0x2fbd487dc4ccae7a01c767addb192df5c285fe07fe06756b463d7b5f3b43044", "method": "upgrade" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x6460654b134506a238bf79b73d2be078f587718521048c80b78514fe90b6336", + "method": "upgrade" + }, + { + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "spawn" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "move" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "set_player_config" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "update_player_config_name" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "reset_player_config" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "set_player_server_profile" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "set_models" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "enter_dungeon" }, { - "target": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", - "method": "upgrade" - }, - { - "target": "0x608ffd2e6b74a7bede256770ebe3d07bc65c79622e6a9396ea764011152102", - "method": "upgrade" - }, - { - "target": "0x780e3207b4f11b56f32cc0f19975af5b3a4df3cad6a4b0ab59a1702ba0304d8", + "target": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", "method": "upgrade" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "uuid" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "set_metadata" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "register_namespace" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "register_event" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "register_model" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "register_contract" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", + "method": "register_library" + }, + { + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "init_contract" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "upgrade_event" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "upgrade_model" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "upgrade_contract" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "emit_event" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "emit_events" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "set_entity" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "set_entities" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "delete_entity" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "delete_entities" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "grant_owner" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "revoke_owner" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "grant_writer" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "method": "revoke_writer" }, { - "target": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "target": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", + "method": "upgrade" + }, + { + "target": "0x64b964be7fb8d9d9161a34b39cb2709b7a36b46063e0180c93a62aabc899b52", "method": "upgrade" }, { diff --git a/crates/dojo/core-cairo-test/src/lib.cairo b/crates/dojo/core-cairo-test/src/lib.cairo index 2963fb10a6..16ae6a5b1f 100644 --- a/crates/dojo/core-cairo-test/src/lib.cairo +++ b/crates/dojo/core-cairo-test/src/lib.cairo @@ -58,6 +58,9 @@ mod tests { mod model; pub use model::deploy_world_for_model_upgrades; + + mod library; + pub use library::*; } mod world { diff --git a/crates/dojo/core-cairo-test/src/tests/helpers/library.cairo b/crates/dojo/core-cairo-test/src/tests/helpers/library.cairo new file mode 100644 index 0000000000..217e11d256 --- /dev/null +++ b/crates/dojo/core-cairo-test/src/tests/helpers/library.cairo @@ -0,0 +1,16 @@ +#[starknet::interface] +pub trait LibraryA { + fn get_byte(self: @T) -> u8; +} + +#[dojo::library] +pub mod library_a { + use super::LibraryA; + + #[abi(embed_v0)] + impl LibraryAImpl of LibraryA { + fn get_byte(self: @ContractState) -> u8 { + 42 + } + } +} diff --git a/crates/dojo/core-cairo-test/src/tests/world/event.cairo b/crates/dojo/core-cairo-test/src/tests/world/event.cairo index df32021b5f..64bb3fa6a1 100644 --- a/crates/dojo/core-cairo-test/src/tests/world/event.cairo +++ b/crates/dojo/core-cairo-test/src/tests/world/event.cairo @@ -311,7 +311,7 @@ fn test_upgrade_event_from_event_writer() { #[test] #[should_panic( - expected: ("Resource `dojo-SimpleEvent` is already registered", 'ENTRYPOINT_FAILED'), + expected: ("Resource (Event) `dojo-SimpleEvent` is already registered", 'ENTRYPOINT_FAILED'), )] fn test_upgrade_event_from_random_account() { let bob = starknet::contract_address_const::<0xb0b>(); diff --git a/crates/dojo/core-cairo-test/src/tests/world/model.cairo b/crates/dojo/core-cairo-test/src/tests/world/model.cairo index 5e510194b6..9dbeff6ebf 100644 --- a/crates/dojo/core-cairo-test/src/tests/world/model.cairo +++ b/crates/dojo/core-cairo-test/src/tests/world/model.cairo @@ -351,7 +351,9 @@ fn test_upgrade_model_from_model_writer() { } #[test] -#[should_panic(expected: ("Resource `dojo-Foo` is already registered", 'ENTRYPOINT_FAILED'))] +#[should_panic( + expected: ("Resource (Model) `dojo-Foo` is already registered", 'ENTRYPOINT_FAILED'), +)] fn test_upgrade_model_from_random_account() { let bob = starknet::contract_address_const::<0xb0b>(); let alice = starknet::contract_address_const::<0xa11ce>(); diff --git a/crates/dojo/core-cairo-test/src/tests/world/world.cairo b/crates/dojo/core-cairo-test/src/tests/world/world.cairo index 0dcc51f53b..eb6d2da1c6 100644 --- a/crates/dojo/core-cairo-test/src/tests/world/world.cairo +++ b/crates/dojo/core-cairo-test/src/tests/world/world.cairo @@ -10,7 +10,8 @@ use dojo::event::{Event, EventStorage}; use crate::tests::helpers::{ bar, IbarDispatcherTrait, drop_all_events, deploy_world_and_bar, Foo, m_Foo, test_contract, - test_contract_with_dojo_init_args, SimpleEvent, e_SimpleEvent, deploy_world, + test_contract_with_dojo_init_args, SimpleEvent, e_SimpleEvent, deploy_world, library_a, + LibraryALibraryDispatcher, LibraryADispatcherTrait, }; use crate::{spawn_test_world, ContractDefTrait, NamespaceDef, TestResource, WorldStorageTestTrait}; @@ -331,3 +332,43 @@ pub fn dns_valid_class_hash() { // TODO: once starknet 0.13.4 is out, uncomment that. //assert!(class_hash == bar::TEST_CLASS_HASH.try_into().unwrap()); } + +#[test] +fn test_register_library() { + let world = deploy_world(); + let world = world.dispatcher; + + world.register_library("dojo", library_a::TEST_CLASS_HASH.try_into().unwrap(), "liba", "0_1_0"); +} + +#[test] +#[should_panic( + expected: ( + "Resource (Library) `dojo-liba_v0_1_0` is already registered. Libraries can't be updated, increment the version in the Dojo configuration file instead.", + 'ENTRYPOINT_FAILED', + ), +)] +fn test_register_library_already_registered() { + let world = deploy_world(); + let world = world.dispatcher; + + world.register_library("dojo", library_a::TEST_CLASS_HASH.try_into().unwrap(), "liba", "0_1_0"); + + world.register_library("dojo", library_a::TEST_CLASS_HASH.try_into().unwrap(), "liba", "0_1_0"); +} + +#[test] +fn test_library_call() { + let world = deploy_world(); + let world = world.dispatcher; + + world.register_library("dojo", library_a::TEST_CLASS_HASH.try_into().unwrap(), "liba", "0_1_0"); + + let world = WorldStorageTrait::new(world, @"dojo"); + + let (_, class_hash) = world.dns(@"liba_v0_1_0").unwrap(); + + let liba = LibraryALibraryDispatcher { class_hash }; + let res = liba.get_byte(); + assert(res == 42, 'should return 42'); +} diff --git a/crates/dojo/core-cairo-test/src/world.cairo b/crates/dojo/core-cairo-test/src/world.cairo index 06ecd82c0e..2bdd4d277e 100644 --- a/crates/dojo/core-cairo-test/src/world.cairo +++ b/crates/dojo/core-cairo-test/src/world.cairo @@ -20,6 +20,8 @@ pub enum TestResource { Event: TestClassHash, Model: TestClassHash, Contract: TestClassHash, + /// (test_class_hash, name, version) + Library: (TestClassHash, @ByteArray, @ByteArray), } #[derive(Drop, Copy)] @@ -172,6 +174,17 @@ pub fn spawn_test_world(namespaces_defs: Span) -> WorldStorage { TestResource::Contract(ch) => { world.register_contract(*ch, namespace.clone(), (*ch).try_into().unwrap()); }, + TestResource::Library(( + ch, name, version, + )) => { + world + .register_library( + namespace.clone(), + (*ch).try_into().unwrap(), + (*name).clone(), + (*version).clone(), + ); + }, } } }; diff --git a/crates/dojo/core/src/contract/interface.cairo b/crates/dojo/core/src/contract/interface.cairo index f8d30031d4..7cf2dd1f4e 100644 --- a/crates/dojo/core/src/contract/interface.cairo +++ b/crates/dojo/core/src/contract/interface.cairo @@ -1,2 +1,5 @@ #[starknet::interface] pub trait IContract {} + +#[starknet::interface] +pub trait ILibrary {} diff --git a/crates/dojo/core/src/lib.cairo b/crates/dojo/core/src/lib.cairo index eb5603d6d0..cb464b3165 100644 --- a/crates/dojo/core/src/lib.cairo +++ b/crates/dojo/core/src/lib.cairo @@ -1,6 +1,9 @@ pub mod contract { pub mod interface; - pub use interface::{IContract, IContractDispatcher, IContractDispatcherTrait}; + pub use interface::{ + IContract, IContractDispatcher, IContractDispatcherTrait, ILibrary, ILibraryDispatcher, + ILibraryDispatcherTrait, + }; pub mod components { pub mod upgradeable; @@ -25,7 +28,8 @@ pub mod meta { pub mod interface; pub use interface::{ IDeployedResource, IDeployedResourceDispatcher, IDeployedResourceDispatcherTrait, - IStoredResource, IStoredResourceDispatcher, IStoredResourceDispatcherTrait, + IDeployedResourceLibraryDispatcher, IStoredResource, IStoredResourceDispatcher, + IStoredResourceDispatcherTrait, }; pub mod introspect; diff --git a/crates/dojo/core/src/world/errors.cairo b/crates/dojo/core/src/world/errors.cairo index dc2eb762a2..232f8920f4 100644 --- a/crates/dojo/core/src/world/errors.cairo +++ b/crates/dojo/core/src/world/errors.cairo @@ -27,15 +27,23 @@ pub fn no_namespace_write_access(caller: ContractAddress, namespace: @ByteArray) } pub fn event_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { - format!("Resource `{}-{}` is already registered", namespace, name) + format!("Resource (Event) `{}-{}` is already registered", namespace, name) } pub fn model_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { - format!("Resource `{}-{}` is already registered", namespace, name) + format!("Resource (Model) `{}-{}` is already registered", namespace, name) } pub fn contract_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { - format!("Resource `{}-{}` is already registered", namespace, name) + format!("Resource (Contract) `{}-{}` is already registered", namespace, name) +} + +pub fn library_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { + format!( + "Resource (Library) `{}-{}` is already registered. Libraries can't be updated, increment the version in the Dojo configuration file instead.", + namespace, + name, + ) } pub fn resource_not_registered_details(namespace: @ByteArray, name: @ByteArray) -> ByteArray { diff --git a/crates/dojo/core/src/world/iworld.cairo b/crates/dojo/core/src/world/iworld.cairo index 82632d2e80..0d7f80a61d 100644 --- a/crates/dojo/core/src/world/iworld.cairo +++ b/crates/dojo/core/src/world/iworld.cairo @@ -83,6 +83,23 @@ pub trait IWorld { ref self: T, salt: felt252, namespace: ByteArray, class_hash: ClassHash, ) -> ContractAddress; + /// Registers and declare a library associated with the world and returns the class_hash of + /// newly declared library. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the contract to be registered. + /// * `class_hash` - The class hash of the library. + /// * `name` - The name of the library. + /// * `version` - The version of the library. + fn register_library( + ref self: T, + namespace: ByteArray, + class_hash: ClassHash, + name: ByteArray, + version: ByteArray, + ) -> ClassHash; + /// Initializes a contract associated registered in the world. /// /// As a constructor call, the initialization function can be called only once, and only diff --git a/crates/dojo/core/src/world/resource.cairo b/crates/dojo/core/src/world/resource.cairo index 7ae8061c90..1a98d530c0 100644 --- a/crates/dojo/core/src/world/resource.cairo +++ b/crates/dojo/core/src/world/resource.cairo @@ -1,6 +1,6 @@ //! World's resources. -use starknet::ContractAddress; +use starknet::{ContractAddress, ClassHash}; /// Resource is the type of the resource that can be registered in the world. /// @@ -28,8 +28,14 @@ use starknet::ContractAddress; /// - Contract: (ContractAddress, NamespaceHash) /// A contract defines user logic to interact with the world's data (models) and to emit events. /// +/// - Library: (ClassHash, NamespaceHash) +/// A library defines user logic. +/// /// - Unregistered: The unregistered state, required to ensure the security of the world /// to not have operations done on non-existent resources. +/// +/// WARNING: enum order must be preserved to ensure backward compatibility +/// #[derive(Drop, starknet::Store, Serde, Default, Debug)] pub enum Resource { Model: (ContractAddress, felt252), @@ -39,6 +45,7 @@ pub enum Resource { World, #[default] Unregistered, + Library: (ClassHash, felt252), } #[generate_trait] diff --git a/crates/dojo/core/src/world/storage.cairo b/crates/dojo/core/src/world/storage.cairo index ca3218dd1c..d787cf346d 100644 --- a/crates/dojo/core/src/world/storage.cairo +++ b/crates/dojo/core/src/world/storage.cairo @@ -77,10 +77,27 @@ pub impl WorldStorageInternalImpl of WorldStorageTrait { // .expect('Failed to get class hash'); Option::Some((contract_address, 0.try_into().unwrap())) }, + Resource::Library(( + class_hash, _, + )) => { Option::Some((starknet::contract_address_const::<0>(), class_hash)) }, _ => Option::None, } } + fn dns_address(self: @WorldStorage, contract_name: @ByteArray) -> Option { + match self.dns(contract_name) { + Option::Some((address, _)) => Option::Some(address), + Option::None => Option::None, + } + } + + fn dns_class_hash(self: @WorldStorage, contract_name: @ByteArray) -> Option { + match self.dns(contract_name) { + Option::Some((_, class_hash)) => Option::Some(class_hash), + Option::None => Option::None, + } + } + fn resource_selector(self: @WorldStorage, name: @ByteArray) -> felt252 { dojo::utils::selector_from_namespace_and_name(*self.namespace_hash, name) } diff --git a/crates/dojo/core/src/world/world_contract.cairo b/crates/dojo/core/src/world/world_contract.cairo index 495412b0d1..8e6a08a986 100644 --- a/crates/dojo/core/src/world/world_contract.cairo +++ b/crates/dojo/core/src/world/world_contract.cairo @@ -42,7 +42,7 @@ pub mod world { use dojo::meta::{ Layout, IStoredResourceDispatcher, IStoredResourceDispatcherTrait, IDeployedResourceDispatcher, IDeployedResourceDispatcherTrait, LayoutCompareTrait, - TyCompareTrait, + IDeployedResourceLibraryDispatcher, TyCompareTrait, }; use dojo::model::{Model, ResourceMetadata, metadata, ModelIndex}; use dojo::storage; @@ -68,6 +68,7 @@ pub mod world { EventUpgraded: EventUpgraded, ContractUpgraded: ContractUpgraded, ContractInitialized: ContractInitialized, + LibraryRegistered: LibraryRegistered, EventEmitted: EventEmitted, MetadataUpdate: MetadataUpdate, StoreSetRecord: StoreSetRecord, @@ -107,6 +108,15 @@ pub mod world { pub class_hash: ClassHash, } + #[derive(Drop, starknet::Event)] + pub struct LibraryRegistered { + #[key] + pub name: ByteArray, + #[key] + pub namespace: ByteArray, + pub class_hash: ClassHash, + } + #[derive(Drop, starknet::Event)] pub struct MetadataUpdate { #[key] @@ -755,6 +765,8 @@ pub mod world { ), ), } + // class_hash will be retrieved with get_class_hash_at_syscall, so no need to update + // resource. } fn init_contract(ref self: ContractState, selector: felt252, init_calldata: Span) { @@ -788,6 +800,47 @@ pub mod world { } } + fn register_library( + ref self: ContractState, + namespace: ByteArray, + class_hash: ClassHash, + name: ByteArray, + version: ByteArray, + ) -> ClassHash { + let caller = get_caller_address(); + + let namespace_hash = bytearray_hash(@namespace); + + let contract_name = format!("{}_v{}", name, version); + self.assert_name(@contract_name); + + let contract_selector = selector_from_namespace_and_name( + namespace_hash, @contract_name, + ); + + let maybe_existing_library = self.resources.read(contract_selector); + if !maybe_existing_library.is_unregistered() { + panic_with_byte_array( + @errors::library_already_registered(@namespace, @contract_name), + ); + } + + if !self.is_namespace_registered(namespace_hash) { + panic_with_byte_array(@errors::namespace_not_registered(@namespace)); + } + + self.assert_caller_permissions(namespace_hash, Permission::Owner); + + self.owners.write((contract_selector, caller), true); + self + .resources + .write(contract_selector, Resource::Library((class_hash, namespace_hash))); + + self.emit(LibraryRegistered { class_hash, namespace, name: contract_name }); + + class_hash + } + fn uuid(ref self: ContractState) -> usize { let current = self.nonce.read(); self.nonce.write(current + 1); @@ -1143,6 +1196,12 @@ pub mod world { Resource::Namespace(ns) => { format!("namespace `{}`", ns) }, Resource::World => { format!("world") }, Resource::Unregistered => { panic!("Unreachable") }, + Resource::Library(( + class_hash, _, + )) => { + let d = IDeployedResourceLibraryDispatcher { class_hash }; + format!("library (or its namespace) `{}`", d.dojo_name()) + }, }; let caller_name = if caller == get_tx_info().account_contract_address { diff --git a/crates/dojo/lang/src/attribute_macros/library.rs b/crates/dojo/lang/src/attribute_macros/library.rs new file mode 100644 index 0000000000..e990d844c3 --- /dev/null +++ b/crates/dojo/lang/src/attribute_macros/library.rs @@ -0,0 +1,239 @@ +use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; +use cairo_lang_defs::plugin::{ + DynGeneratedFileAuxData, MacroPluginMetadata, PluginDiagnostic, PluginGeneratedFile, + PluginResult, +}; +use cairo_lang_diagnostics::Severity; +use cairo_lang_plugins::plugins::HasItemsInCfgEx; +use cairo_lang_syntax::node::ast::MaybeModuleBody; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use dojo_types::naming; + +use crate::aux_data::ContractAuxData; + +const LIBRARY_PATCH: &str = include_str!("./patches/library.patch.cairo"); +const CONSTRUCTOR_FN: &str = "constructor"; +const DOJO_INIT_FN: &str = "dojo_init"; + +#[derive(Debug, Clone, Default)] +pub struct ContractParameters { + pub namespace: Option, +} + +#[derive(Debug)] +pub struct DojoLibrary { + diagnostics: Vec, + systems: Vec, +} + +impl DojoLibrary { + pub fn from_module( + db: &dyn SyntaxGroup, + module_ast: &ast::ItemModule, + metadata: &MacroPluginMetadata<'_>, + ) -> PluginResult { + let name = module_ast.name(db).text(db); + + let mut library = DojoLibrary { diagnostics: vec![], systems: vec![] }; + + for (id, value) in [("name", &name.to_string())] { + if !naming::is_name_valid(value) { + return PluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: module_ast.stable_ptr().0, + message: format!( + "The contract {id} '{value}' can only contain characters (a-z/A-Z), \ + digits (0-9) and underscore (_)." + ), + severity: Severity::Error, + }], + remove_original_item: false, + }; + } + } + + let mut has_event = false; + let mut has_storage = false; + let mut has_init = false; + let mut has_constructor = false; + + if let MaybeModuleBody::Some(body) = module_ast.body(db) { + let mut body_nodes: Vec<_> = body + .iter_items_in_cfg(db, metadata.cfg_set) + .flat_map(|el| { + if let ast::ModuleItem::Enum(ref enum_ast) = el { + if enum_ast.name(db).text(db).to_string() == "Event" { + has_event = true; + return library.merge_event(db, enum_ast.clone()); + } + } else if let ast::ModuleItem::Struct(ref struct_ast) = el { + if struct_ast.name(db).text(db).to_string() == "Storage" { + has_storage = true; + return library.merge_storage(db, struct_ast.clone()); + } + } else if let ast::ModuleItem::FreeFunction(ref fn_ast) = el { + let fn_decl = fn_ast.declaration(db); + let fn_name = fn_decl.name(db).text(db); + + if fn_name == CONSTRUCTOR_FN { + has_constructor = true; + } + + if fn_name == DOJO_INIT_FN { + has_init = true; + } + } + + vec![RewriteNode::Copied(el.as_syntax_node())] + }) + .collect(); + + if has_constructor { + return PluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: module_ast.stable_ptr().0, + message: format!("The library {name} cannot have a constructor"), + severity: Severity::Error, + }], + remove_original_item: false, + }; + } + + if has_init { + return PluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: module_ast.stable_ptr().0, + message: format!("The library {name} cannot have a dojo_init"), + severity: Severity::Error, + }], + remove_original_item: false, + }; + } + + if !has_event { + body_nodes.append(&mut library.create_event()) + } + + if !has_storage { + body_nodes.append(&mut library.create_storage()) + } + + let mut builder = PatchBuilder::new(db, module_ast); + builder.add_modified(RewriteNode::Mapped { + node: Box::new(RewriteNode::interpolate_patched( + LIBRARY_PATCH, + &UnorderedHashMap::from([ + ("name".to_string(), RewriteNode::Text(name.to_string())), + ("body".to_string(), RewriteNode::new_modified(body_nodes)), + ]), + )), + origin: module_ast.as_syntax_node().span_without_trivia(db), + }); + + let (code, code_mappings) = builder.build(); + + crate::debug_expand(&format!("LIBRARY PATCH: {name}"), &code); + + return PluginResult { + code: Some(PluginGeneratedFile { + name: name.clone(), + content: code, + aux_data: Some(DynGeneratedFileAuxData::new(ContractAuxData { + name: name.to_string(), + systems: library.systems.clone(), + })), + code_mappings, + diagnostics_note: None, + }), + diagnostics: library.diagnostics, + remove_original_item: true, + }; + } + + PluginResult::default() + } + + pub fn merge_event( + &mut self, + db: &dyn SyntaxGroup, + enum_ast: ast::ItemEnum, + ) -> Vec { + let mut rewrite_nodes = vec![]; + + 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(",\n"); + + rewrite_nodes.push(RewriteNode::interpolate_patched( + " + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + WorldProviderEvent: world_provider_cpt::Event, + $variants$ + } + ", + &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 { + #[flat] + WorldProviderEvent: world_provider_cpt::Event, + } + " + .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(",\n"); + + rewrite_nodes.push(RewriteNode::interpolate_patched( + " + #[storage] + struct Storage { + #[substorage(v0)] + world_provider: world_provider_cpt::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 { + #[substorage(v0)] + world_provider: world_provider_cpt::Storage, + } + " + .to_string(), + )] + } +} diff --git a/crates/dojo/lang/src/attribute_macros/mod.rs b/crates/dojo/lang/src/attribute_macros/mod.rs index aa04576732..4b66e615de 100644 --- a/crates/dojo/lang/src/attribute_macros/mod.rs +++ b/crates/dojo/lang/src/attribute_macros/mod.rs @@ -7,14 +7,17 @@ pub mod contract; pub mod element; pub mod event; pub mod interface; +pub mod library; pub mod model; pub use contract::DojoContract; pub use event::DojoEvent; pub use interface::DojoInterface; +pub use library::DojoLibrary; pub use model::DojoModel; pub const DOJO_CONTRACT_ATTR: &str = "dojo::contract"; +pub const DOJO_LIBRARY_ATTR: &str = "dojo::library"; pub const DOJO_INTERFACE_ATTR: &str = "dojo::interface"; pub const DOJO_MODEL_ATTR: &str = "dojo::model"; pub const DOJO_EVENT_ATTR: &str = "dojo::event"; diff --git a/crates/dojo/lang/src/attribute_macros/patches/library.patch.cairo b/crates/dojo/lang/src/attribute_macros/patches/library.patch.cairo new file mode 100644 index 0000000000..2071789d27 --- /dev/null +++ b/crates/dojo/lang/src/attribute_macros/patches/library.patch.cairo @@ -0,0 +1,30 @@ +#[starknet::contract] +pub mod $name$ { + use dojo::contract::components::world_provider::{world_provider_cpt, IWorldProvider}; + use dojo::contract::ILibrary; + use dojo::meta::IDeployedResource; + + component!(path: world_provider_cpt, storage: world_provider, event: WorldProviderEvent); + + #[abi(embed_v0)] + impl WorldProviderImpl = world_provider_cpt::WorldProviderImpl; + + #[abi(embed_v0)] + pub impl $name$__LibraryImpl of ILibrary {} + + #[abi(embed_v0)] + pub impl $name$__DeployedLibraryImpl of IDeployedResource { + fn dojo_name(self: @ContractState) -> ByteArray { + "$name$" + } + } + + #[generate_trait] + impl $name$InternalImpl of $name$InternalTrait { + fn world(self: @ContractState, namespace: @ByteArray) -> dojo::world::storage::WorldStorage { + dojo::world::WorldStorageTrait::new(self.world_provider.world_dispatcher(), namespace) + } + } + + $body$ +} diff --git a/crates/dojo/lang/src/cairo_plugin.rs b/crates/dojo/lang/src/cairo_plugin.rs index 69ce6b4360..64ce87aa76 100644 --- a/crates/dojo/lang/src/cairo_plugin.rs +++ b/crates/dojo/lang/src/cairo_plugin.rs @@ -9,10 +9,12 @@ use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use super::attribute_macros::{ - DojoContract, DojoEvent, DojoModel, DOJO_CONTRACT_ATTR, DOJO_EVENT_ATTR, DOJO_MODEL_ATTR, + DojoContract, DojoEvent, DojoModel, DOJO_CONTRACT_ATTR, DOJO_EVENT_ATTR, DOJO_LIBRARY_ATTR, + DOJO_MODEL_ATTR, }; use super::derive_macros::{dojo_derive_all, DOJO_INTROSPECT_DERIVE, DOJO_PACKED_DERIVE}; use super::inline_macros::{BytearrayHashMacro, SelectorFromTagMacro}; +use crate::attribute_macros::DojoLibrary; // #[cfg(test)] // #[path = "plugin_test.rs"] @@ -60,6 +62,8 @@ impl MacroPlugin for BuiltinDojoPlugin { ast::ModuleItem::Module(module_ast) => { if module_ast.has_attr(db, DOJO_CONTRACT_ATTR) { DojoContract::from_module(db, module_ast, metadata) + } else if module_ast.has_attr(db, DOJO_LIBRARY_ATTR) { + DojoLibrary::from_module(db, module_ast, metadata) } else { PluginResult::default() } @@ -98,6 +102,7 @@ impl MacroPlugin for BuiltinDojoPlugin { fn declared_attributes(&self) -> Vec { vec![ DOJO_CONTRACT_ATTR.to_string(), + DOJO_LIBRARY_ATTR.to_string(), DOJO_EVENT_ATTR.to_string(), DOJO_MODEL_ATTR.to_string(), "key".to_string(), diff --git a/crates/dojo/world/src/config/profile_config.rs b/crates/dojo/world/src/config/profile_config.rs index 1466555729..cea34512c4 100644 --- a/crates/dojo/world/src/config/profile_config.rs +++ b/crates/dojo/world/src/config/profile_config.rs @@ -30,6 +30,7 @@ pub struct ProfileConfig { pub world: WorldConfig, pub models: Option>, pub contracts: Option>, + pub libraries: Option>, pub events: Option>, pub external_contracts: Option>, pub namespace: NamespaceConfig, @@ -41,6 +42,8 @@ pub struct ProfileConfig { pub owners: Option>>, /// A mapping > of init call arguments to be passed to the contract. pub init_call_args: Option>>, + /// A mapping of libraries + pub lib_versions: Option>, } impl ProfileConfig { @@ -239,6 +242,9 @@ mod tests { [init_call_args] "ns1-actions" = [ "0x1", "0x2" ] + + [lib_versions] + "ns1-lib" = "0.0.0" "#; let config = toml::from_str::(content).unwrap(); @@ -319,6 +325,10 @@ mod tests { vec!["0x1".to_string(), "0x2".to_string()] )])) ); + assert_eq!( + config.lib_versions, + Some(HashMap::from([("ns1-lib".to_string(), "0.0.0".to_string())])) + ) } #[test] diff --git a/crates/dojo/world/src/contracts/abigen/world.rs b/crates/dojo/world/src/contracts/abigen/world.rs index eec0cfc5bf..cf77e255ac 100644 --- a/crates/dojo/world/src/contracts/abigen/world.rs +++ b/crates/dojo/world/src/contracts/abigen/world.rs @@ -390,6 +390,52 @@ impl cainome::cairo_serde::CairoSerde for FieldLayout { } } #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Debug)] +pub struct LibraryRegistered { + pub name: cainome::cairo_serde::ByteArray, + pub namespace: cainome::cairo_serde::ByteArray, + pub class_hash: cainome::cairo_serde::ClassHash, +} +impl cainome::cairo_serde::CairoSerde for LibraryRegistered { + type RustType = Self; + const SERIALIZED_SIZE: std::option::Option = None; + #[inline] + fn cairo_serialized_size(__rust: &Self::RustType) -> usize { + let mut __size = 0; + __size += cainome::cairo_serde::ByteArray::cairo_serialized_size(&__rust.name); + __size += cainome::cairo_serde::ByteArray::cairo_serialized_size(&__rust.namespace); + __size += cainome::cairo_serde::ClassHash::cairo_serialized_size(&__rust.class_hash); + __size + } + fn cairo_serialize(__rust: &Self::RustType) -> Vec { + let mut __out: Vec = vec![]; + __out.extend(cainome::cairo_serde::ByteArray::cairo_serialize(&__rust.name)); + __out.extend(cainome::cairo_serde::ByteArray::cairo_serialize(&__rust.namespace)); + __out.extend(cainome::cairo_serde::ClassHash::cairo_serialize(&__rust.class_hash)); + __out + } + fn cairo_deserialize( + __felts: &[starknet::core::types::Felt], + __offset: usize, + ) -> cainome::cairo_serde::Result { + let mut __offset = __offset; + let name = cainome::cairo_serde::ByteArray::cairo_deserialize(__felts, __offset)?; + __offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&name); + let namespace = cainome::cairo_serde::ByteArray::cairo_deserialize(__felts, __offset)?; + __offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&namespace); + let class_hash = cainome::cairo_serde::ClassHash::cairo_deserialize(__felts, __offset)?; + __offset += cainome::cairo_serde::ClassHash::cairo_serialized_size(&class_hash); + Ok(LibraryRegistered { name, namespace, class_hash }) + } +} +impl LibraryRegistered { + pub fn event_selector() -> starknet::core::types::Felt { + starknet::core::utils::get_selector_from_name("LibraryRegistered").unwrap() + } + pub fn event_name() -> &'static str { + "LibraryRegistered" + } +} +#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Debug)] pub struct MetadataUpdate { pub resource: starknet::core::types::Felt, pub uri: cainome::cairo_serde::ByteArray, @@ -988,6 +1034,7 @@ pub enum Event { EventUpgraded(EventUpgraded), ContractUpgraded(ContractUpgraded), ContractInitialized(ContractInitialized), + LibraryRegistered(LibraryRegistered), EventEmitted(EventEmitted), MetadataUpdate(MetadataUpdate), StoreSetRecord(StoreSetRecord), @@ -1013,6 +1060,7 @@ impl cainome::cairo_serde::CairoSerde for Event { Event::EventUpgraded(val) => EventUpgraded::cairo_serialized_size(val) + 1, Event::ContractUpgraded(val) => ContractUpgraded::cairo_serialized_size(val) + 1, Event::ContractInitialized(val) => ContractInitialized::cairo_serialized_size(val) + 1, + Event::LibraryRegistered(val) => LibraryRegistered::cairo_serialized_size(val) + 1, Event::EventEmitted(val) => EventEmitted::cairo_serialized_size(val) + 1, Event::MetadataUpdate(val) => MetadataUpdate::cairo_serialized_size(val) + 1, Event::StoreSetRecord(val) => StoreSetRecord::cairo_serialized_size(val) + 1, @@ -1086,51 +1134,57 @@ impl cainome::cairo_serde::CairoSerde for Event { temp.extend(ContractInitialized::cairo_serialize(val)); temp } - Event::EventEmitted(val) => { + Event::LibraryRegistered(val) => { let mut temp = vec![]; temp.extend(usize::cairo_serialize(&10usize)); + temp.extend(LibraryRegistered::cairo_serialize(val)); + temp + } + Event::EventEmitted(val) => { + let mut temp = vec![]; + temp.extend(usize::cairo_serialize(&11usize)); temp.extend(EventEmitted::cairo_serialize(val)); temp } Event::MetadataUpdate(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&11usize)); + temp.extend(usize::cairo_serialize(&12usize)); temp.extend(MetadataUpdate::cairo_serialize(val)); temp } Event::StoreSetRecord(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&12usize)); + temp.extend(usize::cairo_serialize(&13usize)); temp.extend(StoreSetRecord::cairo_serialize(val)); temp } Event::StoreUpdateRecord(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&13usize)); + temp.extend(usize::cairo_serialize(&14usize)); temp.extend(StoreUpdateRecord::cairo_serialize(val)); temp } Event::StoreUpdateMember(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&14usize)); + temp.extend(usize::cairo_serialize(&15usize)); temp.extend(StoreUpdateMember::cairo_serialize(val)); temp } Event::StoreDelRecord(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&15usize)); + temp.extend(usize::cairo_serialize(&16usize)); temp.extend(StoreDelRecord::cairo_serialize(val)); temp } Event::WriterUpdated(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&16usize)); + temp.extend(usize::cairo_serialize(&17usize)); temp.extend(WriterUpdated::cairo_serialize(val)); temp } Event::OwnerUpdated(val) => { let mut temp = vec![]; - temp.extend(usize::cairo_serialize(&17usize)); + temp.extend(usize::cairo_serialize(&18usize)); temp.extend(OwnerUpdated::cairo_serialize(val)); temp } @@ -1180,30 +1234,34 @@ impl cainome::cairo_serde::CairoSerde for Event { __felts, __offset + 1, )?)), - 10usize => { + 10usize => Ok(Event::LibraryRegistered(LibraryRegistered::cairo_deserialize( + __felts, + __offset + 1, + )?)), + 11usize => { Ok(Event::EventEmitted(EventEmitted::cairo_deserialize(__felts, __offset + 1)?)) } - 11usize => { + 12usize => { Ok(Event::MetadataUpdate(MetadataUpdate::cairo_deserialize(__felts, __offset + 1)?)) } - 12usize => { + 13usize => { Ok(Event::StoreSetRecord(StoreSetRecord::cairo_deserialize(__felts, __offset + 1)?)) } - 13usize => Ok(Event::StoreUpdateRecord(StoreUpdateRecord::cairo_deserialize( + 14usize => Ok(Event::StoreUpdateRecord(StoreUpdateRecord::cairo_deserialize( __felts, __offset + 1, )?)), - 14usize => Ok(Event::StoreUpdateMember(StoreUpdateMember::cairo_deserialize( + 15usize => Ok(Event::StoreUpdateMember(StoreUpdateMember::cairo_deserialize( __felts, __offset + 1, )?)), - 15usize => { + 16usize => { Ok(Event::StoreDelRecord(StoreDelRecord::cairo_deserialize(__felts, __offset + 1)?)) } - 16usize => { + 17usize => { Ok(Event::WriterUpdated(WriterUpdated::cairo_deserialize(__felts, __offset + 1)?)) } - 17usize => { + 18usize => { Ok(Event::OwnerUpdated(OwnerUpdated::cairo_deserialize(__felts, __offset + 1)?)) } _ => { @@ -1706,6 +1764,50 @@ impl TryFrom<&starknet::core::types::EmittedEvent> for Event { return Ok(Event::ContractInitialized(ContractInitialized { selector, init_calldata })); } let selector = event.keys[0]; + if selector + == starknet::core::utils::get_selector_from_name("LibraryRegistered") + .unwrap_or_else(|_| panic!("Invalid selector for {}", "LibraryRegistered")) + { + let mut key_offset = 0 + 1; + let mut data_offset = 0; + let name = + match cainome::cairo_serde::ByteArray::cairo_deserialize(&event.keys, key_offset) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "name", "LibraryRegistered", e + )); + } + }; + key_offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&name); + let namespace = + match cainome::cairo_serde::ByteArray::cairo_deserialize(&event.keys, key_offset) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "namespace", "LibraryRegistered", e + )); + } + }; + key_offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&namespace); + let class_hash = match cainome::cairo_serde::ClassHash::cairo_deserialize( + &event.data, + data_offset, + ) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "class_hash", "LibraryRegistered", e + )); + } + }; + data_offset += cainome::cairo_serde::ClassHash::cairo_serialized_size(&class_hash); + return Ok(Event::LibraryRegistered(LibraryRegistered { name, namespace, class_hash })); + } + let selector = event.keys[0]; if selector == starknet::core::utils::get_selector_from_name("EventEmitted") .unwrap_or_else(|_| panic!("Invalid selector for {}", "EventEmitted")) @@ -2586,6 +2688,50 @@ impl TryFrom<&starknet::core::types::Event> for Event { return Ok(Event::ContractInitialized(ContractInitialized { selector, init_calldata })); } let selector = event.keys[0]; + if selector + == starknet::core::utils::get_selector_from_name("LibraryRegistered") + .unwrap_or_else(|_| panic!("Invalid selector for {}", "LibraryRegistered")) + { + let mut key_offset = 0 + 1; + let mut data_offset = 0; + let name = + match cainome::cairo_serde::ByteArray::cairo_deserialize(&event.keys, key_offset) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "name", "LibraryRegistered", e + )); + } + }; + key_offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&name); + let namespace = + match cainome::cairo_serde::ByteArray::cairo_deserialize(&event.keys, key_offset) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "namespace", "LibraryRegistered", e + )); + } + }; + key_offset += cainome::cairo_serde::ByteArray::cairo_serialized_size(&namespace); + let class_hash = match cainome::cairo_serde::ClassHash::cairo_deserialize( + &event.data, + data_offset, + ) { + Ok(v) => v, + Err(e) => { + return Err(format!( + "Could not deserialize field {} for {}: {:?}", + "class_hash", "LibraryRegistered", e + )); + } + }; + data_offset += cainome::cairo_serde::ClassHash::cairo_serialized_size(&class_hash); + return Ok(Event::LibraryRegistered(LibraryRegistered { name, namespace, class_hash })); + } + let selector = event.keys[0]; if selector == starknet::core::utils::get_selector_from_name("EventEmitted") .unwrap_or_else(|_| panic!("Invalid selector for {}", "EventEmitted")) @@ -3150,6 +3296,7 @@ pub enum Resource { Namespace(cainome::cairo_serde::ByteArray), World, Unregistered, + Library((cainome::cairo_serde::ClassHash, starknet::core::types::Felt)), } impl cainome::cairo_serde::CairoSerde for Resource { type RustType = Self; @@ -3177,6 +3324,11 @@ impl cainome::cairo_serde::CairoSerde for Resource { } Resource::World => 1, Resource::Unregistered => 1, + Resource::Library(val) => <( + cainome::cairo_serde::ClassHash, + starknet::core::types::Felt, + )>::cairo_serialized_size(val) + + 1, _ => 0, } } @@ -3217,6 +3369,15 @@ impl cainome::cairo_serde::CairoSerde for Resource { } Resource::World => usize::cairo_serialize(&4usize), Resource::Unregistered => usize::cairo_serialize(&5usize), + Resource::Library(val) => { + let mut temp = vec![]; + temp.extend(usize::cairo_serialize(&6usize)); + temp.extend(<( + cainome::cairo_serde::ClassHash, + starknet::core::types::Felt, + )>::cairo_serialize(val)); + temp + } _ => vec![], } } @@ -3247,6 +3408,12 @@ impl cainome::cairo_serde::CairoSerde for Resource { )?)), 4usize => Ok(Resource::World), 5usize => Ok(Resource::Unregistered), + 6usize => { + Ok(Resource::Library(<( + cainome::cairo_serde::ClassHash, + starknet::core::types::Felt, + )>::cairo_deserialize(__felts, __offset + 1)?)) + } _ => { return Err(cainome::cairo_serde::Error::Deserialize(format!( "Index not handle for enum {}", @@ -3702,6 +3869,49 @@ impl WorldContract { } #[allow(clippy::ptr_arg)] #[allow(clippy::too_many_arguments)] + pub fn register_library_getcall( + &self, + namespace: &cainome::cairo_serde::ByteArray, + class_hash: &cainome::cairo_serde::ClassHash, + name: &cainome::cairo_serde::ByteArray, + version: &cainome::cairo_serde::ByteArray, + ) -> starknet::core::types::Call { + use cainome::cairo_serde::CairoSerde; + let mut __calldata = vec![]; + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(namespace)); + __calldata.extend(cainome::cairo_serde::ClassHash::cairo_serialize(class_hash)); + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(name)); + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(version)); + starknet::core::types::Call { + to: self.address, + selector: starknet::macros::selector!("register_library"), + calldata: __calldata, + } + } + #[allow(clippy::ptr_arg)] + #[allow(clippy::too_many_arguments)] + pub fn register_library( + &self, + namespace: &cainome::cairo_serde::ByteArray, + class_hash: &cainome::cairo_serde::ClassHash, + name: &cainome::cairo_serde::ByteArray, + version: &cainome::cairo_serde::ByteArray, + ) -> starknet::accounts::ExecutionV1 { + use cainome::cairo_serde::CairoSerde; + let mut __calldata = vec![]; + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(namespace)); + __calldata.extend(cainome::cairo_serde::ClassHash::cairo_serialize(class_hash)); + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(name)); + __calldata.extend(cainome::cairo_serde::ByteArray::cairo_serialize(version)); + let __call = starknet::core::types::Call { + to: self.address, + selector: starknet::macros::selector!("register_library"), + calldata: __calldata, + }; + self.account.execute_v1(vec![__call]) + } + #[allow(clippy::ptr_arg)] + #[allow(clippy::too_many_arguments)] pub fn register_model_getcall( &self, namespace: &cainome::cairo_serde::ByteArray, diff --git a/crates/dojo/world/src/contracts/contract_info.rs b/crates/dojo/world/src/contracts/contract_info.rs index 5f63e1c710..dc0394e34a 100644 --- a/crates/dojo/world/src/contracts/contract_info.rs +++ b/crates/dojo/world/src/contracts/contract_info.rs @@ -152,7 +152,7 @@ mod tests { use starknet::macros::felt; use super::*; - use crate::diff::{DojoContract, DojoModel, WorldContract}; + use crate::diff::{DojoContract, DojoLibrary, DojoModel, WorldContract}; use crate::local::{CommonLocalInfo, ContractLocal, ExternalContractLocal, WorldLocal}; #[test] @@ -175,6 +175,14 @@ mod tests { systems: vec!["system_1".to_string()], selector: felt!("0x3333"), }], + libraries: vec![DojoLibrary { + class_hash: felt!("0x9999"), + abi: vec![], + tag: "ns-test_library".to_string(), + systems: vec!["system_1".to_string()], + selector: felt!("0x999"), + version: "0.0.0".to_string(), + }], models: vec![DojoModel { tag: "ns-test_model".to_string(), class_hash: felt!("0x4444"), diff --git a/crates/dojo/world/src/diff/compare.rs b/crates/dojo/world/src/diff/compare.rs index ce2d3f0986..63d4d0017b 100644 --- a/crates/dojo/world/src/diff/compare.rs +++ b/crates/dojo/world/src/diff/compare.rs @@ -3,7 +3,9 @@ //! The point of view is the local one. use super::ResourceDiff; -use crate::local::{ContractLocal, EventLocal, ModelLocal, NamespaceLocal, ResourceLocal}; +use crate::local::{ + ContractLocal, EventLocal, LibraryLocal, ModelLocal, NamespaceLocal, ResourceLocal, +}; use crate::remote::ResourceRemote; /// A trait to compare a local resource with a remote one. @@ -26,6 +28,20 @@ impl ComparableResource for ContractLocal { } } +impl ComparableResource for LibraryLocal { + fn compare(self, remote: ResourceRemote) -> ResourceDiff { + let remote_contract = remote.as_library_or_panic(); + + if self.common.class_hash == remote_contract.common.current_class_hash() + && self.version == remote_contract.version + { + ResourceDiff::Synced(ResourceLocal::Library(self), remote) + } else { + ResourceDiff::Created(ResourceLocal::Library(self)) + } + } +} + impl ComparableResource for ModelLocal { fn compare(self, remote: ResourceRemote) -> ResourceDiff { let remote_model = remote.as_model_or_panic(); @@ -69,6 +85,7 @@ impl ComparableResource for ResourceLocal { ResourceLocal::Model(model) => model.compare(remote), ResourceLocal::Event(event) => event.compare(remote), ResourceLocal::Namespace(ns) => ns.compare(remote), + ResourceLocal::Library(library) => library.compare(remote), } } } diff --git a/crates/dojo/world/src/diff/manifest.rs b/crates/dojo/world/src/diff/manifest.rs index a977780e9b..3d9e39ebc1 100644 --- a/crates/dojo/world/src/diff/manifest.rs +++ b/crates/dojo/world/src/diff/manifest.rs @@ -16,6 +16,7 @@ use crate::ResourceType; pub struct Manifest { pub world: WorldContract, pub contracts: Vec, + pub libraries: Vec, pub models: Vec, pub events: Vec, pub external_contracts: Vec, @@ -63,6 +64,25 @@ pub struct DojoContract { pub systems: Vec, } +#[serde_as] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct DojoLibrary { + /// Class hash of the contract. + #[serde_as(as = "UfeHex")] + pub class_hash: Felt, + /// ABI of the contract. + pub abi: Vec, + /// Tag of the contract. + pub tag: String, + /// Selector of the contract. + #[serde_as(as = "UfeHex")] + pub selector: Felt, + /// Systems of the library. + pub systems: Vec, + /// Version of the library + pub version: String, +} + #[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize)] #[cfg_attr(test, derive(PartialEq))] @@ -141,6 +161,7 @@ impl Manifest { let mut contracts = Vec::new(); let mut models = Vec::new(); let mut events = Vec::new(); + let mut libraries = Vec::new(); let mut external_contracts = Vec::new(); for resource in diff.resources.values() { @@ -152,6 +173,9 @@ impl Manifest { ResourceType::Contract => { contracts.push(resource_diff_to_dojo_contract(diff, resource)) } + ResourceType::Library => { + libraries.push(resource_diff_to_dojo_library(diff, resource)) + } ResourceType::Model => models.push(resource_diff_to_dojo_model(resource)), ResourceType::Event => events.push(resource_diff_to_dojo_event(resource)), ResourceType::Namespace => {} @@ -177,11 +201,12 @@ impl Manifest { // Keep order to ensure deterministic output. contracts.sort_by_key(|c| c.tag.clone()); + libraries.sort_by_key(|c| c.tag.clone()); models.sort_by_key(|m| m.tag.clone()); events.sort_by_key(|e| e.tag.clone()); external_contracts.sort_by_key(|c| c.instance_name.clone()); - Self { world, contracts, models, events, external_contracts } + Self { world, contracts, models, events, libraries, external_contracts } } pub fn get_contract_address(&self, tag: &str) -> Option { @@ -223,6 +248,42 @@ fn resource_diff_to_dojo_contract(diff: &WorldDiff, resource: &ResourceDiff) -> _ => unreachable!(), } } +fn resource_diff_to_dojo_library(diff: &WorldDiff, resource: &ResourceDiff) -> DojoLibrary { + let tag = resource.tag(); + + let version = diff + .profile_config + .lib_versions + .as_ref() + .expect("expected lib_versions") + .get(&tag) + .expect("library mush have a version"); + + let tag = format!("{}_v{}", tag, version); + + match &resource { + ResourceDiff::Created(ResourceLocal::Library(l)) => DojoLibrary { + class_hash: l.common.class_hash, + abi: l.common.class.abi.clone(), + tag, + systems: l.systems.clone(), + selector: resource.dojo_selector(), + version: version.clone(), + }, + ResourceDiff::Updated(ResourceLocal::Library(l), ResourceRemote::Library(_r)) + | ResourceDiff::Synced(ResourceLocal::Library(l), ResourceRemote::Library(_r)) => { + DojoLibrary { + class_hash: l.common.class_hash, + abi: l.common.class.abi.clone(), + tag, + systems: l.systems.clone(), + selector: resource.dojo_selector(), + version: version.clone(), + } + } + _ => unreachable!(), + } +} fn resource_diff_to_dojo_model(resource: &ResourceDiff) -> DojoModel { let tag = resource.tag(); diff --git a/crates/dojo/world/src/lib.rs b/crates/dojo/world/src/lib.rs index 0c61344148..1c8591c7b3 100644 --- a/crates/dojo/world/src/lib.rs +++ b/crates/dojo/world/src/lib.rs @@ -27,4 +27,5 @@ pub enum ResourceType { Model, Event, StarknetContract, + Library, } diff --git a/crates/dojo/world/src/local/artifact_to_local.rs b/crates/dojo/world/src/local/artifact_to_local.rs index 69509e19c0..c11171865a 100644 --- a/crates/dojo/world/src/local/artifact_to_local.rs +++ b/crates/dojo/world/src/local/artifact_to_local.rs @@ -14,7 +14,7 @@ use starknet::core::types::contract::{ use starknet::core::types::Felt; use starknet::core::utils as snutils; use starknet_crypto::poseidon_hash_many; -use tracing::trace; +use tracing::{trace, warn}; use super::*; use crate::config::calldata_decoder::decode_calldata; @@ -22,6 +22,7 @@ use crate::config::ProfileConfig; const WORLD_INTF: &str = "dojo::world::iworld::IWorld"; const CONTRACT_INTF: &str = "dojo::contract::interface::IContract"; +const LIBRARY_INTF: &str = "dojo::contract::interface::ILibrary"; const MODEL_INTF: &str = "dojo::model::interface::IModel"; const EVENT_INTF: &str = "dojo::event::interface::IEvent"; @@ -118,6 +119,52 @@ impl WorldLocal { dojo_resource_found = true; break; } + ResourceType::Library(name) => { + let namespaces = profile_config.namespace.get_namespaces(&name); + + let systems = systems_from_abi(&abi); + + let mut added_resources = false; + for ns in namespaces { + if let Some(version) = profile_config.lib_versions.as_ref() { + if let Some(v) = version.get(&format!("{}-{}", ns, name)) { + trace!( + name, + namespace = ns, + version = v, + "Adding local library from artifact." + ); + + let resource = ResourceLocal::Library(LibraryLocal { + common: CommonLocalInfo { + namespace: ns, + name: name.clone(), + class: sierra.clone(), + casm_class: casm_class.clone(), + class_hash, + casm_class_hash, + }, + systems: systems.clone(), + version: v.clone(), + }); + + resources.push(resource); + added_resources = true; + } + } + } + + if !added_resources { + warn!( + "No library version found for library `{}` in the Dojo \ + profile config. Consider adding a `[lib_versions]` entry \ + with the version.", + name + ); + } + + break; + } ResourceType::Model(name) => { let namespaces = profile_config.namespace.get_namespaces(&name); @@ -325,6 +372,7 @@ fn casm_class_hash_from_sierra_file>(path: P) -> Result { enum ResourceType { World, Contract(String), + Library(String), Model(String), Event(String), Other, @@ -336,6 +384,8 @@ fn identify_resource_type(implem: &AbiImpl) -> ResourceType { ResourceType::World } else if implem.interface_name == CONTRACT_INTF { ResourceType::Contract(name_from_impl(&implem.name)) + } else if implem.interface_name == LIBRARY_INTF { + ResourceType::Library(name_from_impl(&implem.name)) } else if implem.interface_name == MODEL_INTF { ResourceType::Model(name_from_impl(&implem.name)) } else if implem.interface_name == EVENT_INTF { @@ -458,6 +508,7 @@ mod tests { "register_event", "register_model", "register_contract", + "register_library", "init_contract", "upgrade_event", "upgrade_model", diff --git a/crates/dojo/world/src/local/resource.rs b/crates/dojo/world/src/local/resource.rs index 8b2b7b898d..996feae4cb 100644 --- a/crates/dojo/world/src/local/resource.rs +++ b/crates/dojo/world/src/local/resource.rs @@ -11,6 +11,7 @@ pub enum ResourceLocal { Contract(ContractLocal), Model(ModelLocal), Event(EventLocal), + Library(LibraryLocal), } /// Common information about a world's resource. @@ -38,6 +39,16 @@ pub struct ContractLocal { pub systems: Vec, } +#[derive(Debug, Clone)] +pub struct LibraryLocal { + /// Common information about the resource. + pub common: CommonLocalInfo, + /// The systems of the library. + pub systems: Vec, + /// The version of the library + pub version: String, +} + #[derive(Debug, Clone)] pub struct ModelLocal { /// Common information about the resource. @@ -79,6 +90,7 @@ impl ResourceLocal { ResourceLocal::Model(m) => m.common.name.clone(), ResourceLocal::Event(e) => e.common.name.clone(), ResourceLocal::Namespace(n) => n.name.clone(), + ResourceLocal::Library(l) => l.common.name.clone(), } } @@ -89,6 +101,7 @@ impl ResourceLocal { ResourceLocal::Contract(c) => c.common.namespace.clone(), ResourceLocal::Model(m) => m.common.namespace.clone(), ResourceLocal::Event(e) => e.common.namespace.clone(), + ResourceLocal::Library(l) => l.common.namespace.clone(), } } @@ -98,6 +111,7 @@ impl ResourceLocal { ResourceLocal::Contract(c) => c.common.class_hash, ResourceLocal::Model(m) => m.common.class_hash, ResourceLocal::Event(e) => e.common.class_hash, + ResourceLocal::Library(l) => l.common.class_hash, _ => Felt::ZERO, } } @@ -108,6 +122,7 @@ impl ResourceLocal { ResourceLocal::Contract(c) => c.common.class.abi.clone(), ResourceLocal::Model(m) => m.common.class.abi.clone(), ResourceLocal::Event(e) => e.common.class.abi.clone(), + ResourceLocal::Library(l) => l.common.class.abi.clone(), _ => Vec::new(), } } @@ -146,6 +161,7 @@ impl ResourceLocal { ResourceLocal::Model(_) => ResourceType::Model, ResourceLocal::Event(_) => ResourceType::Event, ResourceLocal::Namespace(_) => ResourceType::Namespace, + ResourceLocal::Library(_) => ResourceType::Library, } } @@ -156,6 +172,7 @@ impl ResourceLocal { ResourceLocal::Model(m) => &m.common, ResourceLocal::Event(e) => &e.common, ResourceLocal::Namespace(_) => panic!("Namespace has no common info."), + ResourceLocal::Library(l) => &l.common, } } } diff --git a/crates/dojo/world/src/remote/events_to_remote.rs b/crates/dojo/world/src/remote/events_to_remote.rs index 681561f221..a610f222e3 100644 --- a/crates/dojo/world/src/remote/events_to_remote.rs +++ b/crates/dojo/world/src/remote/events_to_remote.rs @@ -18,7 +18,9 @@ use super::permissions::PermissionsUpdateable; use super::{ResourceRemote, WorldRemote}; use crate::constants::WORLD; use crate::contracts::abigen::world::{self, Event as WorldEvent}; -use crate::remote::{CommonRemoteInfo, ContractRemote, EventRemote, ModelRemote, NamespaceRemote}; +use crate::remote::{ + CommonRemoteInfo, ContractRemote, EventRemote, LibraryRemote, ModelRemote, NamespaceRemote, +}; impl WorldRemote { /// Fetch the events from the world and convert them to remote resources. @@ -64,6 +66,7 @@ impl WorldRemote { world::WriterUpdated::event_selector(), world::OwnerUpdated::event_selector(), world::MetadataUpdate::event_selector(), + world::LibraryRegistered::event_selector(), ]]; let filter = EventFilter { @@ -283,6 +286,35 @@ impl WorldRemote { self.add_resource(r); } + WorldEvent::LibraryRegistered(e) => { + let namespace = e.namespace.to_string()?; + + if !is_whitelisted(whitelisted_namespaces, &namespace) { + debug!( + namespace, + contract = e.name.to_string()?, + "Library's namespace not whitelisted." + ); + + return Ok(()); + } + + let full_name = e.name.to_string().unwrap(); + let version = full_name.split(&"_v").last().expect("expected version"); + let name = full_name.replace(&format!("_v{}", version), ""); + let r = ResourceRemote::Library(LibraryRemote { + common: CommonRemoteInfo::new( + e.class_hash.into(), + &namespace, + &name.to_string(), + Felt::ZERO, + ), + version: version.to_string(), + }); + trace!(?r, "Library registered."); + + self.add_resource(r); + } WorldEvent::ModelUpgraded(e) => { let resource = if let Some(resource) = self.resources.get_mut(&e.selector) { resource diff --git a/crates/dojo/world/src/remote/permissions.rs b/crates/dojo/world/src/remote/permissions.rs index 8ac6ecafc8..3e831459b6 100644 --- a/crates/dojo/world/src/remote/permissions.rs +++ b/crates/dojo/world/src/remote/permissions.rs @@ -97,6 +97,10 @@ impl PermissionsUpdateable for ResourceRemote { ResourceRemote::Namespace(namespace) => { namespace.update_writer(contract_address, is_writer) } + ResourceRemote::Library(_) => { + // ? + unreachable!() + } } } @@ -108,6 +112,10 @@ impl PermissionsUpdateable for ResourceRemote { ResourceRemote::Namespace(namespace) => { namespace.update_owner(contract_address, is_owner) } + ResourceRemote::Library(_) => { + // ? + unreachable!() + } } } } diff --git a/crates/dojo/world/src/remote/resource.rs b/crates/dojo/world/src/remote/resource.rs index 9a8c584079..ea1f4f14aa 100644 --- a/crates/dojo/world/src/remote/resource.rs +++ b/crates/dojo/world/src/remote/resource.rs @@ -13,6 +13,7 @@ pub enum ResourceRemote { Contract(ContractRemote), Model(ModelRemote), Event(EventRemote), + Library(LibraryRemote), // TODO: add starknet contract remote. Sozo needs a way to keep track of the address of this // contract once deployed. } @@ -46,6 +47,14 @@ pub struct ContractRemote { pub is_initialized: bool, } +#[derive(Debug, Clone)] +pub struct LibraryRemote { + /// Common information about the resource. + pub common: CommonRemoteInfo, + /// Version + pub version: String, +} + #[derive(Debug, Clone)] pub struct ModelRemote { /// Common information about the resource. @@ -116,6 +125,13 @@ impl ContractRemote { } } +impl LibraryRemote { + /// The dojo selector of the resource. + pub fn dojo_selector(&self) -> DojoSelector { + self.common.dojo_selector() + } +} + impl ModelRemote { /// The dojo selector of the resource. pub fn dojo_selector(&self) -> DojoSelector { @@ -139,6 +155,7 @@ impl ResourceRemote { ResourceRemote::Contract(contract) => contract.dojo_selector(), ResourceRemote::Model(model) => model.dojo_selector(), ResourceRemote::Event(event) => event.dojo_selector(), + ResourceRemote::Library(library) => library.dojo_selector(), } } /// The name of the resource. @@ -148,6 +165,7 @@ impl ResourceRemote { ResourceRemote::Model(m) => m.common.name.clone(), ResourceRemote::Event(e) => e.common.name.clone(), ResourceRemote::Namespace(ns) => ns.name.clone(), + ResourceRemote::Library(l) => l.common.name.clone(), } } @@ -158,6 +176,7 @@ impl ResourceRemote { ResourceRemote::Model(m) => m.common.namespace.clone(), ResourceRemote::Event(e) => e.common.namespace.clone(), ResourceRemote::Namespace(ns) => ns.name.clone(), + ResourceRemote::Library(l) => l.common.namespace.clone(), } } @@ -173,6 +192,7 @@ impl ResourceRemote { ResourceRemote::Model(m) => m.common.address, ResourceRemote::Event(e) => e.common.address, ResourceRemote::Namespace(_) => Felt::ZERO, + ResourceRemote::Library(_) => Felt::ZERO, } } @@ -183,6 +203,7 @@ impl ResourceRemote { ResourceRemote::Model(m) => m.common.metadata_hash = hash, ResourceRemote::Event(e) => e.common.metadata_hash = hash, ResourceRemote::Namespace(_) => {} + ResourceRemote::Library(l) => l.common.metadata_hash = hash, } } @@ -193,6 +214,7 @@ impl ResourceRemote { ResourceRemote::Model(m) => m.common.metadata_hash, ResourceRemote::Event(e) => e.common.metadata_hash, ResourceRemote::Namespace(_) => Felt::ZERO, + ResourceRemote::Library(l) => l.common.metadata_hash, } } @@ -203,6 +225,7 @@ impl ResourceRemote { ResourceRemote::Contract(contract) => contract.common.push_class_hash(class_hash), ResourceRemote::Model(model) => model.common.push_class_hash(class_hash), ResourceRemote::Event(event) => event.common.push_class_hash(class_hash), + ResourceRemote::Library(library) => library.common.push_class_hash(class_hash), } } @@ -213,6 +236,7 @@ impl ResourceRemote { ResourceRemote::Model(model) => model.common.current_class_hash(), ResourceRemote::Event(event) => event.common.current_class_hash(), ResourceRemote::Namespace(_) => Felt::ZERO, + ResourceRemote::Library(library) => library.common.current_class_hash(), } } @@ -225,6 +249,9 @@ impl ResourceRemote { ResourceRemote::Model(model) => (self.dojo_selector(), model.common.writers.clone()), ResourceRemote::Event(event) => (self.dojo_selector(), event.common.writers.clone()), ResourceRemote::Namespace(ns) => (self.dojo_selector(), ns.writers.clone()), + ResourceRemote::Library(library) => { + (self.dojo_selector(), library.common.writers.clone()) + } } } @@ -237,6 +264,9 @@ impl ResourceRemote { ResourceRemote::Model(model) => (self.dojo_selector(), model.common.owners.clone()), ResourceRemote::Event(event) => (self.dojo_selector(), event.common.owners.clone()), ResourceRemote::Namespace(ns) => (self.dojo_selector(), ns.owners.clone()), + ResourceRemote::Library(library) => { + (self.dojo_selector(), library.common.owners.clone()) + } } } @@ -247,6 +277,7 @@ impl ResourceRemote { ResourceRemote::Model(_) => ResourceType::Model, ResourceRemote::Event(_) => ResourceType::Event, ResourceRemote::Namespace(_) => ResourceType::Namespace, + ResourceRemote::Library(_) => ResourceType::Library, } } @@ -266,6 +297,14 @@ impl ResourceRemote { } } + /// Get the library remote if the resource is a library, otherwise panic. + pub fn as_library_or_panic(&self) -> &LibraryRemote { + match self { + ResourceRemote::Library(library) => library, + _ => panic!("Resource is expected to be a library: {:?}.", self), + } + } + /// Get the model remote if the resource is a model, otherwise panic. pub fn as_model_or_panic(&self) -> &ModelRemote { match self { diff --git a/crates/sozo/ops/src/migrate/mod.rs b/crates/sozo/ops/src/migrate/mod.rs index 2190a99df0..bb262e34be 100644 --- a/crates/sozo/ops/src/migrate/mod.rs +++ b/crates/sozo/ops/src/migrate/mod.rs @@ -151,6 +151,12 @@ where invoker.extend_calls(calls); } + // libraries + if let Some(configs) = &self.diff.profile_config.libraries { + let calls = self.upload_metadata_from_resource_config(service, configs).await?; + invoker.extend_calls(calls); + } + // models if let Some(configs) = &self.diff.profile_config.models { let calls = self.upload_metadata_from_resource_config(service, configs).await?; @@ -494,6 +500,17 @@ where invoker.extend_calls(contract_calls); classes.extend(contract_classes); } + ResourceType::Library => { + let (library_calls, library_classes) = + self.libraries_calls_classes(resource).await?; + + if !library_calls.is_empty() { + n_resources += 1; + } + + invoker.extend_calls(library_calls); + classes.extend(library_classes); + } ResourceType::Model => { let (model_calls, model_classes) = self.models_calls_classes(resource).await?; @@ -697,6 +714,57 @@ where Ok((calls, classes)) } + /// Gathers the calls required to sync the libraries' classes to be declared. + /// + /// Returns a tuple of calls and (casm_class_hash, class) to be declared. + async fn libraries_calls_classes( + &self, + resource: &ResourceDiff, + ) -> Result<(Vec, HashMap), MigrationError> { + let mut calls = vec![]; + let mut classes = HashMap::new(); + + let namespace = resource.namespace(); + let ns_bytearray = ByteArray::from_string(&namespace)?; + let tag = resource.tag(); + + if let ResourceDiff::Created(ResourceLocal::Library(library)) = resource { + trace!( + namespace, + name = library.common.name, + class_hash = format!("{:#066x}", library.common.class_hash), + "Registering library." + ); + + let casm_class_hash = library.common.casm_class_hash; + let class = library.common.class.clone().flatten()?; + + classes.insert( + casm_class_hash, + LabeledClass { label: tag.clone(), casm_class_hash, class }, + ); + + let name = ByteArray::from_string(&library.common.name).unwrap(); + let version = ByteArray::from_string(&library.version).unwrap(); + calls.push(self.world.register_library_getcall( + &ns_bytearray, + &ClassHash(library.common.class_hash), + &name, + &version, + )); + } + + if let ResourceDiff::Updated( + ResourceLocal::Library(_library_local), + ResourceRemote::Library(_library_remote), + ) = resource + { + panic!("libraries cannot be updated!") + } + + Ok((calls, classes)) + } + /// Returns the calls required to sync the models and gather the classes to be declared. /// /// Returns a tuple of calls and (casm_class_hash, class) to be declared. diff --git a/examples/benchmark/Scarb.lock b/examples/benchmark/Scarb.lock index e0066256cf..38a6a1cb6c 100644 --- a/examples/benchmark/Scarb.lock +++ b/examples/benchmark/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.12" +version = "1.1.2" dependencies = [ "dojo_plugin", ] diff --git a/examples/spawn-and-move/dojo_dev.toml b/examples/spawn-and-move/dojo_dev.toml index 6c60434fe3..586012d84b 100644 --- a/examples/spawn-and-move/dojo_dev.toml +++ b/examples/spawn-and-move/dojo_dev.toml @@ -57,6 +57,9 @@ contract_name = "Saloon" constructor_data = [] salt = "1" +[lib_versions] +"ns-simple_math" = "0_1_0" + [namespace] default = "ns" @@ -65,7 +68,7 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x2af9427c5a277474c079a1283c880ee8a6f0f8fbf73ce969c08d88befec1bba" private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2" +world_address = "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221" ipfs_config.url = "https://ipfs.infura.io:5001" ipfs_config.username = "2EBrzr7ZASQZKH32sl2xWauXPSA" ipfs_config.password = "12290b883db9138a8ae3363b6739d220" diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 55e30e1ca6..de2f5c41a8 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -1,7 +1,7 @@ { "world": { - "class_hash": "0x4b767fda48530353c2e44e4b9433f4d75c6c9974aaff43ecc00de9455eb71b8", - "address": "0x5baea2d83fc19bae80dc5d4626a27b2b2d5012822cd862c56ed7007eb92eaa2", + "class_hash": "0x7c9469d45a9cdbab775035afb48e1fa73fb35ab059fcb9dfb0a301aa973e783", + "address": "0x24334e79a3c56e5374c5bdd148c22ff2f0de3b4dc6e734e22ea49795f367221", "seed": "dojo_examples", "name": "example", "entrypoints": [ @@ -11,6 +11,7 @@ "register_event", "register_model", "register_contract", + "register_library", "init_contract", "upgrade_event", "upgrade_model", @@ -78,6 +79,10 @@ { "name": "Unregistered", "type": "()" + }, + { + "name": "Library", + "type": "(core::starknet::class_hash::ClassHash, core::felt252)" } ] }, @@ -362,6 +367,34 @@ ], "state_mutability": "external" }, + { + "type": "function", + "name": "register_library", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + }, + { + "name": "name", + "type": "core::byte_array::ByteArray" + }, + { + "name": "version", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, { "type": "function", "name": "init_contract", @@ -965,6 +998,28 @@ } ] }, + { + "type": "event", + "name": "dojo::world::world_contract::world::LibraryRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world_contract::world::EventEmitted", @@ -1206,6 +1261,11 @@ "type": "dojo::world::world_contract::world::ContractInitialized", "kind": "nested" }, + { + "name": "LibraryRegistered", + "type": "dojo::world::world_contract::world::LibraryRegistered", + "kind": "nested" + }, { "name": "EventEmitted", "type": "dojo::world::world_contract::world::EventEmitted", @@ -1252,8 +1312,8 @@ }, "contracts": [ { - "address": "0x7447baef53fdcc376b73963aa2bd3b0894be7d5bd40f596cc44d1d54d80ea52", - "class_hash": "0x2267c07d691a6da353f0beaec2e15f3b79ef271953364efb143c343713a6ccd", + "address": "0x73011d08f54413ec16fd768a0dcf1e61a6218e196b01b6ddfad09502f47e71d", + "class_hash": "0x747821121b203c09f02f2568e69fa3e4f50840ec33d38546143cffe6114afce", "abi": [ { "type": "impl", @@ -1606,7 +1666,7 @@ ] }, { - "address": "0x608ffd2e6b74a7bede256770ebe3d07bc65c79622e6a9396ea764011152102", + "address": "0x2fbd487dc4ccae7a01c767addb192df5c285fe07fe06756b463d7b5f3b43044", "class_hash": "0x7b31bb137c041fbe8247724a888bb108aaf2ff6b2bb7be5ecf5c954471e30c3", "abi": [ { @@ -1800,7 +1860,7 @@ ] }, { - "address": "0xca72f1cd782b614fa12c8b54afa895a169a4de1792738d4e3f09d0929f7834", + "address": "0x64b964be7fb8d9d9161a34b39cb2709b7a36b46063e0180c93a62aabc899b52", "class_hash": "0x38c4b1f36acecf15ea27430f34956bdfed933271ef23bcb9444db15dce66c6b", "abi": [ { @@ -1976,7 +2036,7 @@ ] }, { - "address": "0x780e3207b4f11b56f32cc0f19975af5b3a4df3cad6a4b0ab59a1702ba0304d8", + "address": "0x6460654b134506a238bf79b73d2be078f587718521048c80b78514fe90b6336", "class_hash": "0x5dd5b200655bdc68fba0ec23b25a9fb2d8ea2da118d60789c2d1d6adac3afae", "abi": [ { @@ -2159,6 +2219,155 @@ ] } ], + "libraries": [ + { + "class_hash": "0x18e785a7f62e106c199d15b41e756a2ba8a89f5d229f31a5b7e8971f7529a69", + "abi": [ + { + "type": "impl", + "name": "simple_math__LibraryImpl", + "interface_name": "dojo::contract::interface::ILibrary" + }, + { + "type": "interface", + "name": "dojo::contract::interface::ILibrary", + "items": [] + }, + { + "type": "impl", + "name": "simple_math__DeployedLibraryImpl", + "interface_name": "dojo::meta::interface::IDeployedResource" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::meta::interface::IDeployedResource", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "SimpleMathImpl", + "interface_name": "dojo_examples::lib_math::SimpleMath" + }, + { + "type": "interface", + "name": "dojo_examples::lib_math::SimpleMath", + "items": [ + { + "type": "function", + "name": "decrement_saturating", + "inputs": [ + { + "name": "value", + "type": "core::integer::u8" + } + ], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "test", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "dojo_examples::lib_math::simple_math::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "flat" + } + ] + } + ], + "tag": "ns-simple_math_v0_1_0", + "selector": "0x43022cc9b4933e6509e2292bf7dfa1c4fadb9a9d0c7a864b3e6487b8502e481", + "systems": [], + "version": "0_1_0" + } + ], "models": [ { "members": [], diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 4683eddb37..7ba26404d9 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -23,8 +23,10 @@ pub mod actions { Position, Moves, MovesValue, Direction, Vec2, PlayerConfig, PlayerItem, ServerProfile, }; use dojo_examples::utils::next_position; + use dojo_examples::lib_math::{SimpleMathLibraryDispatcher, SimpleMathDispatcherTrait}; use dojo::model::{ModelStorage, ModelValueStorage, Model}; use dojo::event::EventStorage; + use dojo::world::{WorldStorage, WorldStorageTrait}; // Features can be used on modules, structs, trait and `use`. Not inside // a function. @@ -108,7 +110,9 @@ pub mod actions { let move_id = dojo::utils::entity_id_from_serialized_keys([player_felt].span()); let mut moves: MovesValue = world.read_value_from_id(move_id); - moves.remaining -= 1; + + let simple_math = self.simple_math_dispatcher(@world); + moves.remaining = simple_math.decrement_saturating(moves.remaining); moves.last_direction = direction; world.write_value_from_id(move_id, @moves); @@ -212,15 +216,25 @@ pub mod actions { /// Use the default namespace "ns". A function is handy since the ByteArray /// can't be const. - fn world_default(self: @ContractState) -> dojo::world::WorldStorage { + fn world_default(self: @ContractState) -> WorldStorage { self.world(@"ns") } /// A gas optimized version of `world_default`, where hash is computed at compile time. /// Can make a difference if switching between namespaces is frequent. - fn world_default_ns_hash(self: @ContractState) -> dojo::world::WorldStorage { + fn world_default_ns_hash(self: @ContractState) -> WorldStorage { self.world_ns_hash(bytearray_hash!("ns")) } + + /// Example of how to get a dispatcher from a declared dojo library. + /// `{library_module_name}_v{version}`. + fn simple_math_dispatcher( + self: @ContractState, world: @WorldStorage, + ) -> SimpleMathLibraryDispatcher { + let (_, class_hash) = world.dns(@"simple_math_v0_1_0").expect('simple_math not found'); + + SimpleMathLibraryDispatcher { class_hash } + } } } diff --git a/examples/spawn-and-move/src/lib.cairo b/examples/spawn-and-move/src/lib.cairo index e927215bc0..1a8fd9ba51 100644 --- a/examples/spawn-and-move/src/lib.cairo +++ b/examples/spawn-and-move/src/lib.cairo @@ -1,5 +1,6 @@ pub mod actions; pub mod externals; +pub mod lib_math; pub mod models; pub mod utils; pub mod others; diff --git a/examples/spawn-and-move/src/lib_math.cairo b/examples/spawn-and-move/src/lib_math.cairo new file mode 100644 index 0000000000..6fcf16bcc3 --- /dev/null +++ b/examples/spawn-and-move/src/lib_math.cairo @@ -0,0 +1,26 @@ +#[starknet::interface] +pub trait SimpleMath { + /// Decrements the value, saturating at 0. + fn decrement_saturating(self: @T, value: u8) -> u8; + fn test(self: @T) -> u8; +} + +#[dojo::library] +pub mod simple_math { + use super::SimpleMath; + use core::num::traits::SaturatingSub; + + #[abi(embed_v0)] + impl SimpleMathImpl of SimpleMath { + fn decrement_saturating(self: @ContractState, value: u8) -> u8 { + let mut v = value; + v.saturating_sub(1); + + v + } + + fn test(self: @ContractState) -> u8 { + 2 + } + } +} diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index cb21bedb6e..ab4c279bcb 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index eea1d59086..08cb9d273a 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ