/0to1CairoDemo

Demo to upgrade your upgradeable cairo 0 proxy contract to cairo 1 via replace_class_syscall

Primary LanguagePythonMIT LicenseMIT

How to upgrade your Cairo 0 contracts to Cairo 1 using replace_class

Why upgrade to Cairo 1.0 ?

With Regenesis approaching on Starknet, Cairo 0.x will soon be deprecated in favour of Cairo 1. To ensure your contracts continue working after Regenesis, it's important to migrate them from Cairo 0.x to Cairo 1. As Starknet Alpha v0.11.0 is now live on Mainnet, Cairo 1 contracts can now deployed and current Cairo 0 contracts can now be upgraded with provided replaced_class syscall without necessitating a change in contract address.

Upgrading your Upgradable Cairo 0.x contracts to Cairo 1

In Cairo 0.x, in order to have an upgradable Cairo 0.x contracts, you will often utilize a Proxy contract with a set_implementation function that allows the contract owner to upgrade

The method that we will be covering utilizes the replace_class syscall to remove the need for a proxy pattern altogether. At the end of migration the contract utlizes an implementation class hash with an upgrade method in it. This method assumes that the replace_call syscall will not be deprecated and the Proxy pattern in cairo0 will not be the dominant pattern for upgradeable contracts in cairo1.

Before proceeding with the tutorial, make sure you have the following dependencies installed.

cairo-lang v0.11.0
cairo v1.0.0-alpha.6
protostar

You can install cairo-lang with pip. We are using protostar but it is optional and can be replaced with any other similar tooling that you might prefer.

The code for the contracts used in this article is available at https://github.com/NethermindEth/0to1CairoDemo. Feel free to follow along!

Building and deploying the Cairo 0 contracts

Let's start off by building the required Cairo 0 contracts.

cd cairo0
protostar build

Declare both the proxy and the cairo0resolver that's built in the previous step.

protostar declare ./build/proxy.json \
    --account-address $ACCOUNT_ADDRESS --max-fee auto \
    --private-key-path=../private --network testnet

protostar declare ./build/cairo0resolver.json \
    --account-address $ACCOUNT_ADDRESS  --max-fee auto \
    --private-key-path=../private --network testnet

Here, the $ACCOUNT_ADDRESS is the address of your wallet, and ./private is the file containing your private key.

If for some reason declare doesnt work from CLI, you can try ArgentX wallet's developer settings under Settings -> Developer settings -> Smart contract development, which provides a GUI for declaring your contract.

argent-dev

Finally, we deploy an implementation of the declared classes on chain via protostar deploy, which calls a UDC to deploy our contracts.

protostar deploy $PROXY_CLASS_HASH \
--account-address $ACCOUNT_ADDRESS \
-n testnet --max-fee auto --private-key-path ./private \
--wait-for-acceptance \
-i $CAIRO0_RESOLVER_CLASS_HASH \
0x2dd76e7ad84dbed81c314ffe5e7a7cacfb8f4836f01af4e913f275f89a3de1a 1 \
$ACCOUNT_ADDRESS

This deploys the Proxy contract and calls the initializer with the desired class hash to proxy to, which in this case would be the our cairo0resolver class.

The inputs provided via the -i flag specifies the class hash for the cairo0resolver, the selector for the initializer function, the length of the calldata, and wallet the address of the proxy admin respectively.

Upgrading your contract

Before we perform the upgrade, let's perform some state changes on the contract. We can do this via invoking a the set_starknet_id function on the contract which will update the resolver variable to the specified value. You can do this either via protostar invoke or just using the Voyager provided function call interface.

protostar invoke \
    --contract-address $PROXY_CONTRACT_ADDRESS \
    --function set_starknet_id \
    --inputs 10 2024 \
    --max-fee auto --private-key-path ../private \
    --network testnet --account-address $ACCOUNT_ADDRESS

In voyager, you can invoke the function by going to https://goerli.voyager.online/contract/{CONTRACT_ADDRESS}#writeContract, where CONTRACT_ADDRESS should be replaced by the proxy contract address.

invoke-with-voyager

After the transaction is accepted, the state should be updated successfully.

Next, let's prepare to upgrade our contract. As most Cairo tooling are still adding support to Cairo 1, we will be using the binaries from the Cairo repo directly as well as utilize the Starknet Cli.

You can reuse the account you used for the deployment of your previous contracts with protostar by editing your starknet account config, which usually resides in ~/.starknet_accounts.

{
    "alpha-goerli": {
        "account_v11": {
            "private_key": "${WALLET_PRIVATE_KEY}",
            "address": "${WALLET_ADDRESS}",
            "deployed": true
        }
    }
}

As usual, build your Cairo contracts, but this time using the binaries from Cairo v1.0.0-alpha.6.

starknet-compile ./cairo1/cairo1resolver.cairo ./cairo1/cairo1resolver.json --replace-ids

Now, we can start declaring our Cairo 1 contract on chain.

starknet declare --contract ./cairo1resolver.json --account account_v11

If you have any issue declaring your Cairo 1 contract, you can check out [our article] on deploying your first Cairo 1 contract, which covers some of the problems you might face.

Finally, we can now perform an upgrade via the proxy to the new Cairo 1 implementation.

starknet invoke \
    --address $PROXY_CONTRACT_ADDRESS \
    --abi ./cairo0/build/cairo0resolver_abi.json \
    --function upgrade \
    --inputs $CAIRO_1_RESOLVER_CLASS_HASH \
        --account account_v11

After the transaction is accepted by L2, you can now check if the implementation has been updated and the state is in tact as well to confirm that the update is successful! The proxy should now point to your Cairo 1 class and reading resolver should return you the same state as before without issues!

With this, you have successfully upgraded your Cairo 0.x implementation to Cairo 1!

Upgradable Cairo 1 contract

With the replace_class syscall, it is now possible to remove the need for a Proxy altogether. Now, to upgrade your contract, you can call the upgrade function which will call replace_class to replace the current class of your implementation with the provided class, effectively performing an upgrade.

Our cairo1resolver class already implemented an upgrade function that utilizes replace_class for this. If you haven't implemented something like this yet, you might need to perform a upgrade for your proxy again to a class with this function implemented. It will look something like this:

#[external]
fn upgrade(new_class_hash: core::starknet::class_hash::ClassHash) ->felt252 {
    replace_class_syscall(new_class_hash);
    1
}

Let's now proceed with calling the above upgrade function:

starknet invoke \
    --address $PROXY_CONTRACT_ADDRESS \
    --function upgrade \
    --inputs $CAIRO_1_RESOLVER_CLASS_HASH \
    --account account_v11

After the transaction is accepted, you should now see that your proxy contract has been changed to a regular contract with an implementation class of cairo1resolver!

With this, you now have a Cairo 1 contract that is upgradable as well!