From 1da9ffff01f08e956dc973117f7008cef7d9138d Mon Sep 17 00:00:00 2001 From: Loaf <90423308+ponderingdemocritus@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:12:13 +1000 Subject: [PATCH] fix(sozo): adjust typescript bindgen to v1 (#2202) * work on ts v1 bindgen * work on model def * formatting * improve codegen * add tests * update enum to string * fix mapping in systems gen * remove unneeded filters * fix imports and errors * fix: fix tests and fmt * chore: bump cainome * chore: move deps to dev deps --------- Co-authored-by: glihm --- Cargo.lock | 48 ++--- Cargo.toml | 2 +- crates/dojo-bindgen/Cargo.toml | 2 + .../src/plugins/typescript/mod.rs | 158 +++++++++++---- .../src/plugins/typescript/tests.rs | 183 ++++++++++++++++++ 5 files changed, 328 insertions(+), 65 deletions(-) create mode 100644 crates/dojo-bindgen/src/plugins/typescript/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 875355c15b..5f41c86f00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2082,14 +2082,14 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cainome" version = "0.2.3" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.0#6c82c5b8e8169a79fe5606f963a3afdf0aa37078" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.2#3aec6d1465e24af3765d3b9220cc233199a6aa14" dependencies = [ "anyhow", "async-trait", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "camino", "clap", "clap_complete", @@ -2132,7 +2132,7 @@ dependencies = [ [[package]] name = "cainome-cairo-serde" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.0#6c82c5b8e8169a79fe5606f963a3afdf0aa37078" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.2#3aec6d1465e24af3765d3b9220cc233199a6aa14" dependencies = [ "serde", "starknet 0.11.0", @@ -2152,7 +2152,7 @@ dependencies = [ [[package]] name = "cainome-parser" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.0#6c82c5b8e8169a79fe5606f963a3afdf0aa37078" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.2#3aec6d1465e24af3765d3b9220cc233199a6aa14" dependencies = [ "convert_case 0.6.0", "quote", @@ -2178,11 +2178,11 @@ dependencies = [ [[package]] name = "cainome-rs" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.0#6c82c5b8e8169a79fe5606f963a3afdf0aa37078" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.2#3aec6d1465e24af3765d3b9220cc233199a6aa14" dependencies = [ "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "camino", "prettyplease 0.2.20", "proc-macro2", @@ -2214,12 +2214,12 @@ dependencies = [ [[package]] name = "cainome-rs-macro" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.0#6c82c5b8e8169a79fe5606f963a3afdf0aa37078" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.3.2#3aec6d1465e24af3765d3b9220cc233199a6aa14" dependencies = [ "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "proc-macro2", "quote", "serde_json", @@ -4206,8 +4206,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" name = "dojo-bindgen" version = "1.0.0-alpha.2" dependencies = [ + "assert_matches", "async-trait", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "camino", "chrono", "convert_case 0.6.0", @@ -4218,6 +4219,7 @@ dependencies = [ "serde_json", "starknet 0.11.0", "thiserror", + "tokio", ] [[package]] @@ -4234,7 +4236,7 @@ version = "1.0.0-alpha.2" dependencies = [ "anyhow", "assert_fs", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "cairo-lang-compiler", "cairo-lang-debug", "cairo-lang-defs", @@ -4350,7 +4352,7 @@ dependencies = [ name = "dojo-types" version = "1.0.0-alpha.2" dependencies = [ - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "crypto-bigint", "hex", "itertools 0.12.1", @@ -4371,7 +4373,7 @@ dependencies = [ "assert_fs", "assert_matches", "async-trait", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "cairo-lang-filesystem", "cairo-lang-project", "cairo-lang-starknet", @@ -7736,7 +7738,7 @@ dependencies = [ "alloy", "anyhow", "assert_matches", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "dojo-metrics", "dojo-test-utils", "dojo-world", @@ -12485,7 +12487,7 @@ dependencies = [ "assert_fs", "async-trait", "bigdecimal 0.4.5", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", @@ -12545,7 +12547,7 @@ dependencies = [ "assert_fs", "async-trait", "bigdecimal 0.4.5", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", @@ -14151,7 +14153,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.21.7", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "camino", "chrono", "crypto-bigint", @@ -14276,7 +14278,7 @@ version = "1.0.0-alpha.2" dependencies = [ "anyhow", "async-trait", - "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.0)", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", "chrono", "crypto-bigint", "dojo-test-utils", diff --git a/Cargo.toml b/Cargo.toml index d179584b9e..a8dd04ec03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ inherits = "release" lto = "fat" [workspace.dependencies] -cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.3.0", features = [ "abigen-rs" ] } +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.3.2", features = [ "abigen-rs" ] } common = { path = "crates/common" } # metrics diff --git a/crates/dojo-bindgen/Cargo.toml b/crates/dojo-bindgen/Cargo.toml index 8a97d8c2c2..747a70a796 100644 --- a/crates/dojo-bindgen/Cargo.toml +++ b/crates/dojo-bindgen/Cargo.toml @@ -24,5 +24,7 @@ cainome.workspace = true dojo-world = { path = "../dojo-world", features = [ "manifest" ] } [dev-dependencies] +assert_matches.workspace = true dojo-test-utils = { path = "../dojo-test-utils", features = [ "build-examples" ] } scarb = { workspace = true } +tokio.workspace = true diff --git a/crates/dojo-bindgen/src/plugins/typescript/mod.rs b/crates/dojo-bindgen/src/plugins/typescript/mod.rs index 2c0d13019c..7750d2d1f0 100644 --- a/crates/dojo-bindgen/src/plugins/typescript/mod.rs +++ b/crates/dojo-bindgen/src/plugins/typescript/mod.rs @@ -9,6 +9,9 @@ use crate::error::BindgenResult; use crate::plugins::BuiltinPlugin; use crate::{DojoContract, DojoData, DojoModel}; +#[cfg(test)] +mod tests; + pub struct TypescriptPlugin {} impl TypescriptPlugin { @@ -82,7 +85,11 @@ impl TypescriptPlugin { fn generated_header() -> String { format!( - "// Generated by dojo-bindgen on {}. Do not modify this file manually.\n", + " +// Generated by dojo-bindgen on {}. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +", chrono::Utc::now().to_rfc2822() ) } @@ -118,7 +125,6 @@ impl TypescriptPlugin { export interface {name} {{ {native_fields} }} - export const {name}Definition = {{ {fields} }}; @@ -134,12 +140,24 @@ export const {name}Definition = {{ // This will be formatted into a C# enum // Enum is mapped using index of cairo enum fn format_enum(token: &Composite) -> String { + // filter out common types + // TODO: Make cleaner + if token.type_path == "core::option::Option::" + || token.type_path == "core::option::Option::" + || token.type_path == "core::option::Option::" + || token.type_path == "core::option::Option::" + || token.type_path == "core::option::Option::" + || token.type_path == "core::option::Option::" + { + return String::new(); // Return an empty string for these enums + } + let name = TypescriptPlugin::map_type(&Token::Composite(token.clone())); let mut result = format!( " // Type definition for `{}` enum -type {} = ", +export type {} = ", token.type_path, name ); @@ -168,7 +186,7 @@ type {} = ", export const {name}Definition = {{ type: RecsType.String,{} }}; -", + ", if !token.inners.is_empty() { "\n value: RecsType.String".to_string() } else { @@ -186,21 +204,34 @@ export const {name}Definition = {{ fn format_model(namespace: &str, model: &Composite) -> String { let mut custom_types = Vec::::new(); let mut types = Vec::::new(); - let fields = model - .inners - .iter() - .map(|field| { + let (fields, _composite_type) = + model.inners.iter().fold((Vec::new(), model.r#type), |(mut fields, _), field| { let mapped = TypescriptPlugin::map_type(&field.token); - if mapped == field.token.type_name() { - custom_types.push(format!("\"{}\"", field.token.type_name())); - format!("{}: {}Definition,", field.name, mapped) - } else { - types.push(format!("\"{}\"", field.token.type_name())); - format!("{}: {},", field.name, mapped) - } - }) - .collect::>() - .join("\n "); + + let field_str = match field.token { + Token::Composite(ref c) if c.r#type == CompositeType::Enum => { + types.push(format!("\"{}\"", field.token.type_name())); + format!("{}: RecsType.String,", field.name) + } + Token::Composite(_) => { + custom_types.push(format!("\"{}\"", field.token.type_name())); + format!("{}: {}Definition,", field.name, mapped) + } + _ if mapped == field.token.type_name() => { + custom_types.push(format!("\"{}\"", field.token.type_name())); + format!("{}: {}Definition,", field.name, mapped) + } + _ => { + types.push(format!("\"{}\"", field.token.type_name())); + format!("{}: {},", field.name, mapped) + } + }; + + fields.push(field_str); + (fields, model.r#type) + }); + + let fields_str = fields.join("\n "); format!( " @@ -209,7 +240,7 @@ export const {name}Definition = {{ return defineComponent( world, {{ - {fields} + {fields_str} }}, {{ metadata: {{ @@ -237,9 +268,8 @@ export const {name}Definition = {{ out += TypescriptPlugin::generated_header().as_str(); out += "import { defineComponent, Type as RecsType, World } from \"@dojoengine/recs\";\n"; out += "\n"; - out += "export type ContractComponents = Awaited< - ReturnType - >;\n"; + out += "export type ContractComponents = Awaited>;\n"; out += "\n\n"; @@ -268,6 +298,14 @@ export const {name}Definition = {{ } }); + for token in &tokens.enums { + if handled_tokens.iter().filter(|t| t.type_name() == token.type_name()).count() > 1 + { + continue; + } + out += TypescriptPlugin::format_enum(token.to_composite().unwrap()).as_str(); + } + for token in &structs { if handled_tokens.iter().filter(|t| t.type_name() == token.type_name()).count() > 1 { @@ -285,14 +323,6 @@ export const {name}Definition = {{ out += TypescriptPlugin::format_struct(token.to_composite().unwrap()).as_str(); } - for token in &tokens.enums { - if handled_tokens.iter().filter(|t| t.type_name() == token.type_name()).count() > 1 - { - continue; - } - out += TypescriptPlugin::format_enum(token.to_composite().unwrap()).as_str(); - } - out += "\n"; } @@ -314,7 +344,20 @@ export function defineContractComponents(world: World) { // Formats a system into a C# method used by the contract class // Handled tokens should be a list of all structs and enums used by the contract // Such as a set of referenced tokens from a model - fn format_system(system: &Function, handled_tokens: &[Composite]) -> String { + fn format_system(system: &Function, handled_tokens: &[Composite], namespace: String) -> String { + if [ + "contract_name", + "namespace", + "tag", + "name_hash", + "selector", + "dojo_init", + "namespace_hash", + ] + .contains(&system.name.as_str()) + { + return String::new(); + } fn map_type(token: &Token) -> String { match token { Token::CoreBasic(_) => TypescriptPlugin::map_type(token) @@ -322,6 +365,7 @@ export function defineContractComponents(world: World) { // types should be lowercased .to_lowercase(), Token::Composite(t) => format!("models.{}", t.type_name()), + Token::Array(_) => TypescriptPlugin::map_type(token), _ => panic!("Unsupported token type: {:?}", token), } } @@ -370,11 +414,7 @@ export function defineContractComponents(world: World) { None => format!("props.{}", arg_name), } } - Token::Array(t) => format!( - "...props.{}.map(item => {}) ", - arg_name, - handle_arg_recursive("item", &t.inner, handled_tokens) - ), + Token::Array(_) => format!("...props.{}", arg_name), Token::Tuple(t) => format!( "...[{}]", t.inners @@ -408,7 +448,8 @@ export function defineContractComponents(world: World) { props.account, contract_name, \"{system_name}\", - [{calldata}] + [{calldata}], + \"{namespace}\" ); }} catch (error) {{ console.error(\"Error executing spawn:\", error); @@ -423,14 +464,19 @@ export function defineContractComponents(world: World) { // formatted args to use our mapped types args = args, // calldata for execute - calldata = calldata + calldata = calldata, + namespace = TypescriptPlugin::get_namespace_from_tag(&namespace) ) } // Formats a contract tag into a pretty contract name - // eg. dojo_examples-actions -> Actions + // eg. dojo_examples-actions -> actions fn formatted_contract_name(tag: &str) -> String { - naming::capitalize(&naming::get_name_from_tag(tag)) + naming::get_name_from_tag(tag) + } + + fn get_namespace_from_tag(tag: &str) -> String { + tag.split('-').next().unwrap_or(tag).to_string() } // Handles a contract definition and its underlying systems @@ -458,8 +504,25 @@ export function defineContractComponents(world: World) { let systems = contract .systems .iter() + .filter(|system| { + let name = system.to_function().unwrap().name.as_str(); + ![ + "contract_name", + "namespace", + "tag", + "name_hash", + "selector", + "dojo_init", + "namespace_hash", + ] + .contains(&name) + }) .map(|system| { - TypescriptPlugin::format_system(system.to_function().unwrap(), handled_tokens) + TypescriptPlugin::format_system( + system.to_function().unwrap(), + handled_tokens, + contract.tag.clone(), + ) }) .collect::>() .join("\n\n "); @@ -485,6 +548,19 @@ export function defineContractComponents(world: World) { contract .systems .iter() + .filter(|system| { + let name = system.to_function().unwrap().name.as_str(); + ![ + "contract_name", + "namespace", + "tag", + "name_hash", + "selector", + "dojo_init", + "namespace_hash", + ] + .contains(&name) + }) .map(|system| { system.to_function().unwrap().name.to_string() }) .collect::>() .join(", ") diff --git a/crates/dojo-bindgen/src/plugins/typescript/tests.rs b/crates/dojo-bindgen/src/plugins/typescript/tests.rs new file mode 100644 index 0000000000..078d9813ac --- /dev/null +++ b/crates/dojo-bindgen/src/plugins/typescript/tests.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; +use std::path::Path; + +use assert_matches::assert_matches; +use cainome::parser::tokens::{ + Composite, CompositeInner, CompositeInnerKind, CompositeType, Token, +}; + +use crate::plugins::typescript::TypescriptPlugin; +use crate::{BuiltinPlugin, DojoData, DojoWorld}; + +#[tokio::test] +async fn test_typescript_plugin_generate_code() { + let plugin = TypescriptPlugin::new(); + let data = create_mock_dojo_data(); + + let result = plugin.generate_code(&data).await; + + assert_matches!(result, Ok(output) => { + assert_eq!(output.len(), 2); + assert!(output.contains_key(Path::new("models.gen.ts"))); + assert!(output.contains_key(Path::new("contracts.gen.ts"))); + + // Check content of models.gen.ts + let models_content = String::from_utf8_lossy(&output[Path::new("models.gen.ts")]); + assert!(models_content.contains("import { defineComponent, Type as RecsType, World } from \"@dojoengine/recs\";")); + assert!(models_content.contains("export type ContractComponents = Awaited>;")); + + // Check content of contracts.gen.ts + let contracts_content = String::from_utf8_lossy(&output[Path::new("contracts.gen.ts")]); + assert!(contracts_content.contains("import { Account, byteArray } from \"starknet\";")); + assert!(contracts_content.contains("import { DojoProvider } from \"@dojoengine/core\";")); + assert!(contracts_content.contains("export type IWorld = Awaited>;")); + }); +} + +#[test] +fn test_map_type() { + let bool_token = + Token::CoreBasic(cainome::parser::tokens::CoreBasic { type_path: "bool".to_string() }); + assert_eq!(TypescriptPlugin::map_type(&bool_token), "RecsType.Boolean"); + + let u32_token = + Token::CoreBasic(cainome::parser::tokens::CoreBasic { type_path: "u32".to_string() }); + assert_eq!(TypescriptPlugin::map_type(&u32_token), "RecsType.Number"); +} + +#[test] +fn test_formatted_contract_name() { + assert_eq!(TypescriptPlugin::formatted_contract_name("dojo_examples-actions"), "actions"); + assert_eq!(TypescriptPlugin::formatted_contract_name("my-contract"), "contract"); +} + +#[test] +fn test_get_namespace_from_tag() { + assert_eq!(TypescriptPlugin::get_namespace_from_tag("dojo_examples-actions"), "dojo_examples"); + assert_eq!(TypescriptPlugin::get_namespace_from_tag("my-contract"), "my"); +} + +#[test] +fn test_format_model() { + // Create a mock Composite representing a model + let model = Composite { + type_path: "game::models::Position".to_string(), + r#type: CompositeType::Struct, + generic_args: vec![], + inners: vec![ + CompositeInner { + index: 0, + name: "x".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(cainome::parser::tokens::CoreBasic { + type_path: "u32".to_string(), + }), + }, + CompositeInner { + index: 1, + name: "y".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(cainome::parser::tokens::CoreBasic { + type_path: "u32".to_string(), + }), + }, + CompositeInner { + index: 2, + name: "player".to_string(), + kind: CompositeInnerKind::Data, + token: Token::Composite(Composite { + type_path: "game::models::Player".to_string(), + r#type: CompositeType::Struct, + generic_args: vec![], + inners: vec![], + is_event: false, + alias: None, + }), + }, + ], + is_event: false, + alias: None, + }; + + let namespace = "game"; + let formatted = TypescriptPlugin::format_model(namespace, &model); + + let expected = r#" + // Model definition for `game::models::Position` model + Position: (() => { + return defineComponent( + world, + { + x: RecsType.Number, + y: RecsType.Number, + player: PlayerDefinition, + }, + { + metadata: { + namespace: "game", + name: "Position", + types: ["u32", "u32"], + customTypes: ["Player"], + }, + } + ); + })(), +"#; + + assert_eq!(formatted.replace([' ', '\n'], "").trim(), expected.replace([' ', '\n'], "").trim()); +} + +#[test] +fn test_format_enum_model() { + // Create a mock Composite representing an enum model + let model = Composite { + type_path: "game::models::Direction".to_string(), + r#type: CompositeType::Enum, + generic_args: vec![], + inners: vec![CompositeInner { + index: 0, + name: "North".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(cainome::parser::tokens::CoreBasic { + type_path: "()".to_string(), + }), + }], + is_event: false, + alias: None, + }; + + let namespace = "game"; + let formatted = TypescriptPlugin::format_model(namespace, &model); + + let expected = r#" + // Model definition for `game::models::Direction` model + Direction: (() => { + return defineComponent( + world, + { + North: ()Definition, + }, + { + metadata: { + namespace: "game", + name: "Direction", + types: [], + customTypes: ["()"], + }, + } + ); + })(), +"#; + + // Remove all spaces and compare + assert_eq!(formatted.replace([' ', '\n'], "").trim(), expected.replace([' ', '\n'], "").trim()); +} + +// // Helper function to create mock DojoData for testing +fn create_mock_dojo_data() -> DojoData { + DojoData { + world: DojoWorld { name: 0x01.to_string() }, + models: HashMap::new(), + contracts: HashMap::new(), + } +}