StarkNet Alpha v0.11.0

StarkNet Alpha 0.11.0 introduces Cairo 1.0 smart contracts.

Until regenesis, old and new contracts will co-exist and will be able to interact with each other. On the regenesis, classes (and contracts that are instances of those classes) written in older Cairo versions will no longer be supported.

regenesis
The path to regenesis

What to expect

In StarkNet v0.11.0, you can declare, deploy and run Cairo 1.0 smart contracts. We also introduce a new system call that allows a smooth transitioning of existing contracts to a Cairo 1.0 implementation.

Historically, contract classes have been defined in terms of Cairo assembly, or Casm for short (the class definition also included more information needed for execution, e.g., hint data). The novelty of Cairo 1.0 is the introduction of Sierra (Safe Intermediate Representation), an intermediate layer between Cairo 1.0 and Casm.

The role of Sierra is to guarantee that every contract execution can be proven. With Cairo 0, you may write contracts that fail ungracefully, e.g., via an assert instruction, which will result in unprovable transactions.

In such cases, it is impossible to trustlessly charge a fee for the failed transaction, which creates a potential DOS vector on StarkNet sequencers. Conversely, Sierra is guaranteed to compile to “safe Casm,” i.e., a subset of Casm that cannot fail (the simplest example that illustrates the restrictions of safe Casm is the usage of if/else instructions rather than asserts).

To guarantee that all classes in the system originated from Sierra, users will send a new contract class structure (one that corresponds to Sierra), and the StarkNet sequencer will compile it to Casm.

In the future, this compilation step will be proven inside the StarkNet OS. In the meantime, users have to perform this compilation and produce the Casm hash locally in order to sign it in the DECLARE transaction. Note that the user’s approval here is vital; if the compilation to Casm is not verified, nothing enforces the generated Casm to correspond to the (Sierra) contract class.

Cairo is only required for proving. In order to prove the validity of blocks, we need to be able to represent every contract execution in Cairo.

For sequencers, that are responsible for block production, proving may be out of scope. Thus, the compilation of the contract class to Casm may not be important for them. In the future, one can imagine a Sierra VM or compilation from Sierra to different CPU architectures, e.g., x86).

However, for the time being, different components in the ecosystem (the sequencer, full nodes, etc.) will keep using the generated Casm for contract execution.

Notable changes

The introduction of Cairo 1.0 and Sierra has several effects on the system. Below we list the effects on each component; of particular note are:

  • A new version of the DECLARE transaction, which allows sending the new class structure

  • The state commitment will now include contract classes

  • Changes to the on-chain data format

  • New system call - replace_class

  • StarkNet API (feeder gateway/gateway) and JSON-RPC updates

Declare v2

Transaction structure

The following is an example of the new version of the DECLARE transaction:

{
	"version": "0x2",
	"max_fee": "0xf99ee42792c",
	"signature": [
		"0x1e3207935d3ab611b30bf486915b3d2f7ddb5eaa00c1fca2ec73343babbe434",
		"0x418a33a22ccba3c743d0a097bd61d749527f4d460bd3317e904762d91eebc05"
	],
	"nonce": "0x1",
	"contract_class": {
		...
	},
	"compiled_class_hash": "0x418a33a22ccba3c743d0a097bd61d749527f4d460bd3317e904762d91eebc05",
	"sender_address": "0x706064eb9da21c692ef9a6b9f19d671b718e3bea99926ba6b6ad938c049fcbe",
	"type": "DECLARE"
}

Transaction flow

Upon receiving a DECLARE v2 transaction, the sequencer will run __validate_declare__ (similarly to version 1). If the transaction is found valid, the sequencer will compile the contract class to Casm and ensure that the resulting compiled class hash equals the given compiled_class_hash. If the hashes do not match, the sequencer is elligible to charge a fee for the transaction (this is necessary to prevent a potential DOS on the sequencer). If the validation was success and the compilation resulted in a matching compiled class hash, the sequencer proceeds to store the given class in its DB and updates the classes tree with the (class_hash, compiled_class_hash) pair.

Deploy and library_call

After the class was declared, it can be used either in deploying new instances or via library calls. Note that to specify the class (either for deploy or library_call), one has to send class_hash and NOT compiled_class_hash. The sequencer and full nodes may internally maintain a mapping between the two, but a reference to a declared class has to be via its class hash.

State commitment

New classes declared by v2 Declare transactions will be included in the state commitment. To this end, we change the state commitment from global_tree_root to h(global_tree_root, classes_root), where classes_root is a height 251 Patricia tree constructed from the key-value pairs (class_hash, compiled_class_hash).

Old classes, or new ones declared via a DECLARE v1 transaction, do not affect the state commitment.

Replace class syscall

We add a new system call that allows changing a contract’s class to a Cairo 1.0 class:

replace_class(class_hash: felt)

Semantics of the new system call

Once replace_class is called, the class of the calling contract (i.e. the contract whose address is returned by get_contract_address at the time the syscall is called) will be replaced by class whose hash is given by the class_hash argument (this class must be previously declared). This change will not affect the currently running function, i.e. the execution of the calling function will continue as usual with the old code. However, call_contract will now use the new class.

Consider the following example:

@external
swap(assetA: felt, assetB: felt)
	let get_price_selector = 0x...
	let address = get_contract_address()

	let pA = get_price(assetA)
	replace_class(new_class_hash)
	let pA = get_price(assetA)

	let calldata = alloc()
	calldate[0] = assetB
	let pB = call_contract(address, get_price_selector, 1, calldata)

@external
get_price(assetId: felt)
	...

The swap function calls get_price three times:

  • In the first call, the original implementation is used, as expected.

  • In the second call, after replace_class, we also use the original implementation since we continue the current execution with the existing code.

  • In the third call, pB will use get_price from the new class (if it exists, otherwise the call will fail).

Replace class flow

The following flow is only applicable to upgradable contracts. If your contract is upgradable, then upgrading the implementation class to Cairo 1.0 doesn’t require anything new, and can be done with a regular transaction which upgrades the implementation.

The problem is thus “upgrading” the proxy itself to the Cairo 1.0 implementation. Suppose that you have an account proxy, or a DeFi app proxy, that you want to migrate to Cairo 1.0. To migrate it to Cairo 1.0 and keep the same address and state, you can do the following:

  • Declare your new Cairo 1.0 proxy class (if not already declared, as can be the case with standard implementations).

  • Upgrade the implementation to add a function that uses the new replace_class system call.

  • Call the new function from the previous step. Now, replace_class(new_class_hash) is called, where new_class_hash is the class hash of the new Cairo 1.0 proxy class.

  • Starting from the next transaction, the class of the calling contract is now replaced (with the old one having no effect).

FAQ

  • Will I be able to replace the class to any previously declared class?

    • You will only be able to use it in order to transition to Cairo 1.0 classes.

  • Can I only use it for proxy contracts?

    • No, we will not enforce anything about the contents of the old/new class, other than the new class being Cairo 1.0 compatible.

  • How much will replacing the class cost?

    • The dominant cost of this system call is sending two words as calldata, which is ~1.2k gas

  • How do I declare Cairo 1.0 classes?

    • In StarkNet v0.11.0 we will introduce declare v2 transactions, that are used for this purpose. For more details, see here.

  • What happens if I use call_contract (on the feeder gateway / Json RPC) and replace_class is called?

    • The effect of the replacement will only last throughout the call (the StarkNet state remains untouched, like in any other call)

On-Chain data

The data published on L1 should allow everyone to construct the StarkNet state locally. The introduction of the replace_class system call and the separation between classes (Sierra) and compiled classes (Casm) induces some changes to our on chain data format:

  • The deployment info section is removed

  • The first word will be the number of contracts affected by the block (this includes storage updates, nonce updates, class updates, or deployments).

  • For each affected contract, the first word will encode the new nonce and number of storage updates. Additionally, it will use one bit to determine whether or not the contract was deployed or if its class was updated.

The specific encoding is given by:

\[\underbrace{0\cdots0}_{\text{127 bits}} |\underbrace{\text{flag}}_{\text{1 bit}} | \underbrace{\text{new nonce}}_{\text{64 bits}} | \underbrace{\text{# of storage updates}}_{\text{64 bits}}\]
  • If the above flag is turned on, then the next word is the new contract class (whether it was just deployed or replaced). Otherwise, you can skip to the next bullet.

  • For each storage update, we send to L1 the key and the new value (this part remains unchanged)

API changes

Feeder gateway

  • New endpoint: get_compiled_class. The purpose of this endpoint is to return the Cairo assembly associated with the Class. This is the data needed for contract execution. You can find an example of the relevant structure here

  • get_class_by_hash - the structure of the returned class will depend on whether or not it is a new type of class, compiled from Cairo 1.0. You can find an example of the new class structure here.

  • get_full_contract - same as above

  • get_state_update

    • added replaced_classes section

    • declared_contracts is split into old_declared_contracts and declared_contracts

    • old_declared_contracts is a list of class hashes

    • declared_classes is a dictionary that maps class_hash to compiled_class_hash

  • get_block

    • The transactions field is subject to the same changes in get_transaction

    • state_root is renamed to state_commitment

  • get_transaciton

    • contract_address is changed to sender_address in invoke transactions

    • Declare v2 transaction may be returned

  • estimate_fee

    • Can now take an additional skip_validate flag in the URL. If set to true, then __validate__ execution will be skipped. This can assist hardware wallets in not having to sign for fee estimations.

Gateway

  • add_transaction

    • Can now take a Declare v2 transaction

    • contract_address is changed to sender_address in Invoke transactions

JSON-RPC changes

The new class structure and Declare V2 are added to the JSON-RPC. You can track the changes in this pull request.

Version 0 Removal

INVOKE and DECLARE transactions of version 0 will no longer be supported in StarkNet Alpha v0.11.0

Mandatory message fees

As of this version, L1→L2 message fees will become mandatory, i.e., the sequencer will no longer process messages sent with zero ETH.