diff --git a/crates/contracts/src/components.cairo b/crates/contracts/src/components.cairo index 23acc1b07..792e4420f 100644 --- a/crates/contracts/src/components.cairo +++ b/crates/contracts/src/components.cairo @@ -1,2 +1,2 @@ mod ownable; -mod upgradeable; +mod upgradable; diff --git a/crates/contracts/src/components/upgradable.cairo b/crates/contracts/src/components/upgradable.cairo new file mode 100644 index 000000000..de162e570 --- /dev/null +++ b/crates/contracts/src/components/upgradable.cairo @@ -0,0 +1,39 @@ +use starknet::{replace_class_syscall, ClassHash}; + +#[starknet::interface] +trait IUpgradable { + fn upgrade_contract(ref self: TContractState, class_hash: ClassHash); +} + + +#[starknet::component] +mod upgradable_component { + use starknet::ClassHash; + use starknet::info::get_caller_address; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + ContractUpgrated: ContractUpgrated + } + + #[derive(Drop, starknet::Event)] + struct ContractUpgrated { + new_class_hash: ClassHash + } + + #[embeddable_as(UpgradableImpl)] + impl Upgradable< + TContractState, +HasComponent + > of super::IUpgradable> { + fn upgrade_contract( + ref self: ComponentState, class_hash: starknet::ClassHash + ) { + starknet::replace_class_syscall(class_hash); + self.emit(ContractUpgrated { new_class_hash: class_hash }); + } + } +} diff --git a/crates/contracts/src/components/upgradeable.cairo b/crates/contracts/src/components/upgradeable.cairo deleted file mode 100644 index 60088299d..000000000 --- a/crates/contracts/src/components/upgradeable.cairo +++ /dev/null @@ -1,3 +0,0 @@ -// TODO - - diff --git a/crates/contracts/src/kakarot_core/kakarot.cairo b/crates/contracts/src/kakarot_core/kakarot.cairo index a68d16d42..4402aff04 100644 --- a/crates/contracts/src/kakarot_core/kakarot.cairo +++ b/crates/contracts/src/kakarot_core/kakarot.cairo @@ -19,6 +19,8 @@ struct ContractAccountStorage { mod KakarotCore { use contracts::components::ownable::ownable_component::InternalTrait; use contracts::components::ownable::{ownable_component}; + use contracts::components::upgradable::IUpgradable; + use contracts::components::upgradable::{upgradable_component}; use contracts::kakarot_core::interface::IKakarotCore; use contracts::kakarot_core::interface; use core::hash::{HashStateExTrait, HashStateTrait}; @@ -35,12 +37,15 @@ mod KakarotCore { use utils::traits::U256TryIntoContractAddress; component!(path: ownable_component, storage: ownable, event: OwnableEvent); + component!(path: upgradable_component, storage: upgradable, event: UpgradableEvent); #[abi(embed_v0)] impl OwnableImpl = ownable_component::Ownable; impl OwnableInternalImpl = ownable_component::InternalImpl; + impl UpgradableImpl = upgradable_component::UpgradableImpl; + #[storage] struct Storage { /// Kakarot storage for accounts: Externally Owned Accounts (EOA) and Contract Accounts (CA) @@ -64,12 +69,15 @@ mod KakarotCore { // Components #[substorage(v0)] ownable: ownable_component::Storage, + #[substorage(v0)] + upgradable: upgradable_component::Storage, } #[event] #[derive(Drop, starknet::Event)] enum Event { OwnableEvent: ownable_component::Event, + UpgradableEvent: upgradable_component::Event, EOADeployed: EOADeployed, } @@ -248,9 +256,9 @@ mod KakarotCore { /// Upgrade the KakarotCore smart contract /// Using replace_class_syscall - fn upgrade( - ref self: ContractState, new_class_hash: ClassHash - ) { //TODO: implement upgrade logic + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradable.upgrade_contract(new_class_hash); } } @@ -269,4 +277,3 @@ mod KakarotCore { } } } - diff --git a/crates/contracts/src/tests/test_kakarot_core.cairo b/crates/contracts/src/tests/test_kakarot_core.cairo index d7f992d5e..4cfb1d36f 100644 --- a/crates/contracts/src/tests/test_kakarot_core.cairo +++ b/crates/contracts/src/tests/test_kakarot_core.cairo @@ -1,5 +1,8 @@ use contracts::components::ownable::ownable_component; use contracts::kakarot_core::{interface::IExtendedKakarotCoreDispatcherImpl, KakarotCore}; +use contracts::tests::test_upgradeable::{ + MockContractUpdatableV1, IMockContractUpdatableDispatcher, IMockContractUpdatableDispatcherTrait +}; use contracts::tests::utils; use debug::PrintTrait; use eoa::externally_owned_account::ExternallyOwnedAccount; @@ -97,3 +100,18 @@ fn test_kakarot_core_compute_starknet_address() { assert(eoa_starknet_address == expected_starknet_address, 'wrong starknet address'); } +#[test] +#[available_gas(20000000)] +fn test_kakarot_core_upgrade_contract() { + let kakarot_core = utils::deploy_kakarot_core(test_utils::native_token()); + let class_hash: ClassHash = MockContractUpdatableV1::TEST_CLASS_HASH.try_into().unwrap(); + + testing::set_contract_address(utils::other_starknet_address()); + kakarot_core.upgrade(class_hash); + + let version = IMockContractUpdatableDispatcher { + contract_address: kakarot_core.contract_address + } + .version(); + assert(version == 1, 'version is not 1'); +} diff --git a/crates/contracts/src/tests/test_upgradeable.cairo b/crates/contracts/src/tests/test_upgradeable.cairo index 60088299d..dec4fe4fd 100644 --- a/crates/contracts/src/tests/test_upgradeable.cairo +++ b/crates/contracts/src/tests/test_upgradeable.cairo @@ -1,3 +1,100 @@ -// TODO +use MockContractUpdatableV0::HasComponentImpl_upgradable_component; +use contracts::components::upgradable::{IUpgradableDispatcher, IUpgradableDispatcherTrait}; +use contracts::components::upgradable::{upgradable_component}; +use contracts::tests::utils; +use debug::PrintTrait; +use serde::Serde; +use starknet::{deploy_syscall, ClassHash, ContractAddress, testing}; +use upgradable_component::{UpgradableImpl}; +#[starknet::interface] +trait IMockContractUpdatable { + fn version(self: @TContractState) -> felt252; +} + +#[starknet::contract] +mod MockContractUpdatableV0 { + use contracts::components::upgradable::{upgradable_component}; + use super::IMockContractUpdatable; + component!(path: upgradable_component, storage: upgradable, event: UpgradableEvent); + + #[abi(embed_v0)] + impl UpgradableImpl = upgradable_component::UpgradableImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + upgradable: upgradable_component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradableEvent: upgradable_component::Event + } + + #[external(v0)] + impl MockContractUpdatableImpl of IMockContractUpdatable { + fn version(self: @ContractState) -> felt252 { + 0 + } + } +} + +type TestingState = upgradable_component::ComponentState; + +impl TestingStateDefault of Default { + fn default() -> TestingState { + upgradable_component::component_state_for_testing() + } +} + +#[test] +#[available_gas(500000)] +fn test_upgradable_update_contract() { + let (contract_address, _) = deploy_syscall( + MockContractUpdatableV0::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap(); + + let version = IMockContractUpdatableDispatcher { contract_address: contract_address }.version(); + + assert(version == 0, 'version is not 0'); + + let mut call_data: Array = array![]; + + let new_class_hash: ClassHash = MockContractUpdatableV1::TEST_CLASS_HASH.try_into().unwrap(); + + IUpgradableDispatcher { contract_address: contract_address }.upgrade_contract(new_class_hash); + + let version = IMockContractUpdatableDispatcher { contract_address: contract_address }.version(); + assert(version == 1, 'version is not 1'); +} + + +#[starknet::contract] +mod MockContractUpdatableV1 { + use contracts::components::upgradable::{upgradable_component}; + use super::IMockContractUpdatable; + component!(path: upgradable_component, storage: upgradable, event: UpgradableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + upgradable: upgradable_component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UpgradableEvent: upgradable_component::Event + } + + #[external(v0)] + impl MockContractUpdatableImpl of IMockContractUpdatable { + fn version(self: @ContractState) -> felt252 { + 1 + } + } +}