diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index fe4b4a63b..b735b7abc 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -50,6 +50,7 @@ * [Inspecting Transactions](starknet/tx-status.md) * [Fees and Versions](starknet/fees-and-versions.md) * [Verifying Contracts](starknet/verify.md) +* [Calldata Transformation](starknet/calldata-transformation.md) --- @@ -131,4 +132,4 @@ * [tx_status](appendix/sncast-library/tx_status.md) * [errors](appendix/sncast-library/errors.md) * [ `snfoundry.toml` Reference](appendix/snfoundry-toml.md) -* [ `Scarb.toml` Reference](appendix/scarb-toml.md) \ No newline at end of file +* [ `Scarb.toml` Reference](appendix/scarb-toml.md) diff --git a/docs/src/starknet/calldata-transformation.md b/docs/src/starknet/calldata-transformation.md new file mode 100644 index 000000000..af6bf20ab --- /dev/null +++ b/docs/src/starknet/calldata-transformation.md @@ -0,0 +1,173 @@ +# Calldata Transformation + +For the examples below, we will consider a dedicated contract - `DataTransformerContract`, defined in `data_transformer_contract` project namespace. + +It's declared on Sepolia network with class hash `0x02a9b456118a86070a8c116c41b02e490f3dcc9db3cad945b4e9a7fd7cec9168`. + +It has a few methods accepting different types and items defined in its namespace: + +```rust +// data_transformer_contract/src/lib.cairo + +pub struct SimpleStruct { + a: felt252 +} + +pub struct NestedStructWithField { + a: SimpleStruct, + b: felt252 +} + +pub enum Enum { + One: (), + Two: u128, + Three: NestedStructWithField +} + +#[starknet::contract] +pub mod DataTransformerContract { + /* ... */ + + use super::*; + + fn tuple_fn(self: @ContractState, a: (felt252, u8, Enum)) { ... } + + fn complex_struct_fn(self: @ContractState, a: ComplexStruct) { ... } + + fn complex_fn( + self: @ContractState, + arr: Array>, + one: u8, + two: i16, + three: ByteArray, + four: (felt252, u32), + five: bool, + six: u256 + ) { + ... + } +} +``` + +A default form of calldata passed to commands requiring it is a series of hex-encoded felts: + +```shell +$ sncast --account myuser \ + call \ + --url http://127.0.0.1:5050 \ + --contract-address 0x016ad425af4585102e139d4fb2c76ce786d1aaa1cfcd88a51f3ed66601b23cdd \ + --function tuple_fn \ + --calldata 0x10 0x3 0x0 \ + --block-id latest +``` + +However, `sncast` allows passing the data in far more handy, human-readable form - as Cairo expressions. +When calldata is delivered in such form, Cast will perform serialization automatically, based on an ABI of the contract we interact with. + +### Basic example + +We can write the same command as above, but with expression calldata: + +```shell +$ sncast --account myuser \ + call \ + --url http://127.0.0.1:5050 \ + --contract-address 0x016ad425af4585102e139d4fb2c76ce786d1aaa1cfcd88a51f3ed66601b23cdd \ + --function tuple_fn \ + --calldata (0x10, 3, data_stransformer_contract::Enum::One) \ + --block-id latest +``` + +getting the same result. + +> 📝 **Note** +> All data types are serialized according to the official [Starknet specification](https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/). + +> 📝 **Note** +> User-defined items such as enums and structs should be referred to depending on a way they are defined in ABI. +> In general, paths to items have form: `::::`. + +### Supported expressions + +> 💡 **Info** +> Only **constant** expressions are supported. Defining and referencing variables and calling functions (either builtin, user-defined or external) is not allowed. + +Cast supports most important Cairo corelib types: + * `bool` + * signed integers (`i8`, `i16`, `i32`, `i64`, `i128`) + * unsigned integers (`u8`, `u16`, `u32`, `u64`, `u96`, `u128`, `u256`, `u384`, `u512`) + * `felt252` (numeric literals and so-called *shortstrings*) + * `ByteArray` + * `ContractAddress` + * `ClassHash` + * `StorageAddress` + * `EthAddress` + * `bytes31` + * `Array` - using `array![]` macro + +Numeric types (primitives and `felt252`) can be paseed with type suffix specified - for example `--calldata 420_u64`. + +### More complex examples + + 1. `complex_fn` - different data types: + + ```shell + $ sncast --account myuser \ + call \ + --url http://127.0.0.1:5050 \ + --contract-address 0x016ad425af4585102e139d4fb2c76ce786d1aaa1cfcd88a51f3ed66601b23cdd \ + --function complex_fn \ + --calldata \ + array![array![1, 2], array![3, 4, 5], array![6]] \ + 12 \ + -128_i8 \ + "Some string (a ByteArray)" \ + ('a shortstring', 32_u32) \ + true \ + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ + --block-id latest + ``` + + 2. `nested_struct_fn` - struct nesting: + + ```shell + $ sncast --account myuser \ + call \ + --url http://127.0.0.1:5050 \ + --contract-address 0x016ad425af4585102e139d4fb2c76ce786d1aaa1cfcd88a51f3ed66601b23cdd \ + --function nested_struct_fn \ + --calldata \ + data_transformer_contract::NestedStructWithField { \ + a: data_transformer_contract::SimpleStruct { a: 10 }, \ + b: 12 \ + } \ + --block-id latest + ``` + +### Caveats + +Reasoning about calldata serialization logic is not always possible for Cast. **In particular, Cast doesn't verify serialized calldata against the ABI**. + +When given a calldata, Cast first tries to transform it according to the contract ABI. +When failed, in case of type or length mismatch, it will then try to interpret it as an already serialized data. + +Thus, calldata written in hex-encoded form is not always going to be interpreted as a list of felts and treated as serialized. Let's see an example: + +Our `DataTransformerContract` exposes a method with signature: +```rust +fn u256_fn(self: @ContractState, a: u256) { ... } +``` + +We can write: +```shell +$ sncast --account myuser \ + call \ + --url http://127.0.0.1:5050 \ + --contract-address 0x016ad425af4585102e139d4fb2c76ce786d1aaa1cfcd88a51f3ed66601b23cdd \ + --function u256_fn \ + --calldata 0x10 \ + --block-id latest +``` + +Despite the call data **not being** the proper serialization of an `u256` (which would be `0x10 0x0`), call succeeds. +That's because Cast interpreted `0x10` as a hex-encoded `u256`, not `felt252`. diff --git a/docs/src/starknet/index.md b/docs/src/starknet/index.md index b38d73c5f..725b7a31e 100644 --- a/docs/src/starknet/index.md +++ b/docs/src/starknet/index.md @@ -43,6 +43,22 @@ response: [0x0] > 📝 **Note** > In the above example we supply `sncast` with `--account` and `--url` flags. If `snfoundry.toml` is present, and have these properties set, values provided using these flags will override values from `snfoundry.toml`. Learn more about `snfoundry.toml` configuration [here](../projects/configuration.md#sncast). + +### Calldata + +Some `sncast` commands (namely `call`, `deploy` and `invoke`) allow passing *calldata* - a series of arguments to perform an action with on blockchain. + +In the example above we called a function with an argument: `0x0`, passed using `--calldata` flag. + +Please note the notation of the argument. The default way of passing calldata is a list of hexadecimally encoded field elements - the *serialized* calldata. +To obtain the serialized form of the wished data, one must write a Cairo program calling `Serde::serialize` on subsequent arguments and displaying the results. + +It is also possible to pass calldata in more friendly, human readable form thanks to the [calldata transformation](./calldata-transformation.md) feature present in Cast. + +> ⚠️ **Warning** +> Cast will not verify the serialized calldata. Any errors caused by passing improper calldata in a serialized form will originate from the network. +> Basic static analysis is possible only when passing expressions - see [calldata transformation](./calldata-transformation.md). + ### How to Use `--wait` Flag Let's invoke a transaction and wait for it to be `ACCEPTED_ON_L2`. @@ -53,7 +69,7 @@ $ sncast --account myuser \ deploy \ --url http://127.0.0.1:5050 \ --class-hash 0x8448a68b5ea1affc45e3fd4b8b480ea36a51dc34e337a16d2567d32d0c6f8a - + Transaction hash: 0x3062310a1e40d4b66d8987ba7447d1c7317381d0295d62cb12f2fe3f11e6983 Waiting for transaction to be received. Retries left: 11 Waiting for transaction to be received. Retries left: 10