This repo will no longer be maintained, in favor of it's new version available here: https://github.com/cartridge-gg/cainome.
Passionate work about providing rust binding to Starknet community, by the community.
The current state of the repo is still very experimental, but we are reaching a first good milestrone.
- Types generation with serialization/deserialization for any type in the contract.
- Support for generic types.
- Auto generation of the contract with it's functions (call and invoke).
- Generation of Events structs to parse automatically
EmittedEvent
.
For now this crate is not yet published on crated.io
, but here is how you can do the following.
# Cargo.toml of your project
[dependencies]
starknet-abigen-parser = { git = "https://github.com/glihm/starknet-abigen-rs", tag = "v0.1.3" }
starknet-abigen-macros = { git = "https://github.com/glihm/starknet-abigen-rs", tag = "v0.1.3" }
starknet = "0.7.0"
// Your main.rs or other rust file:
...
use starknet_abigen_parser;
use starknet_abigen_macros::abigen;
abigen!(MyContract, "./path/to/abi.json");
#[tokio::main]
async fn main() {
...
}
You can find a first simple example in the examples
folder here.
- Terminal 1: Run Katana
dojoup -v nightly
katana
- Terminal 2: Contracts setup
cd contracts && scarb build && make setup
cargo run --example simple_get_set
We've tried to leverage the similarity between Rust and Cairo. With this in mind, the bindings are generated to be as natural as possible from a Rust perspective.
So most of the types are Rust types, and the basic value for us is the FieldElement
from starknet-rs
.
// Cairo: fn get_data(self: @ContractState) -> Span<felt252>
fn get_data() -> Vec<FieldElement>
// Cairo: fn get_opt(self: @ContractState, val: u32) -> Option<felt252>
fn get_opt(val: u32) -> Option<FieldElement>
// Cairo: struct MyData { a: felt252, b: u32, c: Span<u32> }
struct MyData {
a: FieldElement,
b: u32,
c: Vec<u32>,
}
If you want to leverage the (de)serialization generated by the bindings, to make raw calls with starknet-rs
, you can:
let d = MyData {
a: FieldElement::TWO,
b: 123_u32,
c: vec![8, 9],
};
let felts = MyData::serialize(&d);
let felts = vec![FieldElement::ONE, FieldElement::TWO];
// For now you have to provide the index. Later an other method will consider deserialization from index 0.
let values = Vec::<u32>::deserialize(felts, 0).unwrap;
Any type implementing the CairoType
trait can be used this way.
- If you have a large ABI, consider adding a file (at the same level of your
Cargo.toml
) with theJSON
containing the ABI. Then you can load the whole file using:
abigen!(MyContract, "./mycontract.abi.json")
- (DISABLED FOR NOW) If you only want to make a quick call without too much setup, you can paste an ABI directly using:
abigen!(MyContract, r#"
[
{
"type": "function",
"name": "get_val",
"inputs": [],
"outputs": [
{
"type": "core::felt252"
}
],
"state_mutability": "view"
}
]
"#);
- To extract ABI from your contract, please use the tool
jq
if you are in local, or any starknet explorer. With jq, you can do:cat target/dev/my_contract.contract_class.json | jq .abi > /path/abi.json
.
Events are special structs/enum that we usually want to deserialize effectively.
The abigen!
macro generate all the events associated types, and this always include
one enum always named Event
.
Any contract you use abigen!
on will contain this enum, and this also includes the convertion
from EmittedEvent
, which is the starknet-rs
type returned when we fetch events.
So you can do this:
// the Event enum is always declared if at least 1 event is present
// in the cairo file.
use myContract::{Event as AnyEvent};
let events = provider.fetch_events(...);
for e in events {
// The `TryFrom` is already implemented for each variant, which includes
// the deserialization of the variant.
let my_event: AnyEvent = match e.try_into() {
Ok(ev) => ev,
Err(_s) => {
// An event from other contracts, ignore.
continue;
}
};
// Then, you can safely check which event it is, and work with it,
// with the rust type!
match my_event {
AnyEvent::MyEventA(a) => {
// do stuff with a.
}
AnyEvent::MyEventB(b) => {
// do stuff with b.
}
...
};
}
Cairo serializes everything as felt252
. Some edge cases to have in mind:
- Enum
Enumerations are serialized with the index of the variant first, and then the value (is any).
enum MyEnum {
V1: u128,
V2,
}
let a = MyEnum::V1(2_u128);
let b = MyEnum::V2;
Will be serialized like this, with enum variant index first:
a: [0, 2]
b: [1]
- Span/Array
After serialization, Span
and Array
are processed in the same fashion.
The length is serialized first, and then the following elements.
let a = array![];
let b = array![1, 2];
Will be serialized as:
a: [0]
b: [2, 1, 2]
- Struct
struct
are serialized as their fields define it. There is no length at the beginning. It depends on the fields order.
struct MyStruct {
a: felt252,
b: u256,
c: Array<felt252>,
}
let s = MyStruct {
a: 123,
b: 1_u256,
c: array![9],
}
Will be serialized as:
[123, 1, 0, 1, 9]
The goal of this work was to be included in starknet-rs
library.
You can follow the status of such process checking those PRs:
- xJonathanLEI/starknet-rs#475
- Please take a look to this README for the newest information about the syntax being worked on.
But we may choose a standalone path in the future to add more features.
This is a very early stage of the project. The idea is to have a first version that can be revised by the community and then enhanced.
Hopefully one day we can have a great lib that can be integrated to starknet-rs
or remain a stand alone crate which can be combined with starknet-rs
.
None of these crates would have been possible without the great work done in: