From 47fc98d77b57afebab0591598f5aa7b1333adba4 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 3 Oct 2023 11:57:01 -0400 Subject: [PATCH] World deployments with deterministic addresses --- crates/dojo-core/src/lib.cairo | 3 ++ crates/dojo-core/src/test_utils.cairo | 4 +- crates/dojo-core/src/upgradable.cairo | 48 ++++++++++++++++++++++ crates/dojo-core/src/upgradable_test.cairo | 42 +++++++++++++++++++ crates/dojo-core/src/world.cairo | 21 +++++++--- 5 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 crates/dojo-core/src/upgradable.cairo create mode 100644 crates/dojo-core/src/upgradable_test.cairo diff --git a/crates/dojo-core/src/lib.cairo b/crates/dojo-core/src/lib.cairo index 450842abc6..050ba83be6 100644 --- a/crates/dojo-core/src/lib.cairo +++ b/crates/dojo-core/src/lib.cairo @@ -11,6 +11,9 @@ mod packing_test; mod world; #[cfg(test)] mod world_test; +mod upgradable; +#[cfg(test)] +mod upgradable_test; #[cfg(test)] mod test_utils; diff --git a/crates/dojo-core/src/test_utils.cairo b/crates/dojo-core/src/test_utils.cairo index eb3db0c220..e5a392eea0 100644 --- a/crates/dojo-core/src/test_utils.cairo +++ b/crates/dojo-core/src/test_utils.cairo @@ -21,11 +21,11 @@ use dojo::world::{world, IWorldDispatcher, IWorldDispatcherTrait}; /// # Returns /// * address of contract deployed fn deploy_contract(class_hash: felt252, calldata: Span) -> ContractAddress { - let (system_contract, _) = starknet::deploy_syscall( + let (contract, _) = starknet::deploy_syscall( class_hash.try_into().unwrap(), 0, calldata, false ) .unwrap(); - system_contract + contract } /// Deploy classhash and passes in world address to constructor diff --git a/crates/dojo-core/src/upgradable.cairo b/crates/dojo-core/src/upgradable.cairo new file mode 100644 index 0000000000..952b65c5ee --- /dev/null +++ b/crates/dojo-core/src/upgradable.cairo @@ -0,0 +1,48 @@ +use starknet::{ClassHash, SyscallResult, SyscallResultTrait}; +use zeroable::Zeroable; +use result::ResultTrait; +use serde::Serde; +use clone::Clone; +use traits::PartialEq; + +#[starknet::interface] +trait IUpgradeable { + fn upgrade(ref self: T, new_class_hash: ClassHash); +} + +#[derive(Clone, Drop, Serde, PartialEq, starknet::Event)] +struct Upgraded { + class_hash: ClassHash, +} + +trait UpgradeableTrait { + fn upgrade(new_class_hash: ClassHash); +} + +impl UpgradeableTraitImpl of UpgradeableTrait { + fn upgrade(new_class_hash: ClassHash) { + assert(new_class_hash.is_non_zero(), 'class_hash cannot be zero'); + starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); + } +} + +#[starknet::contract] +mod placeholder { + use starknet::{ClassHash}; + use dojo::upgradable::{IUpgradeable, UpgradeableTrait}; + + #[storage] + struct Storage {} + + #[external(v0)] + impl Upgradeable of IUpgradeable { + /// Upgrade contract implementation to new_class_hash + /// + /// # Arguments + /// + /// * `new_class_hash` - The new implementation class hahs. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + UpgradeableTrait::upgrade(new_class_hash); + } + } +} diff --git a/crates/dojo-core/src/upgradable_test.cairo b/crates/dojo-core/src/upgradable_test.cairo new file mode 100644 index 0000000000..9da4febdd9 --- /dev/null +++ b/crates/dojo-core/src/upgradable_test.cairo @@ -0,0 +1,42 @@ +use option::OptionTrait; +use starknet::ClassHash; +use traits::TryInto; + +use dojo::upgradable::{placeholder, IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use dojo::test_utils::deploy_contract; + +#[starknet::contract] +mod contract_upgrade { + #[storage] + struct Storage {} + + #[starknet::interface] + trait IQuantumLeap { + fn plz_more_tps(self: @TState) -> felt252; + } + + #[constructor] + fn constructor(ref self: ContractState) {} + + #[external(v0)] + impl QuantumLeap of IQuantumLeap { + fn plz_more_tps(self: @ContractState) -> felt252 { + 'daddy' + } + } +} + +use contract_upgrade::{IQuantumLeapDispatcher, IQuantumLeapDispatcherTrait}; + +#[test] +#[available_gas(6000000)] +fn test_upgrade() { + let placeholder_address = deploy_contract(placeholder::TEST_CLASS_HASH, array![].span()); + let upgradable_dispatcher = IUpgradeableDispatcher { contract_address: placeholder_address }; + + let new_class_hash: ClassHash = contract_upgrade::TEST_CLASS_HASH.try_into().unwrap(); + upgradable_dispatcher.upgrade(new_class_hash); + + let quantum_dispatcher = IQuantumLeapDispatcher { contract_address: placeholder_address }; + assert(quantum_dispatcher.plz_more_tps() == 'daddy', 'quantum leap failed'); +} diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index fb792c34e3..be156586bd 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -6,6 +6,7 @@ use option::OptionTrait; trait IWorld { fn model(self: @T, name: felt252) -> ClassHash; fn register_model(ref self: T, class_hash: ClassHash); + fn deploy_contract(self: @T, name: felt252, class_hash: ClassHash, calldata: Span) -> SyscallResult<(ContractAddress, Span)>; fn uuid(ref self: T) -> usize; fn emit(self: @T, keys: Array, values: Span); fn entity( @@ -29,6 +30,7 @@ trait IWorld { ) -> (Span, Span>); fn set_executor(ref self: T, contract_address: ContractAddress); fn executor(self: @T) -> ContractAddress; + fn deployment_placeholder(self: @T) -> ClassHash; fn delete_entity(ref self: T, model: felt252, keys: Span); fn is_owner(self: @T, address: ContractAddress, target: felt252) -> bool; fn grant_owner(ref self: T, address: ContractAddress, target: felt252); @@ -49,12 +51,13 @@ mod world { use starknet::{ get_caller_address, get_contract_address, get_tx_info, contract_address::ContractAddressIntoFelt252, ClassHash, Zeroable, ContractAddress, - syscalls::emit_event_syscall, SyscallResultTrait, SyscallResultTraitImpl + syscalls::{deploy_syscall, emit_event_syscall}, SyscallResult, SyscallResultTrait, SyscallResultTraitImpl }; use dojo::database; use dojo::database::index::WhereCondition; use dojo::executor::{IExecutorDispatcher, IExecutorDispatcherTrait}; + use dojo::upgradable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use dojo::world::{IWorldDispatcher, IWorld}; @@ -101,18 +104,18 @@ mod world { #[storage] struct Storage { executor_dispatcher: IExecutorDispatcher, - models: LegacyMap::, + deployment_placeholder: ClassHash, nonce: usize, + models: LegacyMap::, owners: LegacyMap::<(felt252, ContractAddress), bool>, writers: LegacyMap::<(felt252, ContractAddress), bool>, - // Tracks the calling systems name for auth purposes. - call_stack_len: felt252, - call_stack: LegacyMap::, } #[constructor] - fn constructor(ref self: ContractState, executor: ContractAddress) { + fn constructor(ref self: ContractState, executor: ContractAddress, deployment_placeholder: ClassHash) { self.executor_dispatcher.write(IExecutorDispatcher { contract_address: executor }); + self.deployment_placeholder.write(deployment_placeholder); + self .owners .write( @@ -273,6 +276,12 @@ mod world { self.models.read(name) } + fn deploy_contract(self: @ContractState, name: felt252, class_hash: ClassHash) -> (ContractAddress, Span) { + let (contract_address, _) = deploy_syscall(self.deployment_placeholder.read(), name, vec![].span(), false).unwrap_syscall(); + let upgradable_dispatcher = IUpgradeableDispatcher { contract_address }; + upgradable_dispatcher.upgrade(class_hash); + } + /// Issues an autoincremented id to the caller. /// /// # Returns