Skip to content

Commit

Permalink
feat(sozo): upload world/resources metadata if they changed
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Nov 16, 2024
1 parent f6659db commit 4b304de
Show file tree
Hide file tree
Showing 28 changed files with 685 additions and 193 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bin/sozo/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ impl MigrateArgs {
let MigrationResult { manifest, has_changes } =
migration.migrate(&mut spinner).await.context("Migration failed.")?;

migration.upload_metadata(&mut spinner).await.context("Metadata upload failed.")?;

Check warning on line 78 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L78

Added line #L78 was not covered by tests

spinner.update_text("Writing manifest...");
ws.write_manifest_profile(manifest).context("🪦 Failed to write manifest.")?;

Expand Down
11 changes: 6 additions & 5 deletions crates/dojo/core-cairo-test/src/tests/world/metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn test_set_metadata_world() {
let world = world.dispatcher;

let metadata = ResourceMetadata {
resource_id: 0, metadata_uri: format!("ipfs:world_with_a_long_uri_that")
resource_id: 0, metadata_uri: format!("ipfs:world_with_a_long_uri_that"), metadata_hash: 42
};

world.set_metadata(metadata.clone());
Expand All @@ -30,7 +30,7 @@ fn test_set_metadata_resource_owner() {
starknet::testing::set_contract_address(bob);

let metadata = ResourceMetadata {
resource_id: model_selector, metadata_uri: format!("ipfs:bob")
resource_id: model_selector, metadata_uri: format!("ipfs:bob"), metadata_hash: 42
};

drop_all_events(world.contract_address);
Expand All @@ -46,6 +46,7 @@ fn test_set_metadata_resource_owner() {
if let world::Event::MetadataUpdate(event) = event.unwrap() {
assert(event.resource == metadata.resource_id, 'bad resource');
assert(event.uri == metadata.metadata_uri, 'bad uri');
assert(event.hash == metadata.metadata_hash, 'bad hash');
} else {
core::panic_with_felt252('no EventUpgraded event');
}
Expand All @@ -70,7 +71,7 @@ fn test_set_metadata_not_possible_for_resource_writer() {
starknet::testing::set_contract_address(bob);

let metadata = ResourceMetadata {
resource_id: model_selector, metadata_uri: format!("ipfs:bob")
resource_id: model_selector, metadata_uri: format!("ipfs:bob"), metadata_hash: 42
};

world.set_metadata(metadata.clone());
Expand All @@ -85,7 +86,7 @@ fn test_set_metadata_not_possible_for_random_account() {
let world = world.dispatcher;

let metadata = ResourceMetadata { // World metadata.
resource_id: 0, metadata_uri: format!("ipfs:bob"),
resource_id: 0, metadata_uri: format!("ipfs:bob"), metadata_hash: 42
};

let bob = starknet::contract_address_const::<0xb0b>();
Expand All @@ -112,7 +113,7 @@ fn test_set_metadata_through_malicious_contract() {
starknet::testing::set_contract_address(malicious_contract);

let metadata = ResourceMetadata {
resource_id: model_selector, metadata_uri: format!("ipfs:bob")
resource_id: model_selector, metadata_uri: format!("ipfs:bob"), metadata_hash: 42
};

world.set_metadata(metadata.clone());
Expand Down
1 change: 1 addition & 0 deletions crates/dojo/core/src/model/metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct ResourceMetadata {
#[key]
pub resource_id: felt252,
pub metadata_uri: ByteArray,
pub metadata_hash: felt252
}

pub fn default_address() -> starknet::ContractAddress {
Expand Down
9 changes: 7 additions & 2 deletions crates/dojo/core/src/world/world_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ pub mod world {
pub struct MetadataUpdate {
#[key]
pub resource: felt252,
pub uri: ByteArray
pub uri: ByteArray,
pub hash: felt252
}

#[derive(Drop, starknet::Event)]
Expand Down Expand Up @@ -356,7 +357,11 @@ pub mod world {

self
.emit(
MetadataUpdate { resource: metadata.resource_id, uri: metadata.metadata_uri }
MetadataUpdate {
resource: metadata.resource_id,
uri: metadata.metadata_uri,
hash: metadata.metadata_hash
}
);
}

Expand Down
1 change: 1 addition & 0 deletions crates/dojo/world/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ num-bigint.workspace = true

[dev-dependencies]
tokio.workspace = true
futures.workspace = true

[features]
metadata = [ "dep:ipfs-api-backend-hyper" ]
63 changes: 62 additions & 1 deletion crates/dojo/world/src/config/metadata_config.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Metadata configuration for the world.
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

use serde::{Deserialize, Serialize};
use serde_json::json;
use url::Url;

use crate::config::WorldConfig;
use crate::config::{ResourceConfig, WorldConfig};
use crate::uri::Uri;

/// World metadata that describes the world.
Expand Down Expand Up @@ -33,3 +35,62 @@ impl From<WorldConfig> for WorldMetadata {
}
}
}

impl Hash for WorldMetadata {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.seed.hash(state);
self.description.hash(state);
self.cover_uri.hash(state);
self.icon_uri.hash(state);
self.website.hash(state);

json!(self.socials).to_string().hash(state);

// include icon and cover data into the hash to
// detect data changes even if the filename is the same.
if let Some(Uri::File(icon)) = &self.icon_uri {
let icon_data = std::fs::read(icon).expect("read icon failed");
icon_data.hash(state);
};

if let Some(Uri::File(cover)) = &self.cover_uri {
let cover_data = std::fs::read(cover).expect("read cover failed");
cover_data.hash(state);
};
}
}

/// resource metadata that describes world resources such as contracts,
/// models or events.
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
pub struct ResourceMetadata {
pub name: String,
pub description: Option<String>,
pub icon_uri: Option<Uri>,
}

impl From<ResourceConfig> for ResourceMetadata {
fn from(config: ResourceConfig) -> Self {
ResourceMetadata {
name: config.name,
description: config.description,
icon_uri: config.icon_uri,
}
}

Check warning on line 80 in crates/dojo/world/src/config/metadata_config.rs

View check run for this annotation

Codecov / codecov/patch

crates/dojo/world/src/config/metadata_config.rs#L74-L80

Added lines #L74 - L80 were not covered by tests
}

impl Hash for ResourceMetadata {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.description.hash(state);
self.icon_uri.hash(state);

// include icon and cover data into the hash to
// detect data changes even if the filename is the same.
if let Some(Uri::File(icon)) = &self.icon_uri {
let icon_data = std::fs::read(icon).expect("read icon failed");
icon_data.hash(state);
};
}
}
2 changes: 2 additions & 0 deletions crates/dojo/world/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ pub mod metadata_config;
pub mod migration_config;
pub mod namespace_config;
pub mod profile_config;
pub mod resource_config;
pub mod world_config;

pub use environment::Environment;
pub use metadata_config::WorldMetadata;
pub use namespace_config::NamespaceConfig;
pub use profile_config::ProfileConfig;
pub use resource_config::ResourceConfig;
pub use world_config::WorldConfig;
46 changes: 46 additions & 0 deletions crates/dojo/world/src/config/profile_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use toml;
use super::environment::Environment;
use super::migration_config::MigrationConfig;
use super::namespace_config::NamespaceConfig;
use super::resource_config::ResourceConfig;
use super::world_config::WorldConfig;

/// Profile configuration that is used to configure the world and its environment.
Expand All @@ -17,6 +18,9 @@ use super::world_config::WorldConfig;
#[derive(Debug, Clone, Default, Deserialize)]
pub struct ProfileConfig {
pub world: WorldConfig,
pub models: Option<Vec<ResourceConfig>>,
pub contracts: Option<Vec<ResourceConfig>>,
pub events: Option<Vec<ResourceConfig>>,
pub namespace: NamespaceConfig,
pub env: Option<Environment>,
pub migration: Option<MigrationConfig>,
Expand Down Expand Up @@ -134,6 +138,24 @@ mod tests {
website = "https://example.com"
socials = { "twitter" = "test", "discord" = "test" }
[[models]]
tag = "ns1-m1"
name = "m1"
description = "This is the m1 model"
icon_uri = "ipfs://dojo/m1.png"
[[contracts]]
tag = "ns1-c1"
name = "c1"
description = "This is the c1 contract"
icon_uri = "ipfs://dojo/c1.png"
[[events]]
tag = "ns1-e1"
name = "e1"
description = "This is the e1 event"
icon_uri = "ipfs://dojo/e1.png"
[namespace]
default = "test"
mappings = { "test" = ["test2"] }
Expand Down Expand Up @@ -190,6 +212,30 @@ mod tests {
]))
);

assert!(config.models.is_some());
let models = config.models.unwrap();
assert_eq!(models.len(), 1);
assert_eq!(models[0].tag, "ns1-m1");
assert_eq!(models[0].name, "m1");
assert_eq!(models[0].description, Some("This is the m1 model".to_string()));
assert_eq!(models[0].icon_uri, Some(Uri::from_string("ipfs://dojo/m1.png").unwrap()));

assert!(config.contracts.is_some());
let contracts = config.contracts.unwrap();
assert_eq!(contracts.len(), 1);
assert_eq!(contracts[0].tag, "ns1-c1");
assert_eq!(contracts[0].name, "c1");
assert_eq!(contracts[0].description, Some("This is the c1 contract".to_string()));
assert_eq!(contracts[0].icon_uri, Some(Uri::from_string("ipfs://dojo/c1.png").unwrap()));

assert!(config.events.is_some());
let events = config.events.unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].tag, "ns1-e1");
assert_eq!(events[0].name, "e1");
assert_eq!(events[0].description, Some("This is the e1 event".to_string()));
assert_eq!(events[0].icon_uri, Some(Uri::from_string("ipfs://dojo/e1.png").unwrap()));

assert_eq!(config.namespace.default, "test".to_string());
assert_eq!(
config.namespace.mappings,
Expand Down
11 changes: 11 additions & 0 deletions crates/dojo/world/src/config/resource_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::Deserialize;

use crate::uri::Uri;

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ResourceConfig {
pub tag: String,
pub name: String,
pub description: Option<String>,
pub icon_uri: Option<Uri>,
}
Loading

0 comments on commit 4b304de

Please sign in to comment.