Contracts


Page Content:

  • Contracts Storage – Go
  • Contract Hash – Go
  • Contracts Adresses – Go
  • Function Selector – Go
  • Contract ABI – Go

Contracts Storage

Storage Layout

Contract storage is a persistent storage space where you can read, write, modify, and persist data. Storage is a map with 2251 slots, where each slot is a felt and is initialized to 0.

Storage Basic Functions

The basic function for reading storage returns value stored in key – 

let (value) = storage_read(key)

The basic function for writing to storage writes value to key – 

storage_write(key, value)

Both are syscalls that can be imported –

from starkware.starknet.common.syscalls import storage_read, storage_write

Another basic function is used for getting the storage address, this function is created by the compiler when defining a storage variable, as explained below.  This function returns the address of the storage variable. Below we discuss how this address is determined from the variable’s name and keys.

Storage Variables

The most common way for interacting with a contract’s storage is through storage variables.

The @storage_var decorator declares a variable that will be kept as part of the contract storage. The variable can consist of a single felt, or it can be a mapping from multiple arguments to a tuple of felts or structs. To use this variable, the var.read(args)var.write(args, value) and var.addr(args) functions are automatically created by the @storage_var decorator, for reading the storge value, writing the storage value and getting the storage address, respectively. 

The StarkNet contract compiler generates the Cairo code that maps the storage variable’s name and argument values to an address – so that it can be part of the generated proof. The address of a storage variable is computed as follows:

  • If it is a single value, then the address is sn_keccak(variable_name), where variable_name is the ASCII encoding of the variable’s name.
  • If it is a (nested) mapping, then the address of the value at key k_1,...,k_n is 

h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n) where h is the 

Pedersen hash and the final value is taken mod (2^251-256)

  • If it is a mapping to complex values (e.g., tuples or structs), then this complex value lies in a continuous segment starting from the address calculated in the previous point. Note that 256 field elements is the current limitation on the maximal size of a complex storage value.
    Note that when calling var.addr(args) for a storage variable with complex values, the returned value is the address of the first element in the storage.

storage variable address := pedersen(keccak(variable name), argument values)

Example: the following is a storage variable named range that is a mapping from a key to a Tuple of two values.

@storage_var
func range(user : felt) -> (res : (felt, felt))
end

 

Contract Hash

The contract hash is a hash of its definition. The elements defining a contract are:

  • API version (the version under which the contract was deployed)
  • Array of external functions entry points
  • Array of L1 handlers entry points1
  • Array of constructors entry points (currently the compiler allows only one constructor)
  • Array of used builtin names2 (ordered by declaration)
  • Keccak3 of the contract’s program. Here the contract’s program stands for the json obtained by executing starknet-compile with the --ne_debug_info flag. To see the exact computation of this field, see our repo.
  • Bytecode (represented by an array of field elements)

The contract’s hash is the chain hash4 of the above, computed as follows:

  • start with h(0,api_version)
  • for every line in the above (excluding the first), compute:

h(h(previous_line), new_line)

  • the hash of an array a[1],…,a[n] is defined by

h(...h(h(0,a[1)),a[2]),...,a[n]),n)

  • let c denote the cumulative hash resulting from applying the above process, the contract’s hash is then h(c, number_of_lines), where number_of_lines is 7.

For more details, see the Cairo implementation here.

Contracts Addresses

The contract address is a unique identifier of the contract on Starknet. It is a hash chain of the following information:

  1. Prefix.
  2. Caller address.
  3. Salt. In StarkNet alpha this is a random value given by the sequencer
  4. Contract hash
  5. Constructor call data hash

 

Specifically, it is calculated as follows:

contract_address := pedersen(
“STARKNET_CONTRACT_ADDRESS”,
caller_address,
salt,
pedersen(contract_code),
pedersen(constructor_calldata))

Where:

  1. pedersen is a Pedersen hash function applied to a list of elements.
  2. STARKNET_CONTRACT_ADDRESS” is a constant prefix encoded into bytes (ASCII) using big endian encoding.

See the address computation here.

Function Selector

A function selector is an identifier through which the function is callable in transactions or in other contracts. The selector is the sn_keccak hash of the function name, encoded in ASCII.

Contract ABI

Contract ABI is a representation of a StarkNet contract. It is formatted as a JSON and describes the functions and the structs of the contracts.

You can get it via –

starknet-compile contract.cairo \
    --output contract_compiled.json \
    --abi contract_abi.json

or by executing get_code in the StarkNet CLI.

Example ABI

[
    {
        "members": [
            {
                "name": "x",
                "offset": 0,
                "type": "felt"
            },
            {
                "name": "y",
                "offset": 1,
                "type": "felt"
            }
        ],
        "name": "Point",
        "size": 2,
        "type": "struct"
    },
    {
        "inputs": [
            {
                "name": "a_len",
                "type": "felt"
            },
            {
                "name": "a",
                "type": "felt*"
            }
        ],
        "name": "constructor",
        "outputs": [],
        "type": "constructor"
    },
    {
        "inputs": [
            {
                "name": "a_len",
                "type": "felt"
            },
            {
                "name": "a",
                "type": "felt*"
            },
            {
                "name": "b_len",
                "type": "felt"
            },
            {
                "name": "b",
                "type": "felt*"
            }
        ],
        "name": "input_arrays",
        "outputs": [],
        "type": "function"
    },
    {
        "inputs": [
            {
                "name": "array_len",
                "type": "felt"
            },
            {
                "name": "array",
                "type": "felt*"
            }
        ],
        "name": "output_arrays",
        "outputs": [
            {
                "name": "array_len",
                "type": "felt"
            },
            {
                "name": "array",
                "type": "felt*"
            }
        ],
        "type": "function"
    },
    {
        "inputs": [
            {
                "name": "points",
                "type": "(Point, Point)"
            }
        ],
        "name": "input_output_struct",
        "outputs": [
            {
                "name": "res",
                "type": "Point"
            }
        ],
        "type": "function"
    }
]

 

 


1 An entry point is a pair (selector, offset), where offset is the offset of the instruction that should be called inside the contract’s bytecode

2 ASCII encoding of the builtin names

3 Here we mean starknet_keccak, which is the first 250 bits of the standard keccak (we do this to be able to put the result in a single field element)

4 Pedersen hash


<< Transaction Structure and Hash | Events >>