Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/glihm/starknet-abigen-rs

Exploratory work on abigen in rust for Starknet 🦀
https://github.com/glihm/starknet-abigen-rs

Last synced: 3 months ago
JSON representation

Exploratory work on abigen in rust for Starknet 🦀

Awesome Lists containing this project

README

        

# ⚠️ ARCHIVED ⚠️
This repo will no longer be maintained, in favor of it's new version available here: https://github.com/cartridge-gg/cainome.

# Starknet abigen for rust bindings

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.

- [X] Types generation with serialization/deserialization for any type in the contract.
- [X] Support for generic types.
- [X] Auto generation of the contract with it's functions (call and invoke).
- [X] Generation of Events structs to parse automatically `EmittedEvent`.

## How to use it

For now this crate is not yet published on `crated.io`, but here is how you can do the following.

```toml
# 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"
```

```rust
// 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](https://github.com/glihm/starknet-abigen-rs/tree/5cde6560bdaf273f1bea779b73b5e2c53cabc2c2/examples).

## Quick start

1. Terminal 1: Run Katana

```sh
dojoup -v nightly
```

```sh
katana
```

2. Terminal 2: Contracts setup

```sh
cd contracts && scarb build && make setup
```

```sh
cargo run --example simple_get_set
```

## Cairo - Rust similarity

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`.

```rust
// Cairo: fn get_data(self: @ContractState) -> Span
fn get_data() -> Vec

// Cairo: fn get_opt(self: @ContractState, val: u32) -> Option
fn get_opt(val: u32) -> Option

// Cairo: struct MyData { a: felt252, b: u32, c: Span }
struct MyData {
a: FieldElement,
b: u32,
c: Vec,
}
```

If you want to leverage the (de)serialization generated by the bindings, to make raw calls with `starknet-rs`, you can:

```rust
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::::deserialize(felts, 0).unwrap;
```

Any type implementing the `CairoType` trait can be used this way.

## Generate the binding for your contracts

1. If you have a large ABI, consider adding a file (at the same level of your `Cargo.toml`) with the `JSON` containing the ABI.
Then you can load the whole file using:

```rust
abigen!(MyContract, "./mycontract.abi.json")
```

2. (DISABLED FOR NOW) If you only want to make a quick call without too much setup, you can paste an ABI directly using:

```rust
abigen!(MyContract, r#"
[
{
"type": "function",
"name": "get_val",
"inputs": [],
"outputs": [
{
"type": "core::felt252"
}
],
"state_mutability": "view"
}
]
"#);
```

3. 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`.

## How to work with events

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:

```rust
// 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.
}
...
};
}
```

## Serialization

Cairo serializes everything as `felt252`. Some edge cases to have in mind:

1. Enum

Enumerations are serialized with the index of the variant first, and then the value (is any).

```rust
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]
```

2. Span/Array

After serialization, `Span` and `Array` are processed in the same fashion.
The length is serialized first, and then the following elements.

```rust
let a = array![];
let b = array![1, 2];
```

Will be serialized as:

```
a: [0]
b: [2, 1, 2]
```

3. Struct

`struct` are serialized as their fields define it. There is no length at the beginning. It depends on the fields order.

```rust
struct MyStruct {
a: felt252,
b: u256,
c: Array,
}

let s = MyStruct {
a: 123,
b: 1_u256,
c: array![9],
}
```

Will be serialized as:

```
[123, 1, 0, 1, 9]
```

## PR on starknet-rs

The goal of this work was to be included in `starknet-rs` library.
You can follow the status of such process checking those PRs:

1. https://github.com/xJonathanLEI/starknet-rs/pull/475
2. Please take a look to this [README](https://github.com/glihm/starknet-rs/tree/abigen/starknet-macros) for the newest information about the syntax being worked on.

But we may choose a standalone path in the future to add more features.

## Disclaimer

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`.

## Credits

None of these crates would have been possible without the great work done in:

- [`ethers-rs`](https://github.com/gakonst/ethers-rs/)
- [`alloy-rs`](https://github.com/alloy-rs/core/)
- [`starknet-rs`](https://github.com/xJonathanLEI/starknet-rs/)