Skip to content

Commit

Permalink
add namespace attribute to dojo::model
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed May 31, 2024
1 parent ce56028 commit ec736fa
Show file tree
Hide file tree
Showing 6 changed files with 795 additions and 82 deletions.
2 changes: 2 additions & 0 deletions crates/dojo-core/src/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ trait Model<T> {
fn version() -> u8;
fn selector() -> felt252;
fn instance_selector(self: @T) -> felt252;
fn namespace() -> ByteArray;
fn keys(self: @T) -> Span<felt252>;
fn values(self: @T) -> Span<felt252>;
fn layout() -> dojo::database::introspect::Layout;
Expand All @@ -21,6 +22,7 @@ trait IModel<T> {
fn selector(self: @T) -> felt252;
fn name(self: @T) -> ByteArray;
fn version(self: @T) -> u8;
fn namespace(self: @T) -> ByteArray;
fn unpacked_size(self: @T) -> Option<usize>;
fn packed_size(self: @T) -> Option<usize>;
fn layout(self: @T) -> dojo::database::introspect::Layout;
Expand Down
14 changes: 13 additions & 1 deletion crates/dojo-core/src/resource_metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use dojo::world::IWorldDispatcherTrait;
use dojo::model::Model;

const RESOURCE_METADATA_SELECTOR: felt252 = selector!("ResourceMetadata");
const RESOURCE_METADATA_NAMESPACE_SELECTOR: felt252 = selector!("Dojo");

fn initial_address() -> starknet::ContractAddress {
starknet::contract_address_const::<0>()
Expand Down Expand Up @@ -60,14 +61,21 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {

#[inline(always)]
fn selector() -> felt252 {
RESOURCE_METADATA_SELECTOR
poseidon::poseidon_hash_span(
array![RESOURCE_METADATA_NAMESPACE_SELECTOR, RESOURCE_METADATA_SELECTOR].span()
)
}

#[inline(always)]
fn instance_selector(self: @ResourceMetadata) -> felt252 {
Self::selector()
}

fn namespace() -> ByteArray {
// TODO: in future Cairo versions, it should be possible to create const ByteArray
"Dojo"
}

#[inline(always)]
fn keys(self: @ResourceMetadata) -> Span<felt252> {
let mut serialized = core::array::ArrayTrait::new();
Expand Down Expand Up @@ -163,6 +171,10 @@ mod resource_metadata {
ResourceMetadataModel::version()
}

fn namespace(self: @ContractState) -> ByteArray {
ResourceMetadataModel::namespace()
}

#[external(v0)]
fn unpacked_size(self: @ContractState) -> Option<usize> {
dojo::database::introspect::Introspect::<ResourceMetadata>::size()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
kind = "Class"
class_hash = "0xf6f44afb3cacbcc01a371aff62c86ca9a45feba065424c99f7cd8637514d8f"
original_class_hash = "0xf6f44afb3cacbcc01a371aff62c86ca9a45feba065424c99f7cd8637514d8f"
class_hash = "0x120f681cf712a11dbd2008b1cc33bef132f260552b6d90df654fd908c347909"
original_class_hash = "0x120f681cf712a11dbd2008b1cc33bef132f260552b6d90df654fd908c347909"
abi = "manifests/dev/abis/base/dojo_world_world.json"
name = "dojo::world::world"
54 changes: 52 additions & 2 deletions crates/dojo-lang/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode};
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use convert_case::{Case, Casing};
use dojo_world::manifest::Member;
use starknet::core::crypto::compute_hash_on_elements;
use starknet::core::utils::get_selector_from_name;

use crate::plugin::{DojoAuxData, Model, DOJO_MODEL_ATTR};

const DEFAULT_MODEL_VERSION: u8 = 1;

const MODEL_VERSION_NAME: &str = "version";
const MODEL_NAMESPACE: &str = "namespace";

struct ModelParameters {
version: u8,
namespace: Option<String>,
}

impl Default for ModelParameters {
fn default() -> ModelParameters {
ModelParameters { version: DEFAULT_MODEL_VERSION }
ModelParameters { version: DEFAULT_MODEL_VERSION, namespace: Option::None }
}
}

Expand Down Expand Up @@ -73,6 +76,29 @@ fn get_model_version(
}
}

/// Get the model namespace from the `Expr` parameter.
fn get_model_namespace(
db: &dyn SyntaxGroup,
arg_value: Expr,
diagnostics: &mut Vec<PluginDiagnostic>,
) -> Option<String> {
match arg_value {
Expr::ShortString(ss) => Some(ss.string_value(db).unwrap()),
Expr::String(s) => Some(s.string_value(db).unwrap()),
_ => {
diagnostics.push(PluginDiagnostic {
message: format!(
"The argument '{}' of dojo::model must be a string",
MODEL_NAMESPACE
),
stable_ptr: arg_value.stable_ptr().untyped(),
severity: Severity::Error,
});
Option::None
}
}
}

/// Get parameters of the dojo::model attribute.
///
/// Note: dojo::model attribute has already been checked so there is one and only one attribute.
Expand Down Expand Up @@ -114,6 +140,9 @@ fn get_model_parameters(
MODEL_VERSION_NAME => {
parameters.version = get_model_version(db, arg_value, diagnostics);
}
MODEL_NAMESPACE => {
parameters.namespace = get_model_namespace(db, arg_value, diagnostics);
}
_ => {
diagnostics.push(PluginDiagnostic {
message: format!(
Expand Down Expand Up @@ -163,17 +192,28 @@ pub fn handle_model_struct(
db: &dyn SyntaxGroup,
aux_data: &mut DojoAuxData,
struct_ast: ItemStruct,
package_id: String,
) -> (RewriteNode, Vec<PluginDiagnostic>) {
let mut diagnostics = vec![];

let parameters = get_model_parameters(db, struct_ast.clone(), &mut diagnostics);

let model_name = struct_ast.name(db).as_syntax_node().get_text(db).trim().to_string();
let model_namespace = match parameters.namespace {
Option::Some(x) => x,
Option::None => package_id,
};
let (model_version, model_selector) = match parameters.version {
0 => (RewriteNode::Text("0".to_string()), RewriteNode::Text(format!("\"{model_name}\""))),
_ => (
RewriteNode::Text(DEFAULT_MODEL_VERSION.to_string()),
RewriteNode::Text(get_selector_from_name(model_name.as_str()).unwrap().to_string()),
RewriteNode::Text(
compute_hash_on_elements(&[
get_selector_from_name(model_namespace.as_str()).unwrap(),
get_selector_from_name(model_name.as_str()).unwrap(),
])
.to_string(),
),
),
};

Expand Down Expand Up @@ -292,6 +332,11 @@ impl $type_name$Model of dojo::model::Model<$type_name$> {
Self::selector()
}
#[inline(always)]
fn namespace() -> ByteArray {
\"$model_namespace$\"
}
#[inline(always)]
fn keys(self: @$type_name$) -> Span<felt252> {
let mut serialized = core::array::ArrayTrait::new();
Expand Down Expand Up @@ -360,6 +405,10 @@ mod $contract_name$ {
fn version(self: @ContractState) -> u8 {
dojo::model::Model::<$type_name$>::version()
}
fn namespace(self: @ContractState) -> ByteArray {
dojo::model::Model::<$type_name$>::namespace()
}
fn unpacked_size(self: @ContractState) -> Option<usize> {
dojo::database::introspect::Introspect::<$type_name$>::size()
Expand Down Expand Up @@ -393,6 +442,7 @@ mod $contract_name$ {
("serialized_values".to_string(), RewriteNode::new_modified(serialized_values)),
("model_version".to_string(), model_version),
("model_selector".to_string(), model_selector),
("model_namespace".to_string(), RewriteNode::Text(model_namespace)),
]),
),
diagnostics,
Expand Down
68 changes: 63 additions & 5 deletions crates/dojo-lang/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::cmp::Ordering;

use anyhow::Result;
use cairo_lang_defs::patcher::PatchBuilder;
use cairo_lang_defs::plugin::{
DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, MacroPluginMetadata,
PluginDiagnostic, PluginGeneratedFile, PluginResult,
};
use cairo_lang_diagnostics::Severity;
use cairo_lang_filesystem::ids::Directory;
use cairo_lang_semantic::plugin::PluginSuite;
use cairo_lang_starknet::plugin::aux_data::StarkNetEventAuxData;
use cairo_lang_syntax::attribute::structured::{AttributeArgVariant, AttributeStructurize};
Expand All @@ -22,6 +21,9 @@ use scarb::compiler::plugin::{CairoPlugin, CairoPluginInstance};
use scarb::core::{PackageId, PackageName, SourceId};
use semver::Version;
use smol_str::SmolStr;
use std::cmp::Ordering;
use std::fs;
use toml::Table;
use url::Url;

use crate::contract::DojoContract;
Expand Down Expand Up @@ -71,7 +73,11 @@ impl GeneratedFileAuxData for DojoAuxData {
self
}
fn eq(&self, other: &dyn GeneratedFileAuxData) -> bool {
if let Some(other) = other.as_any().downcast_ref::<Self>() { self == other } else { false }
if let Some(other) = other.as_any().downcast_ref::<Self>() {
self == other
} else {
false
}
}
}

Expand All @@ -89,7 +95,11 @@ impl GeneratedFileAuxData for ComputedValuesAuxData {
self
}
fn eq(&self, other: &dyn GeneratedFileAuxData) -> bool {
if let Some(other) = other.as_any().downcast_ref::<Self>() { self == other } else { false }
if let Some(other) = other.as_any().downcast_ref::<Self>() {
self == other
} else {
false
}
}
}

Expand Down Expand Up @@ -316,6 +326,41 @@ fn get_additional_derive_attrs_for_model(derive_attr_names: &[String]) -> Vec<St
additional_attrs
}

// parse the configuration file of the first crate to extract
// the main package Id (so the name field of the package section of the Scarb.toml file)
fn get_package_id(db: &dyn SyntaxGroup) -> Option<String> {
let crates = db.crates();

if crates.is_empty() {
return Option::None;
}

let configuration = match db.crate_config(crates[0]) {
Option::Some(cfg) => cfg,
Option::None => return Option::None,
};

if let Directory::Real(path) = configuration.root {
let config_path = path.parent().unwrap().join("Scarb.toml");
let config_content = match fs::read_to_string(config_path) {
Ok(x) => x,
Err(_) => return Option::None,
};
let config = match config_content.parse::<Table>() {
Ok(x) => x,
Err(_) => return Option::None,
};

if config.contains_key("package") {
if config["package"].as_table().unwrap().contains_key("name") {
return Some(config["package"]["name"].as_str().unwrap().to_string());
}
}
}

Option::None
}

impl MacroPlugin for BuiltinDojoPlugin {
// New metadata field: <https://github.com/starkware-libs/cairo/blob/60340c801125b25baaaddce64dd89c6c1524b59d/crates/cairo-lang-defs/src/plugin.rs#L81>
// Not used for now, but it contains a key-value BTreeSet. TBD what we can do with this.
Expand All @@ -325,6 +370,19 @@ impl MacroPlugin for BuiltinDojoPlugin {
item_ast: ast::ModuleItem,
_metadata: &MacroPluginMetadata<'_>,
) -> PluginResult {
let package_id = match get_package_id(db) {
Option::Some(x) => x,
Option::None => return PluginResult {
code: Option::None,
diagnostics: vec![PluginDiagnostic {
stable_ptr: item_ast.stable_ptr().0,
message: "Unable to find the package ID. Be sure to have a 'package:name' field in your Scarb.toml file.".into(),
severity: Severity::Error,
}],
remove_original_item: false,
},
};

match item_ast {
ast::ModuleItem::Module(module_ast) => self.handle_mod(db, module_ast),
ast::ModuleItem::Trait(trait_ast) => self.handle_trait(db, trait_ast),
Expand Down Expand Up @@ -466,7 +524,7 @@ impl MacroPlugin for BuiltinDojoPlugin {
match model_attrs.len().cmp(&1) {
Ordering::Equal => {
let (model_rewrite_nodes, model_diagnostics) =
handle_model_struct(db, &mut aux_data, struct_ast.clone());
handle_model_struct(db, &mut aux_data, struct_ast.clone(), package_id);
rewrite_nodes.push(model_rewrite_nodes);
diagnostics.extend(model_diagnostics);
}
Expand Down
Loading

0 comments on commit ec736fa

Please sign in to comment.