Skip to content

Commit

Permalink
expose resources through resource() function in world API
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Aug 7, 2024
1 parent 1c0e8cc commit f1e3dfe
Show file tree
Hide file tree
Showing 17 changed files with 362 additions and 296 deletions.
2 changes: 1 addition & 1 deletion crates/dojo-core/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version = 1

[[package]]
name = "dojo"
version = "0.7.3"
version = "1.0.0-alpha.4"
dependencies = [
"dojo_plugin",
]
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub mod world {
mod world_contract;
pub use world_contract::{
world, IWorld, IWorldDispatcher, IWorldDispatcherTrait, IWorldProvider,
IWorldProviderDispatcher, IWorldProviderDispatcherTrait
IWorldProviderDispatcher, IWorldProviderDispatcherTrait, Resource
};
pub(crate) use world_contract::{
IUpgradeableWorld, IUpgradeableWorldDispatcher, IUpgradeableWorldDispatcherTrait
Expand Down
30 changes: 20 additions & 10 deletions crates/dojo-core/src/tests/world.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use dojo::utils::test::{spawn_test_world, deploy_with_world_address, assert_arra
use dojo::utils::entity_id_from_keys;
use dojo::world::{
IWorldDispatcher, IWorldDispatcherTrait, world, IUpgradeableWorld, IUpgradeableWorldDispatcher,
IUpgradeableWorldDispatcherTrait
IUpgradeableWorldDispatcherTrait, Resource
};
use dojo::world::world::NamespaceRegistered;

Expand Down Expand Up @@ -441,11 +441,15 @@ fn test_contract_getter() {
'salt1', test_contract::TEST_CLASS_HASH.try_into().unwrap(), array![].span()
);

let (class_hash, _) = world.contract(selector_from_tag!("dojo-test_contract"));
assert(
class_hash == test_contract::TEST_CLASS_HASH.try_into().unwrap(),
'invalid contract class hash'
);
if let Resource::Contract((class_hash, _)) = world
.resource(selector_from_tag!("dojo-test_contract")) {
assert(
class_hash == test_contract::TEST_CLASS_HASH.try_into().unwrap(),
'invalid contract class hash'
);
} else {
core::panic_with_felt252('invalid resource type');
}
}

#[test]
Expand All @@ -454,8 +458,11 @@ fn test_model_class_hash_getter() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let (foo_class_hash, _) = world.model(Model::<Foo>::selector());
assert(foo_class_hash == foo::TEST_CLASS_HASH.try_into().unwrap(), 'foo wrong class hash');
if let Resource::Model((foo_class_hash, _)) = world.resource(Model::<Foo>::selector()) {
assert(foo_class_hash == foo::TEST_CLASS_HASH.try_into().unwrap(), 'foo wrong class hash');
} else {
core::panic_with_felt252('invalid resource type');
}
}

#[test]
Expand All @@ -465,8 +472,11 @@ fn test_legacy_model_class_hash_getter() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let (foo_class_hash, _) = world.model('Foo');
assert(foo_class_hash == foo::TEST_CLASS_HASH.try_into().unwrap(), 'foo wrong class hash');
if let Resource::Model((foo_class_hash, _)) = world.resource(Model::<Foo>::selector()) {
assert(foo_class_hash == foo::TEST_CLASS_HASH.try_into().unwrap(), 'foo wrong class hash');
} else {
core::panic_with_felt252('invalid resource type');
}
}

#[test]
Expand Down
124 changes: 57 additions & 67 deletions crates/dojo-core/src/world/world_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ use dojo::model::{ModelIndex, ResourceMetadata};
use dojo::model::{Layout};
use dojo::utils::bytearray_hash;

#[derive(Drop, starknet::Store, Serde, Default, Debug)]
pub enum Resource {
Model: (ClassHash, ContractAddress),
Contract: (ClassHash, ContractAddress),
Namespace,
World,
#[default]
Unregistered,
}

#[starknet::interface]
pub trait IWorld<T> {
fn metadata(self: @T, resource_id: felt252) -> ResourceMetadata;
fn set_metadata(ref self: T, metadata: ResourceMetadata);
fn model(self: @T, selector: felt252) -> (ClassHash, ContractAddress);
fn contract(self: @T, selector: felt252) -> (ClassHash, ContractAddress);
fn register_model(ref self: T, class_hash: ClassHash);
fn register_namespace(ref self: T, namespace: ByteArray);
fn deploy_contract(
Expand All @@ -34,6 +42,7 @@ pub trait IWorld<T> {
fn delete_entity(ref self: T, model_selector: felt252, index: ModelIndex, layout: Layout);

fn base(self: @T) -> ClassHash;
fn resource(self: @T, selector: felt252) -> Resource;

/// In Dojo, there are 2 levels of authorization: `owner` and `writer`.
/// Only accounts can own a resource while any contract can write to a resource,
Expand Down Expand Up @@ -120,7 +129,8 @@ pub mod world {
use dojo::utils::{entity_id_from_keys, bytearray_hash};

use super::{
Errors, ModelIndex, IWorldDispatcher, IWorldDispatcherTrait, IWorld, IUpgradeableWorld
Errors, ModelIndex, IWorldDispatcher, IWorldDispatcherTrait, IWorld, IUpgradeableWorld,
Resource
};

const WORLD: felt252 = 0;
Expand Down Expand Up @@ -253,29 +263,19 @@ pub mod world {
contract_base: ClassHash,
nonce: usize,
models_count: usize,
resources: Map::<felt252, ResourceData>,
resources: Map::<felt252, Resource>,
owners: Map::<(felt252, ContractAddress), bool>,
writers: Map::<(felt252, ContractAddress), bool>,
#[substorage(v0)]
config: Config::Storage,
initialized_contract: Map::<felt252, bool>,
}

#[derive(Drop, starknet::Store, Default, Debug)]
pub enum ResourceData {
Model: (ClassHash, ContractAddress),
Contract: (ClassHash, ContractAddress),
Namespace,
World,
#[default]
None,
}

#[generate_trait]
impl ResourceDataIsNoneImpl of ResourceDataIsNoneTrait {
fn is_none(self: @ResourceData) -> bool {
impl ResourceIsNoneImpl of ResourceIsNoneTrait {
fn is_none(self: @Resource) -> bool {
match self {
ResourceData::None => true,
Resource::Unregistered => true,
_ => false
}
}
Expand All @@ -286,18 +286,18 @@ pub mod world {
let creator = starknet::get_tx_info().unbox().account_contract_address;
self.contract_base.write(contract_base);

self.resources.write(WORLD, ResourceData::World);
self.resources.write(WORLD, Resource::World);
self
.resources
.write(
Model::<ResourceMetadata>::selector(),
ResourceData::Model((metadata::initial_class_hash(), metadata::initial_address()))
Resource::Model((metadata::initial_class_hash(), metadata::initial_address()))
);
self.owners.write((WORLD, creator), true);

let dojo_namespace_hash = bytearray_hash(@"__DOJO__");

self.resources.write(dojo_namespace_hash, ResourceData::Namespace);
self.resources.write(dojo_namespace_hash, Resource::Namespace);
self.owners.write((dojo_namespace_hash, creator), true);

self.config.initializer(creator);
Expand Down Expand Up @@ -472,13 +472,13 @@ pub mod world {
self: @ContractState, resource_id: felt252, contract: ContractAddress
) -> bool {
match self.resources.read(resource_id) {
ResourceData::Model((_, model_address)) => self
Resource::Model((_, model_address)) => self
.check_model_write_access(resource_id, model_address, contract),
ResourceData::Contract((_, contract_address)) => self
Resource::Contract((_, contract_address)) => self
.check_contract_write_access(resource_id, contract_address, contract),
ResourceData::Namespace => self.check_basic_write_access(resource_id, contract),
ResourceData::World => self.check_basic_write_access(WORLD, contract),
ResourceData::None => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
Resource::Namespace => self.check_basic_write_access(resource_id, contract),
Resource::World => self.check_basic_write_access(WORLD, contract),
Resource::Unregistered => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
}

Expand All @@ -500,7 +500,7 @@ pub mod world {
self: @ContractState, selector: felt252, contract: ContractAddress
) -> bool {
match self.resources.read(selector) {
ResourceData::Model((_, model_address)) => self
Resource::Model((_, model_address)) => self
.check_model_write_access(selector, model_address, contract),
_ => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
Expand All @@ -524,7 +524,7 @@ pub mod world {
self: @ContractState, selector: felt252, contract: ContractAddress
) -> bool {
match self.resources.read(selector) {
ResourceData::Contract((_, contract_address)) => self
Resource::Contract((_, contract_address)) => self
.check_contract_write_access(selector, contract_address, contract),
_ => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
Expand All @@ -548,7 +548,7 @@ pub mod world {
self: @ContractState, selector: felt252, contract: ContractAddress
) -> bool {
match self.resources.read(selector) {
ResourceData::Namespace => self.check_basic_write_access(selector, contract),
Resource::Namespace => self.check_basic_write_access(selector, contract),
_ => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
}
Expand Down Expand Up @@ -587,21 +587,21 @@ pub mod world {

match self.resources.read(selector) {
// If model is already registered, validate permission to update.
ResourceData::Model((
Resource::Model((
model_hash, model_address
)) => {
assert(self.is_account_owner(selector), Errors::OWNER_ONLY_UPDATE);
prev_class_hash = model_hash;
prev_address = model_address;
},
// new model
ResourceData::None => { self.owners.write((selector, caller), true); },
Resource::Unregistered => { self.owners.write((selector, caller), true); },
// Avoids a model name to conflict with already registered resource,
// which can cause ACL issue with current ACL implementation.
_ => core::panic_with_felt252(Errors::INVALID_MODEL_NAME)
};

self.resources.write(selector, ResourceData::Model((class_hash, address)));
self.resources.write(selector, Resource::Model((class_hash, address)));
EventEmitter::emit(
ref self,
ModelRegistered {
Expand All @@ -621,13 +621,13 @@ pub mod world {
let hash = bytearray_hash(@namespace);

match self.resources.read(hash) {
ResourceData::Namespace => {
Resource::Namespace => {
if !self.is_account_owner(hash) {
core::panic_with_felt252(Errors::NAMESPACE_ALREADY_REGISTERED);
}
},
ResourceData::None => {
self.resources.write(hash, ResourceData::Namespace);
Resource::Unregistered => {
self.resources.write(hash, Resource::Namespace);
self.owners.write((hash, caller_account), true);

EventEmitter::emit(ref self, NamespaceRegistered { namespace, hash });
Expand All @@ -636,31 +636,6 @@ pub mod world {
};
}


/// Gets the class hash of a registered model.
///
/// # Arguments
///
/// * `selector` - The keccak(name) of the model.
///
/// # Returns
///
/// * (`ClassHash`, `ContractAddress`) - The class hash and the contract address of the
/// model.
fn model(self: @ContractState, selector: felt252) -> (ClassHash, ContractAddress) {
match self.resources.read(selector) {
ResourceData::Model(m) => m,
_ => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
}

fn contract(self: @ContractState, selector: felt252) -> (ClassHash, ContractAddress) {
match self.resources.read(selector) {
ResourceData::Contract(c) => c,
_ => core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
}

/// Deploys a contract associated with the world.
///
/// # Arguments
Expand Down Expand Up @@ -710,7 +685,7 @@ pub mod world {

self.owners.write((selector, get_caller_address()), true);

self.resources.write(selector, ResourceData::Contract((class_hash, contract_address)));
self.resources.write(selector, Resource::Contract((class_hash, contract_address)));

EventEmitter::emit(
ref self,
Expand All @@ -734,12 +709,16 @@ pub mod world {
ref self: ContractState, selector: felt252, class_hash: ClassHash
) -> ClassHash {
assert(self.is_account_owner(selector), Errors::NOT_OWNER);
let (_, contract_address) = self.contract(selector);
IUpgradeableDispatcher { contract_address }.upgrade(class_hash);
EventEmitter::emit(
ref self, ContractUpgraded { class_hash, address: contract_address }
);
class_hash

if let Resource::Contract((_, contract_address)) = self.resources.read(selector) {
IUpgradeableDispatcher { contract_address }.upgrade(class_hash);
EventEmitter::emit(
ref self, ContractUpgraded { class_hash, address: contract_address }
);
class_hash
} else {
core::panic_with_felt252(Errors::INVALID_RESOURCE_SELECTOR)
}
}

/// Issues an autoincremented id to the caller.
Expand Down Expand Up @@ -890,6 +869,17 @@ pub mod world {
fn base(self: @ContractState) -> ClassHash {
self.contract_base.read()
}

/// Gets resource data from its selector.
///
/// # Arguments
/// * `selector` - the resource selector
///
/// # Returns
/// * `Resource` - the resource data associated with the selector.
fn resource(self: @ContractState, selector: felt252) -> Resource {
self.resources.read(selector)
}
}


Expand Down Expand Up @@ -1028,7 +1018,7 @@ pub mod world {
#[inline(always)]
fn is_namespace_registered(self: @ContractState, namespace_hash: felt252) -> bool {
match self.resources.read(namespace_hash) {
ResourceData::Namespace => true,
Resource::Namespace => true,
_ => false
}
}
Expand Down
Loading

0 comments on commit f1e3dfe

Please sign in to comment.