Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial execution context implementation #392

Merged
Merged
38 changes: 21 additions & 17 deletions crates/dojo-core/src/auth/systems.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,20 @@ mod RouteAuth {

// Set status
commands::set_entity(
(route.role_id, route.resource_id).into(),
(AuthStatus { is_authorized: true })
(route.role_id, route.resource_id).into(), (AuthStatus { is_authorized: true })
);
}
}

#[system]
mod IsAccountAdmin {
use traits::Into;
use starknet::get_tx_info;
use box::BoxTrait;
use dojo_core::{auth::components::{AuthStatus, AuthRole}, integer::u250};

fn execute() -> bool {
fn execute(ctx: Context) -> bool {
// Get calling account contract address
let caller = get_tx_info().unbox().account_contract_address; // tx origin
let caller = ctx.caller_account;
let role = commands::<AuthRole>::entity(caller.into());
// Authorize if role is Admin
role.id.into() == 'Admin'
Expand All @@ -62,12 +60,22 @@ mod IsAuthorized {
use dojo_core::{auth::components::{AuthStatus, AuthRole}, integer::u250};


fn execute(target_id: u250, resource_id: u250) -> bool {
// Get component-scoped role
let maybe_scoped_role = commands::<AuthRole>::try_entity((target_id, resource_id).into());
let scoped_role = match maybe_scoped_role {
Option::Some(scoped_role) => scoped_role.id.into(),
Option::None(_) => 0,
fn execute(ctx: Context, target_id: u250, resource_id: u250) -> bool {
// Check if execution role is not set
let scoped_role = if ctx.execution_role.id == 0.into() {
// Use default component-scoped role
// TODO: use commands once parsing is fixed
let mut role = ctx
.world
.entity('AuthRole'.into(), (target_id, resource_id).into(), 0, 0);
let scoped_role = serde::Serde::<AuthRole>::deserialize(ref role);
match scoped_role {
Option::Some(scoped_role) => scoped_role.id,
Option::None(_) => 0.into(),
}
} else {
// Use the set execution role
ctx.execution_role.id
};

// Get authorization status for scoped role
Expand Down Expand Up @@ -121,9 +129,7 @@ mod GrantResource {
use dojo_core::{auth::components::AuthStatus, integer::u250};

fn execute(role_id: u250, resource_id: u250) {
commands::set_entity(
(role_id, resource_id).into(), (AuthStatus { is_authorized: true })
);
commands::set_entity((role_id, resource_id).into(), (AuthStatus { is_authorized: true }));
}
}

Expand Down Expand Up @@ -155,8 +161,6 @@ mod RevokeResource {
use dojo_core::{auth::components::AuthStatus, integer::u250};

fn execute(role_id: u250, resource_id: u250) {
commands::set_entity(
(role_id, resource_id).into(), (AuthStatus { is_authorized: false })
);
commands::set_entity((role_id, resource_id).into(), (AuthStatus { is_authorized: false }));
}
}
12 changes: 12 additions & 0 deletions crates/dojo-core/src/execution_context.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use dojo_core::interfaces::IWorldDispatcher;
use dojo_core::auth::components::AuthRole;
use dojo_core::string::ShortString;
use starknet::{ContractAddress, ClassHash};

#[derive(Copy, Drop, Serde)]
struct Context {
world: IWorldDispatcher, // Dispatcher to the world contract
caller_account: ContractAddress, // Address of the origin
caller_system: ShortString, // Name of the calling system
execution_role: AuthRole, // Authorization role used for this call
}
61 changes: 55 additions & 6 deletions crates/dojo-core/src/executor.cairo
Original file line number Diff line number Diff line change
@@ -1,25 +1,74 @@
use core::serde::Serde;
#[contract]
mod Executor {
use array::{ArrayTrait, ArrayTCloneImpl};
use array::{ArrayTrait, ArrayTCloneImpl, SpanTrait};
use serde::Serde;
use clone::Clone;
use box::BoxTrait;
use traits::Into;
use dojo_core::execution_context::Context;
use dojo_core::integer::u250;
use dojo_core::interfaces::{IWorldDispatcher, ISystemLibraryDispatcher, ISystemDispatcherTrait};
use dojo_core::auth::components::AuthRole;
use starknet::contract_address::ContractAddressIntoFelt252;
use starknet::{get_caller_address, get_tx_info};

use dojo_core::serde::SpanSerde;

const EXECUTE_ENTRYPOINT: felt252 =
0x0240060cdb34fcc260f41eac7474ee1d7c80b7e3607daff9ac67c7ea2ebb1c44;

/// Executes a System by calling its execute entrypoint.
///
/// # Arguments
///
/// * `class_hash` - The class hash of the System to execute.
/// * `execution_role` - The execution role to be assumed by the System.
/// * `execute_calldata` - The calldata to pass to the System.
///
/// # Returns
///
/// The return value of the System's execute entrypoint.
#[external]
fn execute(class_hash: starknet::ClassHash, calldata: Span<felt252>) -> Span<felt252> {
let world_address = starknet::get_caller_address();
fn execute(
class_hash: starknet::ClassHash,
execution_role: AuthRole,
mut execute_calldata: Span<felt252>
) -> Span<felt252> {
// Get the world address and instantiate the world dispatcher.
let world_address = get_caller_address();
let world = IWorldDispatcher { contract_address: world_address };

let mut calldata_arr = calldata.snapshot.clone();
calldata_arr.append(world_address.into());
// Get the caller account address
let caller_account = get_tx_info().unbox().account_contract_address;

// Get system name
let caller_system = ISystemLibraryDispatcher { class_hash }.name();

// Instantiate the execution context
let mut ctx = Context { world, caller_account, caller_system, execution_role, };

// Serialize the context
let mut calldata_arr = ArrayTrait::new();
ctx.serialize(ref calldata_arr);

// Append the execute_calldata
loop {
match execute_calldata.pop_front() {
Option::Some(val) => {
calldata_arr.append(*val);
},
Option::None(_) => {
break ();
}
};
};

// Call the system
let res = starknet::syscalls::library_call_syscall(
class_hash, EXECUTE_ENTRYPOINT, calldata_arr.span()
).unwrap_syscall();
)
.unwrap_syscall();
res
}
}
10 changes: 6 additions & 4 deletions crates/dojo-core/src/integer.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use integer::BoundedInt;
use option::OptionTrait;
use traits::{Into, TryInto};

use starknet::{ContractAddress, SyscallResult, contract_address::ContractAddressIntoFelt252, storage_access::{StorageAccess, StorageBaseAddress}};
use starknet::{
ContractAddress, SyscallResult, contract_address::ContractAddressIntoFelt252,
storage_access::{StorageAccess, StorageBaseAddress}
};

// max value of u256's high part when u250::max is converted into u256
const HIGH_BOUND: u128 = 0x3ffffffffffffffffffffffffffffff;
Expand Down Expand Up @@ -77,9 +80,8 @@ impl LegacyHashU250 of LegacyHash<u250> {
impl StorageAccessU250 of StorageAccess<u250> {
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<u250> {
Result::Ok(
Felt252TryIntoU250::try_into(
StorageAccess::read(address_domain, base)?
).expect('StorageAccessU250 - non u250')
Felt252TryIntoU250::try_into(StorageAccess::read(address_domain, base)?)
.expect('StorageAccessU250 - non u250')
)
}
#[inline(always)]
Expand Down
37 changes: 32 additions & 5 deletions crates/dojo-core/src/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
use dojo_core::{integer::u250, string::ShortString, serde::SpanSerde, storage::query::Query, auth::systems::Route};
use dojo_core::{
integer::u250, string::ShortString, serde::SpanSerde, storage::query::Query,
auth::systems::Route, auth::components::AuthRole, execution_context::Context
};
use starknet::{ClassHash, ContractAddress};
use serde::Serde;
use array::{ArrayTrait, SpanTrait};
use traits::{TryInto, Into};
use option::OptionTrait;
use starknet::contract_address::Felt252TryIntoContractAddress;


#[abi]
trait IWorld {
Expand All @@ -11,17 +20,35 @@ trait IWorld {
fn uuid() -> usize;
fn execute(name: ShortString, execute_calldata: Span<felt252>) -> Span<felt252>;
fn entity(component: ShortString, key: Query, offset: u8, length: usize) -> Span<felt252>;
fn set_entity(component: ShortString, key: Query, offset: u8, value: Span<felt252>);
fn set_entity(
context: Context, component: ShortString, key: Query, offset: u8, value: Span<felt252>
);
fn entities(component: ShortString, partition: u250) -> (Span<u250>, Span<Span<felt252>>);
fn set_executor(contract_address: ContractAddress);
fn is_authorized(system: ClassHash, component: ClassHash) -> bool;
fn is_authorized(system: ShortString, component: ShortString, execution_role: AuthRole) -> bool;
fn is_account_admin() -> bool;
fn delete_entity(component: ShortString, query: Query);
fn delete_entity(context: Context, component: ShortString, query: Query);
fn set_execution_role(role_id: u250);
fn execution_role() -> u250;
}

// TODO: Remove once Serde is derivable for dispatchers
impl IWorldDispatcherSerde of Serde<IWorldDispatcher> {
fn serialize(self: @IWorldDispatcher, ref output: Array<felt252>) {
output.append((*self.contract_address).into());
}
fn deserialize(ref serialized: Span<felt252>) -> Option<IWorldDispatcher> {
let contract_address: felt252 = *serialized.pop_front()?;
let contract_address: ContractAddress = contract_address.try_into().unwrap();
Option::Some(IWorldDispatcher { contract_address })
}
}

#[abi]
trait IExecutor {
fn execute(class_hash: ClassHash, data: Span<felt252>) -> Span<felt252>;
fn execute(
class_hash: ClassHash, execution_role: AuthRole, execute_calldata: Span<felt252>
) -> Span<felt252>;
}

#[abi]
Expand Down
1 change: 1 addition & 0 deletions crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod auth;
mod executor;
mod execution_context;
mod integer;
mod interfaces;
mod serde;
Expand Down
7 changes: 3 additions & 4 deletions crates/dojo-core/src/serde.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ use array::{ArrayTrait, SpanTrait};
use option::OptionTrait;
use serde::{Serde, serialize_array_helper, deserialize_array_helper};

impl SpanSerde<T,
impl TSerde: Serde<T>,
impl TCopy: Copy<T>,
impl TDrop: Drop<T>> of Serde<Span<T>> {
impl SpanSerde<
T, impl TSerde: Serde<T>, impl TCopy: Copy<T>, impl TDrop: Drop<T>
> of Serde<Span<T>> {
fn serialize(self: @Span<T>, ref output: Array<felt252>) {
(*self).len().serialize(ref output);
serialize_array_helper(*self, ref output);
Expand Down
4 changes: 3 additions & 1 deletion crates/dojo-core/src/storage/db.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ mod Database {

// returns a tuple of spans, first contains the entity IDs,
// second the deserialized entities themselves
fn all(class_hash: starknet::ClassHash, component: u250, partition: u250) -> (Span<u250>, Span<Span<felt252>>) {
fn all(
class_hash: starknet::ClassHash, component: u250, partition: u250
) -> (Span<u250>, Span<Span<felt252>>) {
let table = {
if partition == 0.into() {
component
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/storage/index.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ mod Index {
if idx == table_len {
break ();
}

res.append(tables::read((table, idx)));
idx += 1;
};
Expand Down
16 changes: 8 additions & 8 deletions crates/dojo-core/src/storage/kv.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ mod KeyValueStore {
use dojo_core::{integer::u250, serde::SpanSerde};

fn address(table: u250, key: u250) -> starknet::StorageBaseAddress {
starknet::storage_base_address_from_felt252(
hash::LegacyHash::hash(0x420, (table, key))
)
starknet::storage_base_address_from_felt252(hash::LegacyHash::hash(0x420, (table, key)))
}

#[view]
Expand All @@ -32,11 +30,13 @@ mod KeyValueStore {
return ();
}

value.append(
starknet::storage_read_syscall(
address_domain, starknet::storage_address_from_base_and_offset(base, offset)
).unwrap_syscall()
);
value
.append(
starknet::storage_read_syscall(
address_domain, starknet::storage_address_from_base_and_offset(base, offset)
)
.unwrap_syscall()
);

return _get(address_domain, base, ref value, offset + 1, length);
}
Expand Down
Loading