Skip to content

Commit

Permalink
add namespace attribute for contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Jun 2, 2024
1 parent 96fc6fb commit e812370
Show file tree
Hide file tree
Showing 22 changed files with 434 additions and 7,978 deletions.
5 changes: 2 additions & 3 deletions crates/dojo-core/src/resource_metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
//! Manually expand to ensure that dojo-core
//! does not depend on dojo plugin to be built.
//!
use dojo::world::IWorldDispatcherTrait;
use dojo::world::{IWorldDispatcherTrait, DOJO_NAMESPACE_SELECTOR};

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 @@ -62,7 +61,7 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
#[inline(always)]
fn selector() -> felt252 {
poseidon::poseidon_hash_span(
array![RESOURCE_METADATA_NAMESPACE_SELECTOR, RESOURCE_METADATA_SELECTOR].span()
array![DOJO_NAMESPACE_SELECTOR, RESOURCE_METADATA_SELECTOR].span()
)
}

Expand Down
7 changes: 7 additions & 0 deletions crates/dojo-core/src/world.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use traits::{Into, TryInto};
use option::OptionTrait;
use dojo::resource_metadata::ResourceMetadata;

const DOJO_NAMESPACE_SELECTOR: felt252 = selector!("Dojo");

#[starknet::interface]
trait IWorld<T> {
fn metadata(self: @T, resource_id: felt252) -> ResourceMetadata;
Expand Down Expand Up @@ -53,6 +55,11 @@ trait IDojoResourceProvider<T> {
fn dojo_resource(self: @T) -> felt252;
}

#[starknet::interface]
trait INamespace<T> {
fn namespace(self: @T) -> ByteArray;
}

mod Errors {
const METADATA_DESER: felt252 = 'metadata deser error';
const NOT_OWNER: felt252 = 'not owner';
Expand Down
191 changes: 159 additions & 32 deletions crates/dojo-lang/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use cairo_lang_defs::plugin::{
DynGeneratedFileAuxData, PluginDiagnostic, PluginGeneratedFile, PluginResult,
};
use cairo_lang_diagnostics::Severity;
use cairo_lang_syntax::attribute::structured::{
Attribute, AttributeArg, AttributeArgVariant, AttributeListStructurize,
};
use cairo_lang_syntax::node::ast::MaybeModuleBody;
use cairo_lang_syntax::node::ast::{ArgClause, Expr, MaybeModuleBody, OptionArgListParenthesized};
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::node::{ast, ids, Terminal, TypedStablePtr, TypedSyntaxNode};
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use dojo_types::system::Dependency;
Expand All @@ -18,27 +16,45 @@ use crate::plugin::{DojoAuxData, SystemAuxData, DOJO_CONTRACT_ATTR};

const ALLOW_REF_SELF_ARG: &str = "allow_ref_self";
const DOJO_INIT_FN: &str = "dojo_init";
const CONTRACT_NAMESPACE: &str = "namespace";

#[derive(Clone, Default)]
pub struct ContractParameters {
do_allow_ref_self: bool,
namespace: Option<String>,
}

pub struct DojoContract {
diagnostics: Vec<PluginDiagnostic>,
dependencies: HashMap<smol_str::SmolStr, Dependency>,
do_allow_ref_self: bool,
parameters: ContractParameters,
}

impl DojoContract {
pub fn from_module(db: &dyn SyntaxGroup, module_ast: ast::ItemModule) -> PluginResult {
pub fn from_module(
db: &dyn SyntaxGroup,
module_ast: &ast::ItemModule,
package_id: String,
) -> PluginResult {
let name = module_ast.name(db).text(db);

let attrs = module_ast.attributes(db).structurize(db);
let dojo_contract_attr = attrs.iter().find(|attr| attr.id.as_str() == DOJO_CONTRACT_ATTR);
let do_allow_ref_self = extract_allow_ref_self(dojo_contract_attr, db).unwrap_or_default();
let mut diagnostics = vec![];
let parameters = get_parameters(db, module_ast, &mut diagnostics);

let mut system =
DojoContract { diagnostics: vec![], dependencies: HashMap::new(), do_allow_ref_self };
let mut system = DojoContract {
diagnostics,
dependencies: HashMap::new(),
parameters: parameters.clone(),
};
let mut has_event = false;
let mut has_storage = false;
let mut has_init = false;

let contract_namespace = match parameters.namespace {
Some(x) => x.to_string(),
None => package_id,
};

if let MaybeModuleBody::Some(body) = module_ast.body(db) {
let mut body_nodes: Vec<_> = body
.items(db)
Expand Down Expand Up @@ -108,7 +124,7 @@ impl DojoContract {
body_nodes.append(&mut system.create_storage())
}

let mut builder = PatchBuilder::new(db, &module_ast);
let mut builder = PatchBuilder::new(db, module_ast);
builder.add_modified(RewriteNode::interpolate_patched(
"
#[starknet::contract]
Expand All @@ -118,7 +134,7 @@ impl DojoContract {
use dojo::world::IWorldDispatcherTrait;
use dojo::world::IWorldProvider;
use dojo::world::IDojoResourceProvider;
use dojo::world::INamespace;
component!(path: dojo::components::upgradeable::upgradeable, storage: \
upgradeable, event: UpgradeableEvent);
Expand All @@ -130,6 +146,13 @@ impl DojoContract {
}
}
#[abi(embed_v0)]
impl NamespaceImpl of INamespace<ContractState> {
fn namespace(self: @ContractState) -> ByteArray {
\"$contract_namespace$\"
}
}
#[abi(embed_v0)]
impl WorldProviderImpl of IWorldProvider<ContractState> {
fn world(self: @ContractState) -> IWorldDispatcher {
Expand All @@ -147,6 +170,7 @@ impl DojoContract {
&UnorderedHashMap::from([
("name".to_string(), RewriteNode::Text(name.to_string())),
("body".to_string(), RewriteNode::new_modified(body_nodes)),
("contract_namespace".to_string(), RewriteNode::Text(contract_namespace)),
]),
));

Expand Down Expand Up @@ -449,7 +473,7 @@ impl DojoContract {
});
}

if has_ref_self && !self.do_allow_ref_self {
if has_ref_self && !self.parameters.do_allow_ref_self {
self.diagnostics.push(PluginDiagnostic {
stable_ptr: diagnostic_item,
message: "Functions of dojo::contract cannot have 'ref self' parameter."
Expand Down Expand Up @@ -558,25 +582,128 @@ impl DojoContract {
}
}

/// Extract the allow_ref_self attribute.
pub(crate) fn extract_allow_ref_self(
allow_ref_self_attr: Option<&Attribute>,
/// Get the contract namespace from the `Expr` parameter.
fn get_contract_namespace(
db: &dyn SyntaxGroup,
) -> Option<bool> {
let Some(attr) = allow_ref_self_attr else {
return None;
};

#[allow(clippy::collapsible_match)]
match &attr.args[..] {
[AttributeArg { variant: AttributeArgVariant::Unnamed(value), .. }] => match value {
ast::Expr::Path(path)
if path.as_syntax_node().get_text_without_trivia(db) == ALLOW_REF_SELF_ARG =>
{
Some(true)
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::contract must be a string",
CONTRACT_NAMESPACE
),
stable_ptr: arg_value.stable_ptr().untyped(),
severity: Severity::Error,
});
Option::None
}
}
}

/// Get parameters of the dojo::contract attribute.
///
/// Parameters:
/// * db: The semantic database.
/// * module_ast: The AST of the contract module.
/// * diagnostics: vector of compiler diagnostics.
///
/// Returns:
/// * A [`ContractParameters`] object containing all the dojo::contract parameters with their
/// default values if not set in the code.
fn get_parameters(
db: &dyn SyntaxGroup,
module_ast: &ast::ItemModule,
diagnostics: &mut Vec<PluginDiagnostic>,
) -> ContractParameters {
let mut parameters = ContractParameters::default();
let mut processed_args: HashMap<String, bool> = HashMap::new();

if let OptionArgListParenthesized::ArgListParenthesized(arguments) =
module_ast.attributes(db).query_attr(db, DOJO_CONTRACT_ATTR).first().unwrap().arguments(db)
{
arguments.arguments(db).elements(db).iter().for_each(|a| match a.arg_clause(db) {
ArgClause::Named(x) => {
let arg_name = x.name(db).text(db).to_string();
let arg_value = x.value(db);

if processed_args.contains_key(&arg_name) {
diagnostics.push(PluginDiagnostic {
message: format!("Too many '{}' attributes for dojo::contract", arg_name),
stable_ptr: module_ast.stable_ptr().untyped(),
severity: Severity::Error,
});
} else {
processed_args.insert(arg_name.clone(), true);

match arg_name.as_str() {
CONTRACT_NAMESPACE => {
parameters.namespace =
get_contract_namespace(db, arg_value, diagnostics);
}
_ => {
diagnostics.push(PluginDiagnostic {
message: format!(
"Unexpected argument '{}' for dojo::contract",
arg_name
),
stable_ptr: x.stable_ptr().untyped(),
severity: Severity::Warning,
});
}
}
}
}
_ => None,
},
_ => None,
ArgClause::Unnamed(arg) => {
let arg_name = arg.value(db).as_syntax_node().get_text(db);
let arg_value = arg.value(db);

if processed_args.contains_key(&arg_name) {
diagnostics.push(PluginDiagnostic {
message: format!("Too many '{}' attributes for dojo::contract", arg_name),
stable_ptr: module_ast.stable_ptr().untyped(),
severity: Severity::Error,
});
} else {
processed_args.insert(arg_name.clone(), true);

match arg_value {
Expr::Path(path) => {
if path.as_syntax_node().get_text_without_trivia(db)
== ALLOW_REF_SELF_ARG
{
parameters.do_allow_ref_self = true;
}
}
_ => {
diagnostics.push(PluginDiagnostic {
message: format!(
"Unexpected argument '{}' for dojo::contract",
arg_name
),
stable_ptr: arg.stable_ptr().untyped(),
severity: Severity::Warning,
});
}
}
}
}
ArgClause::FieldInitShorthand(x) => {
diagnostics.push(PluginDiagnostic {
message: format!(
"Unexpected argument '{}' for dojo::contract",
x.name(db).name(db).text(db).to_string()
),
stable_ptr: x.stable_ptr().untyped(),
severity: Severity::Warning,
});
}
})
}

parameters
}
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,11 @@
"type": "core::byte_array::ByteArray",
"kind": "data"
},
{
"name": "namespace",
"type": "core::byte_array::ByteArray",
"kind": "data"
},
{
"name": "class_hash",
"type": "core::starknet::class_hash::ClassHash",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
kind = "Class"
class_hash = "0x120f681cf712a11dbd2008b1cc33bef132f260552b6d90df654fd908c347909"
original_class_hash = "0x120f681cf712a11dbd2008b1cc33bef132f260552b6d90df654fd908c347909"
class_hash = "0x610b8797d5ca3d551eb33a4086b13893f10d8d60b8ff23da40d4bd8d907dccb"
original_class_hash = "0x610b8797d5ca3d551eb33a4086b13893f10d8d60b8ff23da40d4bd8d907dccb"
abi = "manifests/dev/abis/base/dojo_world_world.json"
name = "dojo::world::world"
11 changes: 8 additions & 3 deletions crates/dojo-lang/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ pub const PACKAGE_NAME: &str = "dojo_plugin";
pub struct BuiltinDojoPlugin;

impl BuiltinDojoPlugin {
fn handle_mod(&self, db: &dyn SyntaxGroup, module_ast: ast::ItemModule) -> PluginResult {
fn handle_mod(
&self,
db: &dyn SyntaxGroup,
module_ast: ast::ItemModule,
package_id: String,
) -> PluginResult {
if module_ast.has_attr(db, DOJO_CONTRACT_ATTR) {
return DojoContract::from_module(db, module_ast);
return DojoContract::from_module(db, &module_ast, package_id);
}

PluginResult::default()
Expand Down Expand Up @@ -382,7 +387,7 @@ impl MacroPlugin for BuiltinDojoPlugin {
};

match item_ast {
ast::ModuleItem::Module(module_ast) => self.handle_mod(db, module_ast),
ast::ModuleItem::Module(module_ast) => self.handle_mod(db, module_ast, package_id),
ast::ModuleItem::Trait(trait_ast) => self.handle_trait(db, trait_ast),
ast::ModuleItem::Enum(enum_ast) => {
let aux_data = DojoAuxData::default();
Expand Down
Loading

0 comments on commit e812370

Please sign in to comment.