Upgradeable Contracts
Starknet separates contracts into classes and instances, making it simple to upgrade a contract's logic without affecting its state.
A contract class is the definition of the semantics of a contract. It includes the entire logic of a contract: the name of the entry points, the addresses of the storage variables, the events that can be emitted, etc. Each class is uniquely identified by its class hash. A class does not have its own storage: it's only a definition of logic.
Classes are typically identified by a class hash. When declaring a class, the network registers it and assigns a unique hash used to identify the class and deploy contract instances from it.
A contract instance is a deployed contract corresponding to a class, with its own storage.
Starknet natively supports upgradeable contracts through the replace_class_syscall
system call, enabling simple contract upgrades without affecting the contract's state.
Upgrading Contracts
To upgrade a contract, expose an entry point that executes replace_class_syscall
with the new class hash as an argument:
use core::starknet::{ClassHash, syscalls};
use core::starknet::class_hash::class_hash_const;
use core::num::traits::Zero;
fn _upgrade(new_class_hash: ClassHash) {
assert(!new_class_hash.is_zero(), 'Class hash cannot be zero');
syscalls::replace_class_syscall(new_class_hash).unwrap();
}
Note: Thoroughly review changes and potential impacts before upgrading, as it's a delicate procedure with security implications. Don't allow arbitrary addresses to upgrade your contract.
Upgradeable Component
OpenZeppelin Contracts for Cairo provides the Upgradeable
component that can be embedded into your contract to make it upgradeable. This component is a simple way to add upgradeability to your contract while relying on an audited library. It can be combined with the Ownable
component to restrict the upgradeability to a single address, so that the contract owner has the exclusive right to upgrade the contract.
#[starknet::contract]
mod UpgradeableContract {
use openzeppelin::access::ownable::OwnableComponent;
use openzeppelin::upgrades::UpgradeableComponent;
use openzeppelin::upgrades::interface::IUpgradeable;
use core::starknet::{ContractAddress, ClassHash};
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
/// Ownable
#[abi(embed_v0)]
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
/// Upgradeable
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
upgradeable: UpgradeableComponent::Storage,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
UpgradeableEvent: UpgradeableComponent::Event,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.ownable.initializer(owner);
}
#[abi(embed_v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// This function can only be called by the owner
self.ownable.assert_only_owner();
// Replace the class hash upgrading the contract
self.upgradeable.upgrade(new_class_hash);
}
}
}
For more information, please refer to the OpenZeppelin docs API reference.