diff --git a/crates/dojo-lang/src/contract.rs b/crates/dojo-lang/src/contract.rs index fac9667ba0..c2a9c292ff 100644 --- a/crates/dojo-lang/src/contract.rs +++ b/crates/dojo-lang/src/contract.rs @@ -4,37 +4,28 @@ use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; 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::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, ids, Terminal, TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use dojo_types::system::Dependency; -use crate::plugin::{DojoAuxData, SystemAuxData, DOJO_CONTRACT_ATTR}; +use crate::plugin::{DojoAuxData, SystemAuxData}; +use crate::syntax::world_param::{self, WorldParamInjectionKind}; +use crate::syntax::{self_param, utils as syntax_utils}; -const ALLOW_REF_SELF_ARG: &str = "allow_ref_self"; const DOJO_INIT_FN: &str = "dojo_init"; pub struct DojoContract { diagnostics: Vec, dependencies: HashMap, - do_allow_ref_self: bool, } impl DojoContract { pub fn from_module(db: &dyn SyntaxGroup, module_ast: ast::ItemModule) -> 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 system = - DojoContract { diagnostics: vec![], dependencies: HashMap::new(), do_allow_ref_self }; + let mut system = DojoContract { diagnostics: vec![], dependencies: HashMap::new() }; let mut has_event = false; let mut has_storage = false; let mut has_init = false; @@ -182,14 +173,14 @@ impl DojoContract { let fn_decl = fn_ast.declaration(db); let fn_name = fn_decl.name(db).text(db); - let (params_str, _, world_removed) = self.rewrite_parameters( + let (params_str, was_world_injected) = self.rewrite_parameters( db, fn_decl.signature(db).parameters(db), fn_ast.stable_ptr().untyped(), ); let mut world_read = ""; - if world_removed { + if was_world_injected { world_read = "let world = self.world_dispatcher.read();"; } @@ -303,166 +294,61 @@ impl DojoContract { )] } - /// Gets name, modifiers and type from a function parameter. - pub fn get_parameter_info( - &mut self, - db: &dyn SyntaxGroup, - param: ast::Param, - ) -> (String, String, String) { - let name = param.name(db).text(db).trim().to_string(); - let modifiers = param.modifiers(db).as_syntax_node().get_text(db).trim().to_string(); - let param_type = - param.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(); - - (name, modifiers, param_type) - } - - /// Check if the function has a self parameter. - /// - /// Returns - /// * a boolean indicating if `self` has to be added, - // * a boolean indicating if there is a `ref self` parameter. - pub fn check_self_parameter( - &mut self, - db: &dyn SyntaxGroup, - param_list: ast::ParamList, - ) -> (bool, bool) { - let mut add_self = true; - let mut has_ref_self = false; - if !param_list.elements(db).is_empty() { - let (param_name, param_modifiers, param_type) = - self.get_parameter_info(db, param_list.elements(db)[0].clone()); - - if param_name.eq(&"self".to_string()) { - if param_modifiers.contains(&"ref".to_string()) - && param_type.eq(&"ContractState".to_string()) - { - has_ref_self = true; - add_self = false; - } - - if param_type.eq(&"@ContractState".to_string()) { - add_self = false; - } - } - }; - - (add_self, has_ref_self) - } - - /// Check if the function has multiple IWorldDispatcher parameters. - /// - /// Returns - /// * a boolean indicating if the function has multiple world dispatchers. - pub fn check_world_dispatcher( - &mut self, - db: &dyn SyntaxGroup, - param_list: ast::ParamList, - ) -> bool { - let mut count = 0; - - param_list.elements(db).iter().for_each(|param| { - let (_, _, param_type) = self.get_parameter_info(db, param.clone()); - - if param_type.eq(&"IWorldDispatcher".to_string()) { - count += 1; - } - }); - - count > 1 - } - /// Rewrites parameter list by: - /// * adding `self` parameter if missing, - /// * removing `world` if present as first parameter (self excluded), as it will be read from - /// the first function statement. + /// * adding `self` parameter based on the `world` parameter mutability. If `world` is not + /// provided, a `View` is assumed. + /// * removing `world` if present as first parameter, as it will be read from the first + /// function statement. /// /// Reports an error in case of: - /// * `ref self`, as systems are supposed to be 100% stateless, - /// * multiple IWorldDispatcher parameters. - /// * the `IWorldDispatcher` is not the first parameter (self excluded) and named 'world'. + /// * `self` used explicitly, + /// * multiple world parameters, + /// * the `world` parameter is not the first parameter and named 'world'. /// /// Returns - /// * the list of parameters in a String - /// * a boolean indicating if `self` has been added - // * a boolean indicating if `world` parameter has been removed + /// * the list of parameters in a String. + /// * true if the world has to be injected (found as the first param). pub fn rewrite_parameters( &mut self, db: &dyn SyntaxGroup, param_list: ast::ParamList, - diagnostic_item: ids::SyntaxStablePtrId, - ) -> (String, bool, bool) { - let (add_self, has_ref_self) = self.check_self_parameter(db, param_list.clone()); - let has_multiple_world_dispatchers = self.check_world_dispatcher(db, param_list.clone()); + fn_diagnostic_item: ids::SyntaxStablePtrId, + ) -> (String, bool) { + self_param::check_parameter(db, ¶m_list, fn_diagnostic_item, &mut self.diagnostics); - let mut world_removed = false; + let world_injection = world_param::parse_world_injection( + db, + param_list.clone(), + fn_diagnostic_item, + &mut self.diagnostics, + ); let mut params = param_list .elements(db) .iter() - .enumerate() - .filter_map(|(idx, param)| { - let (name, modifiers, param_type) = self.get_parameter_info(db, param.clone()); - - if param_type.eq(&"IWorldDispatcher".to_string()) - && modifiers.eq(&"".to_string()) - && !has_multiple_world_dispatchers - { - let has_good_pos = (add_self && idx == 0) || (!add_self && idx == 1); - let has_good_name = name.eq(&"world".to_string()); - - if has_good_pos && has_good_name { - world_removed = true; - None - } else { - if !has_good_pos { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: param.stable_ptr().untyped(), - message: "The IWorldDispatcher parameter must be the first \ - parameter of the function (self excluded)." - .to_string(), - severity: Severity::Error, - }); - } + .filter_map(|param| { + let (name, _, param_type) = syntax_utils::get_parameter_info(db, param.clone()); - if !has_good_name { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: param.stable_ptr().untyped(), - message: "The IWorldDispatcher parameter must be named 'world'." - .to_string(), - severity: Severity::Error, - }); - } - Some(param.as_syntax_node().get_text(db)) - } + // If the param is `IWorldDispatcher`, we don't need to keep it in the param list + // as it is flatten in the first statement. + if world_param::is_world_param(&name, ¶m_type) { + None } else { Some(param.as_syntax_node().get_text(db)) } }) .collect::>(); - if has_multiple_world_dispatchers { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: diagnostic_item, - message: "Only one parameter of type IWorldDispatcher is allowed.".to_string(), - severity: Severity::Error, - }); - } - - if has_ref_self && !self.do_allow_ref_self { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: diagnostic_item, - message: "Functions of dojo::contract cannot have 'ref self' parameter." - .to_string(), - severity: Severity::Error, - }); - } - - if add_self { - params.insert(0, "self: @ContractState".to_string()); + match world_injection { + WorldParamInjectionKind::None | WorldParamInjectionKind::View => { + params.insert(0, "self: @ContractState".to_string()); + } + WorldParamInjectionKind::External => { + params.insert(0, "ref self: ContractState".to_string()); + } } - (params.join(", "), add_self, world_removed) + (params.join(", "), world_injection != WorldParamInjectionKind::None) } /// Rewrites function statements by adding the reading of `world` at first statement. @@ -493,21 +379,23 @@ impl DojoContract { ) -> Vec { let mut rewritten_fn = RewriteNode::from_ast(&fn_ast); - let (params_str, self_added, world_removed) = self.rewrite_parameters( + let (params_str, was_world_injected) = self.rewrite_parameters( db, fn_ast.declaration(db).signature(db).parameters(db), fn_ast.stable_ptr().untyped(), ); - if self_added || world_removed { - let rewritten_params = rewritten_fn - .modify_child(db, ast::FunctionWithBody::INDEX_DECLARATION) - .modify_child(db, ast::FunctionDeclaration::INDEX_SIGNATURE) - .modify_child(db, ast::FunctionSignature::INDEX_PARAMETERS); - rewritten_params.set_str(params_str); - } - - if world_removed { + // We always rewrite the params as the self parameter is added based on the + // world mutability. + let rewritten_params = rewritten_fn + .modify_child(db, ast::FunctionWithBody::INDEX_DECLARATION) + .modify_child(db, ast::FunctionDeclaration::INDEX_SIGNATURE) + .modify_child(db, ast::FunctionSignature::INDEX_PARAMETERS); + rewritten_params.set_str(params_str); + + // If the world was injected, we also need to rewrite the statements of the function + // to ensure the `world` injection is effective. + if was_world_injected { let rewritten_statements = rewritten_fn .modify_child(db, ast::FunctionWithBody::INDEX_BODY) .modify_child(db, ast::ExprBlock::INDEX_STATEMENTS); @@ -557,26 +445,3 @@ impl DojoContract { vec![RewriteNode::Copied(impl_ast.as_syntax_node())] } } - -/// Extract the allow_ref_self attribute. -pub(crate) fn extract_allow_ref_self( - allow_ref_self_attr: Option<&Attribute>, - db: &dyn SyntaxGroup, -) -> Option { - 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) - } - _ => None, - }, - _ => None, - } -} diff --git a/crates/dojo-lang/src/interface.rs b/crates/dojo-lang/src/interface.rs index 54205335c0..fc9bf2ef1b 100644 --- a/crates/dojo-lang/src/interface.rs +++ b/crates/dojo-lang/src/interface.rs @@ -5,6 +5,9 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, ids, Terminal, TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use crate::syntax::self_param; +use crate::syntax::world_param::{self, WorldParamInjectionKind}; + pub struct DojoInterface { diagnostics: Vec, } @@ -77,9 +80,7 @@ impl DojoInterface { } } - /// Rewrites parameter list by adding `self` parameter if missing. - /// - /// Reports an error in case of `ref self` as systems are supposed to be 100% stateless. + /// Rewrites parameter list by adding `self` parameter based on the `world` parameter. pub fn rewrite_parameters( &mut self, db: &dyn SyntaxGroup, @@ -92,50 +93,29 @@ impl DojoInterface { .map(|e| e.as_syntax_node().get_text(db)) .collect::>(); - let mut need_to_add_self = true; - if !params.is_empty() { - let first_param = param_list.elements(db)[0].clone(); - let param_name = first_param.name(db).text(db).to_string(); - - if param_name.eq(&"self".to_string()) { - let param_modifiers = first_param - .modifiers(db) - .elements(db) - .iter() - .map(|e| e.as_syntax_node().get_text(db).trim().to_string()) - .collect::>(); - - let param_type = first_param - .type_clause(db) - .ty(db) - .as_syntax_node() - .get_text(db) - .trim() - .to_string(); - - if param_modifiers.contains(&"ref".to_string()) - && param_type.eq(&"TContractState".to_string()) - { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: diagnostic_item, - message: "Functions of dojo::interface cannot have `ref self` parameter." - .to_string(), - severity: Severity::Error, - }); + self_param::check_parameter(db, ¶m_list, diagnostic_item, &mut self.diagnostics); - need_to_add_self = false; - } - - if param_type.eq(&"@TContractState".to_string()) { - need_to_add_self = false; - } + let world_injection = world_param::parse_world_injection( + db, + param_list, + diagnostic_item, + &mut self.diagnostics, + ); + + match world_injection { + WorldParamInjectionKind::None => { + params.insert(0, "self: @TContractState".to_string()); + } + WorldParamInjectionKind::View => { + params.remove(0); + params.insert(0, "self: @TContractState".to_string()); + } + WorldParamInjectionKind::External => { + params.remove(0); + params.insert(0, "ref self: TContractState".to_string()); } }; - if need_to_add_self { - params.insert(0, "self: @TContractState".to_string()); - } - params.join(", ") } @@ -151,11 +131,13 @@ impl DojoInterface { .modify_child(db, ast::FunctionDeclaration::INDEX_SIGNATURE) .modify_child(db, ast::FunctionSignature::INDEX_PARAMETERS); - rewritten_params.set_str(self.rewrite_parameters( + let params_str = self.rewrite_parameters( db, fn_ast.declaration(db).signature(db).parameters(db), fn_ast.stable_ptr().untyped(), - )); + ); + + rewritten_params.set_str(params_str); vec![rewritten_fn] } } diff --git a/crates/dojo-lang/src/lib.rs b/crates/dojo-lang/src/lib.rs index b76ea602c8..4f7e0e4bca 100644 --- a/crates/dojo-lang/src/lib.rs +++ b/crates/dojo-lang/src/lib.rs @@ -13,6 +13,7 @@ pub mod model; pub mod plugin; pub mod print; pub mod semantics; +pub mod syntax; pub(crate) mod version; // Copy of non pub functions from scarb + extension. diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index c23d1a35a0..f7fbef26bb 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -85,81 +85,54 @@ trait IEmptyTrait; trait IFaultyTrait { const ONE: u8; - fn do_ref_self(ref self: TContractState); - #[my_attr] fn do_with_attrs(p1: u8) -> u16; } -#[starknet::interface] -trait IAllowedRefSelf { - fn spawn(ref self: T); -} - -#[dojo::contract(allow_ref_self)] -mod ContractAllowedRefSelf { - #[abi(embed_v0)] - impl AllowedImpl of IAllowedRefSelf { - fn spawn(ref self: ContractState) {} - } -} - #[dojo::interface] trait INominalTrait { - fn do_no_param(); - fn do_no_param_but_self(self: @TContractState); - fn do_params(p1: dojo_examples::models::Direction, p2: u8); - fn do_params_and_self(self: @TContractState, p2: u8); - fn do_return_value(p1: u8) -> u16; + fn do_no_param() -> felt252; + fn do_no_param_but_world(world: @IWorldDispatcher) -> felt252; + fn do_no_param_but_world_ref(ref world: IWorldDispatcher) -> felt252; + fn do_params_no_world(p1: felt252, p2: u8) -> felt252; + fn do_params_and_world(world: @IWorldDispatcher, p2: u8) -> felt252; + fn do_params_and_world_ref(ref world: IWorldDispatcher, p2: u8) -> felt252; } #[dojo::interface] -trait IWorldTrait { +trait IFaultyTrait { + fn do_with_self(self: @ContractState) -> felt252; fn do_with_ref_self(ref self: ContractState) -> felt252; fn do_with_several_world_dispatchers( - world: IWorldDispatcher, vec: Vec2, another_world: IWorldDispatcher - ) -> felt252; - fn do_with_world_not_named_world(another_world: IWorldDispatcher) -> felt252; - fn do_with_self_and_world_not_named_world( - self: @ContractState, another_world: IWorldDispatcher - ); - fn do_with_world_not_first(vec: Vec2, world: IWorldDispatcher) -> felt252; - fn do_with_self_and_world_not_first( - self: @ContractState, vec: Vec2, world: IWorldDispatcher + world: @IWorldDispatcher, vec: Vec2, ref another_world: IWorldDispatcher ) -> felt252; + fn do_with_world_not_named_world(another_world: @IWorldDispatcher) -> felt252; + fn do_with_world_not_first(vec: Vec2, ref world: IWorldDispatcher) -> felt252; } #[dojo::contract] mod MyFaultyContract { #[abi(embed_v0)] - impl TestWorldImpl of IWorldTrait { - fn do_with_ref_self(ref self: ContractState) -> felt252 { - 'land' - } - - fn do_with_several_world_dispatchers( - world: IWorldDispatcher, vec: Vec2, another_world: IWorldDispatcher - ) -> felt252 { + impl TestFaultyImpl of IFaultyTrait { + fn do_with_self(ref self: ContractState) -> felt252 { 'land' } - fn do_with_world_not_named_world(another_world: IWorldDispatcher) -> felt252 { + fn do_with_ref_self(ref self: ContractState) -> felt252 { 'land' } - fn do_with_self_and_world_not_named_world( - self: @ContractState, another_world: IWorldDispatcher + fn do_with_several_world_dispatchers( + world: @IWorldDispatcher, vec: Vec2, ref another_world: IWorldDispatcher ) -> felt252 { 'land' } - fn do_with_world_not_first(vec: Vec2, world: IWorldDispatcher) -> felt252 { + fn do_with_world_not_named_world(another_world: @IWorldDispatcher) -> felt252 { 'land' } - fn do_with_self_and_world_not_first( - self: @ContractState, vec: Vec2, world: IWorldDispatcher - ) -> felt252 { + fn do_with_world_not_first(vec: Vec2, ref world: IWorldDispatcher) -> felt252 { 'land' } } @@ -173,22 +146,28 @@ mod MyNominalContract { } #[abi(embed_v0)] - impl TestWorldImpl of IWorldTrait { - fn do(vec: Vec2) -> felt252 { + impl TestNominalImpl of INominalTrait { + fn do_no_param() -> felt252 { 'land' } - fn do_with_self(self: @ContractState, vec: Vec2) -> felt252 { + fn do_no_param_but_world(world: @IWorldDispatcher) -> felt252 { 'land' } - fn do_with_world_first(world: IWorldDispatcher, vec: Vec2) -> felt252 { + fn do_no_param_but_world_ref(ref world: IWorldDispatcher) -> felt252 { 'land' } - fn do_with_self_and_world_first( - self: @ContractState, world: IWorldDispatcher, vec: Vec2 - ) -> felt252 { + fn do_params_no_world(p1: felt252, p2: u8) -> felt252 { + 'land' + } + + fn do_params_and_world(world: @IWorldDispatcher, p2: u8) -> felt252 { + 'land' + } + + fn do_params_and_world_ref(ref world: IWorldDispatcher, p2: u8) -> felt252 { 'land' } } @@ -347,40 +326,40 @@ error: Anything other than functions is not supported in a dojo::interface const ONE: u8; ^************^ -error: Functions of dojo::interface cannot have `ref self` parameter. - --> test_src/lib.cairo:82:5 - fn do_ref_self(ref self: TContractState); - ^***************************************^ +error: In a dojo contract or interface, you should use `world: @IWorldDispatcher` instead of `self: @ContractState`. + --> test_src/lib.cairo:98:5 + fn do_with_self(self: @ContractState) -> felt252; + ^***********************************************^ -error: Functions of dojo::contract cannot have 'ref self' parameter. - --> test_src/lib.cairo:130:9 - fn do_with_ref_self(ref self: ContractState) -> felt252 { - ^*******************************************************^ +error: In a dojo contract or interface, you should use `ref world: IWorldDispatcher` instead of `ref self: ContractState`. + --> test_src/lib.cairo:99:5 + fn do_with_ref_self(ref self: ContractState) -> felt252; + ^******************************************************^ -error: Only one parameter of type IWorldDispatcher is allowed. - --> test_src/lib.cairo:134:9 - fn do_with_several_world_dispatchers( - ^***********************************^ +error: World parameter must be the first parameter. + --> test_src/lib.cairo:104:5 + fn do_with_world_not_first(vec: Vec2, ref world: IWorldDispatcher) -> felt252; + ^****************************************************************************^ -error: The IWorldDispatcher parameter must be named 'world'. - --> test_src/lib.cairo:140:42 - fn do_with_world_not_named_world(another_world: IWorldDispatcher) -> felt252 { - ^*****************************^ +error: In a dojo contract or interface, you should use `ref world: IWorldDispatcher` instead of `ref self: ContractState`. + --> test_src/lib.cairo:111:9 + fn do_with_self(ref self: ContractState) -> felt252 { + ^***************************************************^ -error: The IWorldDispatcher parameter must be named 'world'. - --> test_src/lib.cairo:145:35 - self: @ContractState, another_world: IWorldDispatcher - ^*****************************^ +error: In a dojo contract or interface, you should use `ref world: IWorldDispatcher` instead of `ref self: ContractState`. + --> test_src/lib.cairo:115:9 + fn do_with_ref_self(ref self: ContractState) -> felt252 { + ^*******************************************************^ -error: The IWorldDispatcher parameter must be the first parameter of the function (self excluded). - --> test_src/lib.cairo:150:47 - fn do_with_world_not_first(vec: Vec2, world: IWorldDispatcher) -> felt252 { - ^*********************^ +error: World parameter must be the first parameter. + --> test_src/lib.cairo:129:9 + fn do_with_world_not_first(vec: Vec2, ref world: IWorldDispatcher) -> felt252 { + ^*****************************************************************************^ -error: The IWorldDispatcher parameter must be the first parameter of the function (self excluded). - --> test_src/lib.cairo:155:46 - self: @ContractState, vec: Vec2, world: IWorldDispatcher - ^*********************^ +error: World parameter must be a snapshot if `ref` is not used. + --> test_src/lib.cairo:180:5 + fn dojo_init( + ^***********^ error: Unsupported attribute. --> test_src/lib.cairo:1:1 @@ -408,32 +387,27 @@ error: Unsupported attribute. ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:84:5 + --> test_src/lib.cairo:82:5 #[my_attr] ^********^ error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ @@ -673,217 +647,172 @@ error: Unsupported attribute. ^***************^ error: Unknown inline item macro: 'component'. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:95:5 - #[abi(embed_v0)] - ^**************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unsupported attribute. - --> test_src/lib.cairo:93:1 -#[dojo::contract(allow_ref_self)] -^*******************************^ - -error: Unknown inline item macro: 'component'. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:128:5 + --> test_src/lib.cairo:109:5 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:126:1 + --> test_src/lib.cairo:107:1 #[dojo::contract] ^***************^ error: Unknown inline item macro: 'component'. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:169:5 + --> test_src/lib.cairo:142:5 #[abi(embed_v0)] ^**************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:162:1 + --> test_src/lib.cairo:135:1 #[dojo::contract] ^***************^ error: Unknown inline item macro: 'component'. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:199:1 + --> test_src/lib.cairo:178:1 #[dojo::contract] ^***************^ error: Unknown inline item macro: 'component'. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ error: Unsupported attribute. - --> test_src/lib.cairo:216:1 + --> test_src/lib.cairo:195:1 #[dojo::contract] ^***************^ @@ -901,11 +830,6 @@ mod testcomponent2 { struct Storage {} } -#[starknet::interface] -trait IAllowedRefSelf { - fn spawn(ref self: T); -} - #[starknet::contract] mod spawn { use dojo::world; @@ -1210,95 +1134,31 @@ impl EventDrop of core::traits::Drop::; #[starknet::interface] trait IFaultyTrait { - fn do_ref_self(ref self: TContractState); - #[my_attr] fn do_with_attrs(self: @TContractState, p1: u8) -> u16; } - #[starknet::contract] - mod ContractAllowedRefSelf { - use dojo::world; - use dojo::world::IWorldDispatcher; - use dojo::world::IWorldDispatcherTrait; - use dojo::world::IWorldProvider; - use dojo::world::IDojoResourceProvider; - - #[abi(embed_v0)] - impl DojoResourceProviderImpl of IDojoResourceProvider { - fn dojo_resource(self: @ContractState) -> felt252 { - 'ContractAllowedRefSelf' - } - } - - #[abi(embed_v0)] - impl WorldProviderImpl of IWorldProvider { - fn world(self: @ContractState) -> IWorldDispatcher { - self.world_dispatcher.read() - } - } - - #[abi(embed_v0)] - impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; - - #[abi(embed_v0)] - impl AllowedImpl of IAllowedRefSelf { - fn spawn(ref self: ContractState) {} - } - - #[starknet::interface] - trait IDojoInit { - fn dojo_init(self: @ContractState); - } - - #[abi(embed_v0)] - impl IDojoInitImpl of IDojoInit { - fn dojo_init(self: @ContractState) { - assert(starknet::get_caller_address() == self.world().contract_address, 'Only world can init'); - } - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - UpgradeableEvent: dojo::components::upgradeable::upgradeable::Event, - } - - #[storage] - struct Storage { - world_dispatcher: IWorldDispatcher, - #[substorage(v0)] - upgradeable: dojo::components::upgradeable::upgradeable::Storage, - } -impl EventDrop of core::traits::Drop::; - - } - #[starknet::interface] trait INominalTrait { - fn do_no_param(self: @TContractState); - fn do_no_param_but_self(self: @TContractState); - fn do_params(self: @TContractState, p1: dojo_examples::models::Direction, p2: u8); - fn do_params_and_self(self: @TContractState, p2: u8); - fn do_return_value(self: @TContractState, p1: u8) -> u16; + fn do_no_param(self: @TContractState) -> felt252; + fn do_no_param_but_world(self: @TContractState) -> felt252; + fn do_no_param_but_world_ref(ref self: TContractState) -> felt252; + fn do_params_no_world(self: @TContractState, p1: felt252, p2: u8) -> felt252; + fn do_params_and_world(self: @TContractState, p2: u8) -> felt252; + fn do_params_and_world_ref(ref self: TContractState, p2: u8) -> felt252; } #[starknet::interface] - trait IWorldTrait { - fn do_with_ref_self(self: @TContractState, ref self: ContractState) -> felt252; + trait IFaultyTrait { + fn do_with_self(self: @TContractState, self: @ContractState) -> felt252; + fn do_with_ref_self(self: @TContractState, ref self: ContractState) -> felt252; fn do_with_several_world_dispatchers( -self: @TContractState, world: IWorldDispatcher, vec: Vec2, another_world: IWorldDispatcher - ) -> felt252; - fn do_with_world_not_named_world(self: @TContractState, another_world: IWorldDispatcher) -> felt252; - fn do_with_self_and_world_not_named_world( -self: @TContractState, self: @ContractState, another_world: IWorldDispatcher - ); - fn do_with_world_not_first(self: @TContractState, vec: Vec2, world: IWorldDispatcher) -> felt252; - fn do_with_self_and_world_not_first( -self: @TContractState, self: @ContractState, vec: Vec2, world: IWorldDispatcher +self: @TContractState, vec: Vec2, ref another_world: IWorldDispatcher ) -> felt252; + fn do_with_world_not_named_world(self: @TContractState, another_world: @IWorldDispatcher) -> felt252; + fn do_with_world_not_first(self: @TContractState, vec: Vec2, ref world: IWorldDispatcher) -> felt252; } @@ -1328,34 +1188,27 @@ self: @TContractState, self: @ContractState, vec: Vec2, world: IWorldDis impl UpgradableImpl = dojo::components::upgradeable::upgradeable::UpgradableImpl; #[abi(embed_v0)] - impl TestWorldImpl of IWorldTrait { - fn do_with_ref_self(ref self: ContractState) -> felt252 { + impl TestFaultyImpl of IFaultyTrait { + fn do_with_self(self: @ContractState, ref self: ContractState) -> felt252 { 'land' } - fn do_with_several_world_dispatchers( -self: @ContractState, world: IWorldDispatcher, vec: Vec2, another_world: IWorldDispatcher - ) -> felt252 { - 'land' - } - - fn do_with_world_not_named_world(self: @ContractState, another_world: IWorldDispatcher) -> felt252 { + fn do_with_ref_self(self: @ContractState, ref self: ContractState) -> felt252 { 'land' } - fn do_with_self_and_world_not_named_world( - self: @ContractState, another_world: IWorldDispatcher + fn do_with_several_world_dispatchers( +self: @ContractState, vec: Vec2, ref another_world: IWorldDispatcher ) -> felt252 { +let world = self.world_dispatcher.read(); 'land' } - fn do_with_world_not_first(self: @ContractState, vec: Vec2, world: IWorldDispatcher) -> felt252 { + fn do_with_world_not_named_world(self: @ContractState, another_world: @IWorldDispatcher) -> felt252 { 'land' } - fn do_with_self_and_world_not_first( - self: @ContractState, vec: Vec2, world: IWorldDispatcher - ) -> felt252 { + fn do_with_world_not_first(self: @ContractState, vec: Vec2) -> felt252 { 'land' } } @@ -1419,23 +1272,31 @@ impl EventDrop of core::traits::Drop::; } #[abi(embed_v0)] - impl TestWorldImpl of IWorldTrait { - fn do(self: @ContractState, vec: Vec2) -> felt252 { + impl TestNominalImpl of INominalTrait { + fn do_no_param(self: @ContractState) -> felt252 { + 'land' + } + + fn do_no_param_but_world(self: @ContractState) -> felt252 { +let world = self.world_dispatcher.read(); + 'land' + } + + fn do_no_param_but_world_ref(ref self: ContractState) -> felt252 { +let world = self.world_dispatcher.read(); 'land' } - fn do_with_self(self: @ContractState, vec: Vec2) -> felt252 { + fn do_params_no_world(self: @ContractState, p1: felt252, p2: u8) -> felt252 { 'land' } - fn do_with_world_first(self: @ContractState, vec: Vec2) -> felt252 { + fn do_params_and_world(self: @ContractState, p2: u8) -> felt252 { let world = self.world_dispatcher.read(); 'land' } - fn do_with_self_and_world_first( - self: @ContractState, vec: Vec2 - ) -> felt252 { + fn do_params_and_world_ref(ref self: ContractState, p2: u8) -> felt252 { let world = self.world_dispatcher.read(); 'land' } diff --git a/crates/dojo-lang/src/syntax/mod.rs b/crates/dojo-lang/src/syntax/mod.rs new file mode 100644 index 0000000000..cfd40715a2 --- /dev/null +++ b/crates/dojo-lang/src/syntax/mod.rs @@ -0,0 +1,3 @@ +pub mod self_param; +pub mod utils; +pub mod world_param; diff --git a/crates/dojo-lang/src/syntax/self_param.rs b/crates/dojo-lang/src/syntax/self_param.rs new file mode 100644 index 0000000000..2f21ae7522 --- /dev/null +++ b/crates/dojo-lang/src/syntax/self_param.rs @@ -0,0 +1,49 @@ +use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_diagnostics::Severity; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, ids}; + +use crate::syntax::utils as syntax_utils; + +const SELF_PARAM_NAME: &str = "self"; + +/// Checks if the given function parameter is using `self` instead of `world` param. +/// Adds diagnostic if that case. +/// +/// # Arguments +/// +/// - `db` - The syntax group. +/// - `param_list` - The parameter list of the function. +/// - `fn_diagnostic_item` - The diagnostic item of the function. +/// - `diagnostics` - The diagnostics vector. +pub fn check_parameter( + db: &dyn SyntaxGroup, + param_list: &ast::ParamList, + fn_diagnostic_item: ids::SyntaxStablePtrId, + diagnostics: &mut Vec, +) { + if param_list.elements(db).is_empty() { + return; + } + + let param_0 = param_list.elements(db)[0].clone(); + let (name, modifier, _) = syntax_utils::get_parameter_info(db, param_0.clone()); + + if name.eq(SELF_PARAM_NAME) { + let (expected, actual) = if modifier.eq(&"ref".to_string()) { + ("ref world: IWorldDispatcher", "ref self: ContractState") + } else { + ("world: @IWorldDispatcher", "self: @ContractState") + }; + + diagnostics.push(PluginDiagnostic { + stable_ptr: fn_diagnostic_item, + message: format!( + "In a dojo contract or interface, you should use `{}` instead of `{}`.", + expected, actual + ) + .to_string(), + severity: Severity::Error, + }); + } +} diff --git a/crates/dojo-lang/src/syntax/utils.rs b/crates/dojo-lang/src/syntax/utils.rs new file mode 100644 index 0000000000..b4bf5298a1 --- /dev/null +++ b/crates/dojo-lang/src/syntax/utils.rs @@ -0,0 +1,20 @@ +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; + +/// Gets the name, modifiers and type of a function parameter. +/// +/// # Arguments +/// +/// * `db` - The syntax group. +/// * `param` - The parameter. +/// +/// # Returns +/// +/// * A tuple containing the name, modifiers and type of the parameter. +pub fn get_parameter_info(db: &dyn SyntaxGroup, param: ast::Param) -> (String, String, String) { + let name = param.name(db).text(db).trim().to_string(); + let modifiers = param.modifiers(db).as_syntax_node().get_text(db).trim().to_string(); + let param_type = param.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(); + + (name, modifiers, param_type) +} diff --git a/crates/dojo-lang/src/syntax/world_param.rs b/crates/dojo-lang/src/syntax/world_param.rs new file mode 100644 index 0000000000..aac7939770 --- /dev/null +++ b/crates/dojo-lang/src/syntax/world_param.rs @@ -0,0 +1,92 @@ +use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_diagnostics::Severity; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, ids}; + +use super::utils as syntax_utils; + +const WORLD_PARAM_NAME: &str = "world"; +const WORLD_PARAM_TYPE: &str = "IWorldDispatcher"; +const WORLD_PARAM_TYPE_SNAPSHOT: &str = "@IWorldDispatcher"; + +#[derive(Debug, PartialEq, Eq)] +pub enum WorldParamInjectionKind { + None, + View, + External, +} + +/// Checks if the given parameter is the `world` parameter. +/// +/// The `world` must be named `world`, and be placed first in the argument list. +pub fn is_world_param(param_name: &str, param_type: &str) -> bool { + param_name == WORLD_PARAM_NAME + && (param_type == WORLD_PARAM_TYPE || param_type == WORLD_PARAM_TYPE_SNAPSHOT) +} + +/// Extracts the state mutability of a function from the `world` parameter. +/// +/// Checks if the function has only one `world` parameter (or None). +/// The `world` must be named `world`, and be placed first in the argument list. +/// +/// `fn func1(ref world)` // would be external. +/// `fn func2(world)` // would be view. +/// `fn func3()` // would be view. +/// +/// Returns +/// * The [`WorldParamInjectionKind`] determined from the function's params list. +pub fn parse_world_injection( + db: &dyn SyntaxGroup, + param_list: ast::ParamList, + fn_diagnostic_item: ids::SyntaxStablePtrId, + diagnostics: &mut Vec, +) -> WorldParamInjectionKind { + let mut has_world_injected = false; + let mut injection_kind = WorldParamInjectionKind::None; + + param_list.elements(db).iter().enumerate().for_each(|(idx, param)| { + let (name, modifiers, param_type) = syntax_utils::get_parameter_info(db, param.clone()); + + if !is_world_param(&name, ¶m_type) { + return; + } + + if has_world_injected { + diagnostics.push(PluginDiagnostic { + stable_ptr: fn_diagnostic_item, + message: "Only one world parameter is allowed".to_string(), + severity: Severity::Error, + }); + + return; + } else { + has_world_injected = true; + } + + if idx != 0 { + diagnostics.push(PluginDiagnostic { + stable_ptr: fn_diagnostic_item, + message: "World parameter must be the first parameter.".to_string(), + severity: Severity::Error, + }); + + return; + } + + if modifiers.contains(&"ref".to_string()) { + injection_kind = WorldParamInjectionKind::External; + } else { + injection_kind = WorldParamInjectionKind::View; + + if param_type == WORLD_PARAM_TYPE { + diagnostics.push(PluginDiagnostic { + stable_ptr: fn_diagnostic_item, + message: "World parameter must be a snapshot if `ref` is not used.".to_string(), + severity: Severity::Error, + }); + } + } + }); + + injection_kind +} diff --git a/crates/torii/types-test/src/contracts.cairo b/crates/torii/types-test/src/contracts.cairo index 821e8f957a..df0ef8be39 100644 --- a/crates/torii/types-test/src/contracts.cairo +++ b/crates/torii/types-test/src/contracts.cairo @@ -1,9 +1,9 @@ use starknet::{ContractAddress, ClassHash}; -#[starknet::interface] -trait IRecords { - fn create(self: @TContractState, num_records: u8); - fn delete(self: @TContractState, record_id: u32); +#[dojo::interface] +trait IRecords { + fn create(ref world: IWorldDispatcher, num_records: u8); + fn delete(ref world: IWorldDispatcher, record_id: u32); } #[dojo::contract] @@ -33,8 +33,7 @@ mod records { #[abi(embed_v0)] impl RecordsImpl of IRecords { - fn create(self: @ContractState, num_records: u8) { - let world = self.world_dispatcher.read(); + fn create(ref world: IWorldDispatcher, num_records: u8) { let mut record_idx = 0; loop { @@ -118,7 +117,7 @@ mod records { return (); } // Implemment fn delete, input param: record_id - fn delete(self: @ContractState, record_id: u32) { + fn delete(ref world: IWorldDispatcher, record_id: u32) { let world = self.world_dispatcher.read(); let (record, record_sibling) = get!(world, record_id, (Record, RecordSibling)); let subrecord_id = record_id + 1; diff --git a/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json b/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json index 21aed968a7..0882f7c56d 100644 --- a/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json +++ b/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json @@ -182,7 +182,7 @@ "name": "spawn", "inputs": [], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -194,7 +194,7 @@ } ], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -206,6 +206,17 @@ } ], "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_player_position", + "inputs": [], + "outputs": [ + { + "type": "dojo_examples::models::Position" + } + ], "state_mutability": "view" } ] diff --git a/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json b/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json index 21aed968a7..0882f7c56d 100644 --- a/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json +++ b/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json @@ -182,7 +182,7 @@ "name": "spawn", "inputs": [], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -194,7 +194,7 @@ } ], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -206,6 +206,17 @@ } ], "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_player_position", + "inputs": [], + "outputs": [ + { + "type": "dojo_examples::models::Position" + } + ], "state_mutability": "view" } ] diff --git a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml index 09f30e5dfa..405be86d28 100644 --- a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml +++ b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767" -original_class_hash = "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767" +class_hash = "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d" +original_class_hash = "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d" base_class_hash = "0x0" abi = "manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json" reads = [] diff --git a/examples/spawn-and-move/manifests/dev/manifest.json b/examples/spawn-and-move/manifests/dev/manifest.json index ecf5e24f79..0b6a6af2fb 100644 --- a/examples/spawn-and-move/manifests/dev/manifest.json +++ b/examples/spawn-and-move/manifests/dev/manifest.json @@ -1020,8 +1020,8 @@ { "kind": "DojoContract", "address": "0x5c70a663d6b48d8e4c6aaa9572e3735a732ac3765700d470463e670587852af", - "class_hash": "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767", - "original_class_hash": "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767", + "class_hash": "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d", + "original_class_hash": "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d", "base_class_hash": "0x22f3e55b61d86c2ac5239fa3b3b8761f26b9a5c0b5f61ddbd5d756ced498b46", "abi": [ { @@ -1207,7 +1207,7 @@ "name": "spawn", "inputs": [], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -1219,7 +1219,7 @@ } ], "outputs": [], - "state_mutability": "view" + "state_mutability": "external" }, { "type": "function", @@ -1231,6 +1231,17 @@ } ], "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_player_position", + "inputs": [], + "outputs": [ + { + "type": "dojo_examples::models::Position" + } + ], "state_mutability": "view" } ] diff --git a/examples/spawn-and-move/manifests/dev/manifest.toml b/examples/spawn-and-move/manifests/dev/manifest.toml index 0e32561e97..601c02ab1b 100644 --- a/examples/spawn-and-move/manifests/dev/manifest.toml +++ b/examples/spawn-and-move/manifests/dev/manifest.toml @@ -22,8 +22,8 @@ name = "dojo::base::base" [[contracts]] kind = "DojoContract" address = "0x5c70a663d6b48d8e4c6aaa9572e3735a732ac3765700d470463e670587852af" -class_hash = "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767" -original_class_hash = "0x6d905953360cf18e3393d128c6ced40b38fc83b033412c8541fd4aba59d2767" +class_hash = "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d" +original_class_hash = "0x69ba4e0f7a03ae24f85aad88bd1a6b4eab5395474bbb6717803ffeb5aa13b8d" base_class_hash = "0x22f3e55b61d86c2ac5239fa3b3b8761f26b9a5c0b5f61ddbd5d756ced498b46" abi = "manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json" reads = [] diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 65dc16c55c..143696ed80 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -2,9 +2,10 @@ use dojo_examples::models::{Direction, Position, Vec2}; #[dojo::interface] trait IActions { - fn spawn(); - fn move(direction: Direction); - fn set_player_config(name: ByteArray); + fn spawn(ref world: IWorldDispatcher); + fn move(ref world: IWorldDispatcher, direction: Direction); + fn set_player_config(ref world: IWorldDispatcher, name: ByteArray); + fn get_player_position(world: @IWorldDispatcher) -> Position; } #[dojo::interface] @@ -61,7 +62,7 @@ mod actions { #[abi(embed_v0)] impl ActionsImpl of IActions { // ContractState is defined by system decorator expansion - fn spawn(world: IWorldDispatcher) { + fn spawn(ref world: IWorldDispatcher) { let player = get_caller_address(); let position = get!(world, player, (Position)); @@ -76,7 +77,7 @@ mod actions { ); } - fn move(world: IWorldDispatcher, direction: Direction) { + fn move(ref world: IWorldDispatcher, direction: Direction) { let player = get_caller_address(); let (mut position, mut moves) = get!(world, player, (Position, Moves)); moves.remaining -= 1; @@ -86,7 +87,7 @@ mod actions { emit!(world, (Moved { player, direction })); } - fn set_player_config(world: IWorldDispatcher, name: ByteArray) { + fn set_player_config(ref world: IWorldDispatcher, name: ByteArray) { let player = get_caller_address(); let items = array![ @@ -97,6 +98,11 @@ mod actions { set!(world, (config)); } + + fn get_player_position(world: @IWorldDispatcher) -> Position { + let player = get_caller_address(); + get!(world, player, (Position)) + } } } diff --git a/examples/spawn-and-move/src/others.cairo b/examples/spawn-and-move/src/others.cairo index 0f27d036a8..8b64df6a23 100644 --- a/examples/spawn-and-move/src/others.cairo +++ b/examples/spawn-and-move/src/others.cairo @@ -16,7 +16,7 @@ mod others { fn dojo_init( - world: IWorldDispatcher, + world: @IWorldDispatcher, actions_address: ContractAddress, actions_class: ClassHash, value: u8