Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for #[dojo::library] #2938

Merged
merged 17 commits into from
Feb 13, 2025
Merged
233 changes: 42 additions & 191 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ] }
Expand Down Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions bin/sozo/src/commands/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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)));
}
Expand Down
63 changes: 63 additions & 0 deletions bin/sozo/src/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ enum ResourceInspect {
Contract(ContractInspect),
Model(ModelInspect),
Event(EventInspect),
Library(LibraryInspect),
}

#[derive(Debug, Tabled, Serialize)]
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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![];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Ohayo! Tests needed for library display order, sensei! 🧪

The implementation shows a specific display order (namespaces → contracts → libraries → models → events), but there are no tests verifying this behavior. Consider adding tests to ensure:

  • Library resources are correctly collected
  • Libraries are properly sorted by tag
  • Display order remains consistent
🔗 Analysis chain

Clean integration of library display, sensei! 👍

The changes to inspect_world properly integrate library resource display alongside other resource types. However, consider adding a test to verify the display order.

Let's verify the test coverage:

Also applies to: 305-308, 317-317, 321-321

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tests related to library display
rg -l "test.*library.*display" --type rust

Length of output: 44


Script:

#!/bin/bash
# Search for inspect-related tests
echo "=== Inspect Tests ==="
rg "mod tests" -A 5 bin/sozo/src/commands/inspect.rs

# Search for library-related tests
echo -e "\n=== Library Tests ==="
rg "#\[test\]" -A 5 -B 1 bin/sozo/src/commands/inspect.rs | rg -A 5 -B 1 "library"

# Look for the implementation
echo -e "\n=== Library Display Implementation ==="
rg "libraries_disp" -A 5 bin/sozo/src/commands/inspect.rs

Length of output: 1329


for resource in world_diff.resources.values() {
match resource.resource_type() {
Expand All @@ -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!(),
},
_ => {}
}
}
Expand All @@ -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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate library table printing, sensei! 🔄

The library table is printed twice at lines 357 and 360. Remove one of these calls.

     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);

Also applies to: 360-360

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);
}

Expand Down Expand Up @@ -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(),
})
}
Comment on lines +435 to +474
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential panic in library version lookup, sensei! 🚨

The code uses expect() which could panic if the library version is not found. Consider using more graceful error handling.

Here's a suggested improvement:

-            let version = world_diff
-                .profile_config
-                .lib_versions
-                .as_ref()
-                .expect("expected lib_versions")
-                .get(&resource.tag())
-                .expect("lib_version not found");
+            let version = world_diff
+                .profile_config
+                .lib_versions
+                .as_ref()
+                .and_then(|versions| versions.get(&resource.tag()))
+                .map(ToString::to_string)
+                .unwrap_or_else(|| "unknown".to_string());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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::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()
.and_then(|versions| versions.get(&resource.tag()))
.map(ToString::to_string)
.unwrap_or_else(|| "unknown".to_string());
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,
Expand Down
82 changes: 43 additions & 39 deletions bin/sozo/tests/test_data/policies.json
Original file line number Diff line number Diff line change
@@ -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"
},
{
Expand Down
3 changes: 3 additions & 0 deletions crates/dojo/core-cairo-test/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ mod tests {

mod model;
pub use model::deploy_world_for_model_upgrades;

mod library;
pub use library::*;
}

mod world {
Expand Down
16 changes: 16 additions & 0 deletions crates/dojo/core-cairo-test/src/tests/helpers/library.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[starknet::interface]
pub trait LibraryA<T> {
fn get_byte(self: @T) -> u8;
}

#[dojo::library]
pub mod library_a {
use super::LibraryA;

#[abi(embed_v0)]
impl LibraryAImpl of LibraryA<ContractState> {
fn get_byte(self: @ContractState) -> u8 {
42
}
}
}
2 changes: 1 addition & 1 deletion crates/dojo/core-cairo-test/src/tests/world/event.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down
4 changes: 3 additions & 1 deletion crates/dojo/core-cairo-test/src/tests/world/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down
Loading
Loading