Upgrades

In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns.

Starknet has native upgradeability through a syscall that updates the contract source code, removing the need for proxies.

Replacing contract classes

To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class.

Contract Classes represent the source code of a program. All contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by a class hash, and before a contract of a class can be deployed, the class hash needs to be declared.

replace_class_syscall

The replace_class syscall allows a contract to update its source code by replacing its class hash once deployed.

/// Upgrades the contract source code to the new contract class.
fn _upgrade(new_class_hash: ClassHash) {
    assert(!new_class_hash.is_zero(), 'Class hash cannot be zero');
    starknet::replace_class_syscall(new_class_hash).unwrap();
}
If a contract is deployed without this mechanism, its class hash can still be replaced through library calls.

Upgradeable module

OpenZeppelin Contracts for Cairo provides Upgradeable to add upgradeability support to your contracts.

Usage

Upgrades are often very sensitive operations, and some form of access control is usually required to avoid unauthorized upgrades. The Ownable module is used in this example.

We will be using the following module to implement the IUpgradeable interface described in the API Reference section.
#[starknet::contract]
mod UpgradeableContract {
    use openzeppelin::access::ownable::Ownable;
    use openzeppelin::upgrades::Upgradeable;
    use openzeppelin::upgrades::interface::IUpgradeable;
    use starknet::ClassHash;
    use starknet::ContractAddress;

    #[storage]
    struct Storage {}

    #[constructor]
    fn constructor(self: @ContractState, owner: ContractAddress) {
        let mut unsafe_state = Ownable::unsafe_new_contract_state();
        Ownable::InternalImpl::initializer(ref unsafe_state, owner);
    }

    #[external(v0)]
    impl UpgradeableImpl of IUpgradeable<ContractState> {
        fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
            // This function can only be called by the owner
            let ownable_state = Ownable::unsafe_new_contract_state();
            Ownable::InternalImpl::assert_only_owner(@ownable_state);

            // Replace the class hash upgrading the contract
            let mut upgradeable_state = Upgradeable::unsafe_new_contract_state();
            Upgradeable::InternalImpl::_upgrade(ref upgradeable_state, new_class_hash);
        }
    }

    (...)
}

Proxies and Starknet

Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them.

In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement.

Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used for redirecting every potential function call to the implementation. This means that a generic proxy contract can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward their execution to another contract class. This can still be useful for example to upgrade the logic of some functions.