More Features¶
Storage variable with multiple values¶
A storage variable does not have to be a single field element, it can also be a tuple of several field elements. For example:
// A mapping from user to a pair (min, max).
@storage_var
func range(user: felt) -> (res: (felt, felt)) {
}
You can read and write this value as follows:
@external
func extend_range{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
}(user: felt) {
let (min_max) = range.read(user);
range.write(user, (min_max[0] - 1, min_max[1] + 1));
return ();
}
Note that in this case the range.read()
returns one item that is a pair.
Thus, let (min, max) = range.read(user);
will not work.
Storage variable with struct arguments¶
An argument of a storage variable may also be a struct or a tuple, as long as they don’t contain pointers (such types, that don’t contain pointers, are called felts-only types). For example:
struct User {
first_name: felt,
last_name: felt,
}
// A mapping from a user to 1 if they voted and 0 otherwise.
@storage_var
func user_voted(user: User) -> (res: felt) {
}
@external
func vote{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
}(user: User) {
user_voted.write(user, 1);
return ();
}
Array arguments in calldata¶
An external function may get an array of field elements as an argument.
In order to define an array named a
, pass two consecutive arguments:
a_len
of type felt
and a
of type felt*
(the first argument must be named a_len
if the second argument is named a
).
For example:
@external
func compare_arrays(
a_len: felt, a: felt*, b_len: felt, b: felt*
) {
assert a_len = b_len;
if (a_len == 0) {
return ();
}
assert a[0] = b[0];
return compare_arrays(
a_len=a_len - 1, a=&a[1], b_len=b_len - 1, b=&b[1]
);
}
In order to call compare_arrays
with the arrays [10, 20, 30, 40]
and [50, 60]
,
you should pass the following inputs to starknet invoke
:
starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi contract_abi.json \
--function compare_arrays \
--inputs 4 10 20 30 40 2 50 60
The first value, 4
, is the length of the first array,
then its 4 entires. After that, we have the length of the second arrays (2
) followed by
its entries.
Note that calling compare_arrays
with the aforementioned
arguments will fail as the arrays are different.
A StarkNet contract using array arguments in external functions must have the range_check builtin, which is used to validate that the array’s length is nonnegative.
Passing tuples and structs in calldata¶
Calldata arguments and return values may be of any type that does not contain pointers. E.g., structs with felt members, tuples of felts and tuples of tuples of felts. For example:
struct Point {
x: felt,
y: felt,
}
@view
func sum_points(points: (Point, Point)) -> (res: Point) {
return (
res=Point(
x=points[0].x + points[1].x,
y=points[0].y + points[1].y,
),
);
}
In order to call sum_points
with the points (1, 2), (10, 20)
,
you should pass the following inputs to starknet call
:
starknet call \
--address ${CONTRACT_ADDRESS} \
--abi contract_abi.json \
--function sum_points \
--inputs 1 2 10 20
Passing arrays of structs¶
In a similar way, passing arrays of structs is supported, as long as the structs do not contain pointers:
@external
func sum_points_arr(a_len: felt, a: Point*) -> (res: Point) {
if (a_len == 0) {
return (res=Point(0, 0));
}
let (res) = sum_points_arr(a_len=a_len - 1, a=&a[1]);
return (res=Point(x=res.x + a[0].x, y=res.y + a[0].y));
}
In order to call sum_points_arr
with the 3 points (1, 2), (10, 20), (100, 200)
,
you should pass the following inputs to starknet call
:
starknet call \
--address ${CONTRACT_ADDRESS} \
--abi contract_abi.json \
--function sum_points_arr \
--inputs 3 1 2 10 20 100 200
Retrieving the transaction information¶
You can retrieve the transaction information
(which includes, for example, the signature and the transaction fee),
by using the get_tx_info()
library function:
from starkware.starknet.common.syscalls import get_tx_info
func get_tx_max_fee{syscall_ptr: felt*}() -> (max_fee: felt) {
let (tx_info) = get_tx_info();
return (max_fee=tx_info.max_fee);
}
The returned value is a pointer to a TxInfo
struct, which is defined as follows:
struct TxInfo {
// The version of the transaction. It is fixed (currently, 1) in the OS, and should be
// signed by the account contract.
// This field allows invalidating old transactions, whenever the meaning of the other
// transaction fields is changed (in the OS).
version: felt,
// The account contract from which this transaction originates.
account_contract_address: felt,
// The max_fee field of the transaction.
max_fee: felt,
// The signature of the transaction.
signature_len: felt,
signature: felt*,
// The hash of the transaction.
transaction_hash: felt,
// The identifier of the chain.
// This field can be used to prevent replay of testnet transactions on mainnet.
chain_id: felt,
// The transaction's nonce.
nonce: felt,
}
Block number and timestamp¶
You can get the current block number and timestamp (seconds since unix epoch) by using the
get_block_number()
and get_block_timestamp()
library functions.
from starkware.starknet.common.syscalls import (
get_block_number,
get_block_timestamp,
)
// ...
let (block_number) = get_block_number();
let (block_timestamp) = get_block_timestamp();
Note that both of the above functions require the implicit argument syscall_ptr
. Presently, the
result of get_block_timestamp()
is not enforced by the StarkNet OS or Core contract (i.e., the
sequencer may choose an arbitrary timestamp). In the future, some restrictions on the new timestamp
will be added. Also note that the block timestamp is the time at the beginning of the
block creation, which can differ significantly from the time the block is accepted on L1.