Default entry point

There are cases where the contract entry points are not known in advance. The most prominent example is a delegate proxy that forwards calls to an implementation contract class. Such a proxy can be implemented using the __default__ entry point as follows:

%lang starknet
%builtins pedersen range_check bitwise

from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.starknet.common.syscalls import library_call

// The implementation class hash.
@storage_var
func implementation_hash() -> (class_hash: felt) {
}

@constructor
func constructor{
    syscall_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
}(implementation_hash_: felt) {
    implementation_hash.write(value=implementation_hash_);
    return ();
}

@external
@raw_input
@raw_output
func __default__{
    syscall_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
}(selector: felt, calldata_size: felt, calldata: felt*) -> (
    retdata_size: felt, retdata: felt*
) {
    let (class_hash) = implementation_hash.read();

    let (retdata_size: felt, retdata: felt*) = library_call(
        class_hash=class_hash,
        function_selector=selector,
        calldata_size=calldata_size,
        calldata=calldata,
    );
    return (retdata_size=retdata_size, retdata=retdata);
}

The __default__ entry point is executed if the requested selector does not match any of the entry point selectors in the contract.

The @raw_input decorator instructs the compiler to pass the calldata as-is to the entry point, instead of parsing it into the requested arguments. In such a case, the function’s arguments must be selector, calldata_size and calldata. Similarly, the @raw_output decorator instructs the compiler not to process the function’s return value. In such a case the function’s return values must be retdata_size and retdata.

Let’s see how to use this proxy pattern.

Create a file named balance_contract.cairo containing the example balance contract code in Writing StarkNet contracts and declare that contract as explained in Declare the contract on the StarkNet testnet. Denote the hash of the new contract class by BALANCE_CLASS_HASH.

Now, create a file named delegate_proxy.cairo containing the proxy code above, and deploy that contract, with the value BALANCE_CLASS_HASH for the implementation_hash_ constructor argument:

starknet deploy \
    --contract delegate_proxy_compiled.json \
    --inputs BALANCE_CLASS_HASH --no_wallet

Denote the address of the new contract by PROXY_CONTRACT.

Invoke the increase_balance function of the balance class through the delegate proxy contract. You should use the address of the delegate proxy contract with the ABI of the implementation class, which is where the invoked function is defined. The rest of the parameters are as expected – the inputs of the function.

starknet invoke \
    --address PROXY_CONTRACT \
    --abi balance_contract_abi.json \
    --function increase_balance \
    --inputs 10000

This will increase the balance stored in the proxy contract. Note that in our case, the implementation balance contract was only declared and not deployed, so it does not have storage of its own.

In a similar way to __default__, the __l1_default__ entry point is executed when an L1 handler is invoked but the requested selector is missing. This entry point in combination with the library_call_l1_handler system call can be used to forward L1 handlers as follows:

from starkware.starknet.common.syscalls import (
    library_call_l1_handler,
)

@l1_handler
@raw_input
func __l1_default__{
    syscall_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
}(selector: felt, calldata_size: felt, calldata: felt*) {
    let (class_hash) = implementation_hash.read();

    library_call_l1_handler(
        class_hash=class_hash,
        function_selector=selector,
        calldata_size=calldata_size,
        calldata=calldata,
    );
    return ();
}

The library_call_l1_handler system call is similar to library_call except that it invokes an l1_handler entry point instead of an external entry point. The system call does not consume an L1 -> L2 message as in the typical use case, the relevant message is consumed by the L1 handler that issued the system call.

Note that calling library_call_l1_handler outside of an L1 handler may be dangerous, as the called handler is likely to assume the appropriate message was consumed.