Skip to content

Commit

Permalink
Upgradeable component (#1147)
Browse files Browse the repository at this point in the history
* core: upgradeable component wip

* #[dojo::contract] merge Event, update upgradeable

* update base_test.cairo & dojo-lang plugin_test_data

* add Upgraded event in upgradeable component

* move erc20, fix build issue, delete old tests

* update erc20 to use #[dojo::contract]

* revert changes on erc20

* revert changes on erc20

* fully qualify component

* handle when Event is not defined in contract

* follow clippy recomandation

* torii: update records_contract address

* trigger CI

* trigger CI

* rebase main

* update manifest_test_data

* use right records_contract address
  • Loading branch information
notV4l authored Nov 21, 2023
1 parent 0fa1b2a commit d955c1a
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 180 deletions.
39 changes: 19 additions & 20 deletions crates/dojo-core/src/base.cairo
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
use dojo::world::IWorldDispatcher;

#[starknet::interface]
trait IBase<T> {
fn world(self: @T) -> IWorldDispatcher;
}

#[starknet::contract]
mod base {
use starknet::{ClassHash, get_caller_address};

use dojo::upgradable::{IUpgradeable, UpgradeableTrait};
use dojo::world::IWorldDispatcher;
use dojo::world::IWorldProvider;

use dojo::components::upgradeable::upgradeable as upgradeable_component;

component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
UpgradeableEvent: upgradeable_component::Event
}

#[storage]
struct Storage {
world_dispatcher: IWorldDispatcher,
#[substorage(v0)]
upgradeable: upgradeable_component::Storage,
}

#[constructor]
Expand All @@ -23,19 +29,12 @@ mod base {
}

#[external(v0)]
fn world(self: @ContractState) -> IWorldDispatcher {
self.world_dispatcher.read()
}

#[external(v0)]
impl Upgradeable of IUpgradeable<ContractState> {
/// 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);
impl WorldProviderImpl of IWorldProvider<ContractState> {
fn world(self: @ContractState) -> IWorldDispatcher {
self.world_dispatcher.read()
}
}

#[abi(embed_v0)]
impl UpgradableImpl = upgradeable_component::UpgradableImpl<ContractState>;
}
33 changes: 27 additions & 6 deletions crates/dojo-core/src/base_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use starknet::ClassHash;
use traits::TryInto;

use dojo::base::base;
use dojo::upgradable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait};
use dojo::test_utils::deploy_contract;
use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait};
use dojo::test_utils::{spawn_test_world};
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait};


#[starknet::contract]
mod contract_upgrade {
Expand All @@ -29,15 +31,34 @@ mod contract_upgrade {

use contract_upgrade::{IQuantumLeapDispatcher, IQuantumLeapDispatcherTrait};

// Utils
fn deploy_world() -> IWorldDispatcher {
spawn_test_world(array![])
}

#[test]
#[available_gas(6000000)]
fn test_upgrade() {
let base_address = deploy_contract(base::TEST_CLASS_HASH, array![].span());
let upgradable_dispatcher = IUpgradeableDispatcher { contract_address: base_address };
fn test_upgrade_from_world() {
let world = deploy_world();

let base_address = world.deploy_contract('salt', base::TEST_CLASS_HASH.try_into().unwrap());
let new_class_hash: ClassHash = contract_upgrade::TEST_CLASS_HASH.try_into().unwrap();
upgradable_dispatcher.upgrade(new_class_hash);

world.upgrade_contract(base_address, new_class_hash);

let quantum_dispatcher = IQuantumLeapDispatcher { contract_address: base_address };
assert(quantum_dispatcher.plz_more_tps() == 'daddy', 'quantum leap failed');
}

#[test]
#[available_gas(6000000)]
#[should_panic(expected: ('must be called by world', 'ENTRYPOINT_FAILED'))]
fn test_upgrade_direct() {
let world = deploy_world();

let base_address = world.deploy_contract('salt', base::TEST_CLASS_HASH.try_into().unwrap());
let new_class_hash: ClassHash = contract_upgrade::TEST_CLASS_HASH.try_into().unwrap();

let upgradeable_dispatcher = IUpgradeableDispatcher { contract_address: base_address };
upgradeable_dispatcher.upgrade(new_class_hash);
}
1 change: 1 addition & 0 deletions crates/dojo-core/src/components.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod upgradeable;
56 changes: 56 additions & 0 deletions crates/dojo-core/src/components/upgradeable.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use starknet::ClassHash;

#[starknet::interface]
trait IUpgradeable<T> {
fn upgrade(ref self: T, new_class_hash: ClassHash);
}

#[starknet::component]
mod upgradeable {
use starknet::ClassHash;
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet::syscalls::replace_class_syscall;
use dojo::world::{IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher};

#[storage]
struct Storage {}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Upgraded: Upgraded,
}

#[derive(Drop, starknet::Event)]
struct Upgraded {
class_hash: ClassHash
}

mod Errors {
const INVALID_CLASS: felt252 = 'class_hash cannot be zero';
const INVALID_CALLER: felt252 = 'must be called by world';
const INVALID_WORLD_ADDRESS: felt252 = 'invalid world address';
}

#[embeddable_as(UpgradableImpl)]
impl Upgradable<
TContractState, +HasComponent<TContractState>, +IWorldProvider<TContractState>
> of super::IUpgradeable<ComponentState<TContractState>> {
fn upgrade(ref self: ComponentState<TContractState>, new_class_hash: ClassHash) {
assert(
self.get_contract().world().contract_address.is_non_zero(),
Errors::INVALID_WORLD_ADDRESS
);
assert(
get_caller_address() == self.get_contract().world().contract_address,
Errors::INVALID_CALLER
);
assert(new_class_hash.is_non_zero(), Errors::INVALID_CLASS);

replace_class_syscall(new_class_hash).unwrap();

self.emit(Upgraded { class_hash: new_class_hash });
}
}
}
5 changes: 3 additions & 2 deletions crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ mod packing_test;
mod world;
#[cfg(test)]
mod world_test;
mod upgradable;

#[cfg(test)]
mod test_utils;

#[cfg(test)]
mod benchmarks;
mod benchmarks;

mod components;
19 changes: 0 additions & 19 deletions crates/dojo-core/src/upgradable.cairo

This file was deleted.

14 changes: 10 additions & 4 deletions crates/dojo-core/src/world.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ trait IWorld<T> {
fn revoke_writer(ref self: T, model: felt252, system: ContractAddress);
}

#[starknet::interface]
trait IWorldProvider<T> {
fn world(self: @T) -> IWorldDispatcher;
}


#[starknet::contract]
mod world {
use core::traits::TryInto;
Expand All @@ -66,9 +72,9 @@ mod world {
use dojo::database;
use dojo::database::index::WhereCondition;
use dojo::executor::{IExecutorDispatcher, IExecutorDispatcherTrait};
use dojo::upgradable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait};
use dojo::world::{IWorldDispatcher, IWorld};


use dojo::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait};

const NAME_ENTRYPOINT: felt252 =
0x0361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60;
Expand Down Expand Up @@ -404,8 +410,8 @@ mod world {
self.contract_base.read(), salt, array![].span(), false
)
.unwrap_syscall();
let upgradable_dispatcher = IUpgradeableDispatcher { contract_address };
upgradable_dispatcher.upgrade(class_hash);
let upgradeable_dispatcher = IUpgradeableDispatcher { contract_address };
upgradeable_dispatcher.upgrade(class_hash);

self.owners.write((contract_address.into(), get_caller_address()), true);

Expand Down
Loading

0 comments on commit d955c1a

Please sign in to comment.