Skip to content

Commit

Permalink
feat: serialize integers higher than u32 in hex format
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Oct 18, 2024
1 parent 188d3d5 commit bbd8c99
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 18 deletions.
31 changes: 31 additions & 0 deletions contracts/src/gen.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,36 @@ mod gen {
v2: felt252,
}

#[derive(Serde, Drop)]
struct PlainStruct {
f1: u8,
f2: u16,
f3: u32,
f4: u64,
f5: u128,
f6: felt252,
}

#[derive(Serde, Drop)]
struct MyStruct<T> {
f1: felt252,
f2: T,
f3: felt252,
}

#[derive(Serde, Drop)]
enum MyEnum {
One: u8,
Two: u16,
Three: u32,
Four: u64,
Five: u128,
Six: felt252,
Seven: i32,
Eight: i64,
Nine: i128,
}

#[external(v0)]
fn func1(ref self: ContractState, a: MyStruct<felt252>) {
self.v1.write(a.f1);
Expand All @@ -31,4 +54,12 @@ mod gen {
fn read(self: @ContractState) -> (felt252, felt252) {
(self.v1.read(), self.v2.read())
}

#[external(v0)]
fn func3(self: @ContractState, _a: PlainStruct) {
}

#[external(v0)]
fn func4(self: @ContractState, _a: MyEnum) {
}
}
9 changes: 9 additions & 0 deletions crates/cairo-serde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ pub trait CairoSerde {
/// Deserializes an array of felts into the given type.
fn cairo_deserialize(felts: &[Felt], offset: usize) -> Result<Self::RustType>;
}

/// Serialize a value as a hex string.
pub fn serialize_as_hex<S, T>(value: &T, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize + std::fmt::LowerHex,
{
serializer.serialize_str(&format!("{:#x}", value))
}
6 changes: 4 additions & 2 deletions crates/rs/src/expand/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ impl CairoEnum {
let name = utils::str_to_ident(&inner.name);
let ty = utils::str_to_type(&inner.token.to_rust_type());

let serde = utils::serde_hex_derive(&inner.token.to_rust_type());

if inner.token.type_name() == "()" {
variants.push(quote!(#name));
variants.push(quote!(#serde #name));
} else {
variants.push(quote!(#name(#ty)));
variants.push(quote!(#serde #name(#ty)));
}
}

Expand Down
14 changes: 8 additions & 6 deletions crates/rs/src/expand/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ impl CairoStruct {
let name = utils::str_to_ident(&inner.name);
let ty = utils::str_to_type(&inner.token.to_rust_type());

let serde = utils::serde_hex_derive(&inner.token.to_rust_type());

// r#{name} is not a valid identifier, thus we can't create an ident.
// And with proc macro 2, we cannot do `quote!(r##name)`.
// TODO: this needs to be done more elegantly...
if &inner.name == "type" {
members.push(quote!(r#type: #ty));
members.push(quote!(#serde pub r#type: #ty));
} else if &inner.name == "move" {
members.push(quote!(r#move: #ty));
members.push(quote!(#serde pub r#move: #ty));
} else if &inner.name == "final" {
members.push(quote!(r#final: #ty));
members.push(quote!(#serde pub r#final: #ty));
} else {
members.push(quote!(#name: #ty));
members.push(quote!(#serde pub #name: #ty));
}
}

Expand All @@ -57,14 +59,14 @@ impl CairoStruct {
quote! {
#[derive(#(#internal_derives,)*)]
pub struct #struct_name<#(#gen_args),*> {
#(pub #members),*
#(#members),*
}
}
} else {
quote! {
#[derive(#(#internal_derives,)*)]
pub struct #struct_name {
#(pub #members),*
#(#members),*
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions crates/rs/src/expand/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,25 @@ pub fn rust_associated_type_gen_args(entity_name: &Ident, gen_args: &[Ident]) ->

quote!(type RustType = #entity_name<#(#gen_args_rust),*>;)
}

/// To simplify the serde interop with client in javascript,
/// we use the hex format for all the types greater than u32.
/// IEEE 754 standard for floating-point arithmetic,
/// which can safely represent integers up to 2^53 - 1, which is what Javascript uses.
#[inline]
fn is_serde_hex_int(ty: &str) -> bool {
ty == "u128" || ty == "u64" || ty == "i128" || ty == "i64"
}

/// Serde derive for hex serialization of struct member or enum variant.
pub fn serde_hex_derive(ty: &str) -> TokenStream2 {
let serde_path = format!("{}::serialize_as_hex", cainome_cairo_serde_path());

if is_serde_hex_int(ty) {
quote! {
#[serde(serialize_with = #serde_path)]
}
} else {
quote!()
}
}
14 changes: 11 additions & 3 deletions examples/cairo_serde_derive.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use cainome_cairo_serde::CairoSerde;
use cainome_cairo_serde_derive::CairoSerde;
use serde::Serialize;
use starknet_types_core::felt::Felt;

#[derive(Debug, CairoSerde, PartialEq)]
#[derive(Debug, CairoSerde, PartialEq, Serialize)]
struct ExampleSimple {
x: Vec<Felt>,
y: Felt,
#[serde(serialize_with = "cainome_cairo_serde::serialize_as_hex")]
z: u128,
}

#[derive(Debug, CairoSerde, PartialEq)]
#[derive(Debug, CairoSerde, PartialEq, Serialize)]
struct ExampleNested {
x: Felt,
y: ExampleSimple,
}

#[derive(Debug, CairoSerde, PartialEq)]
#[derive(Debug, CairoSerde, PartialEq, Serialize)]
struct ExampleTuple(ExampleNested, Vec<Felt>);

#[derive(Debug, CairoSerde, PartialEq)]
Expand All @@ -32,16 +35,21 @@ fn main() {
y: ExampleSimple {
x: vec![Felt::from(2), Felt::from(3)],
y: Felt::from(4),
z: 1729281360,
},
},
vec![Felt::from(1)],
);

let s = serde_json::to_string(&tuple).unwrap();
println!("s = {}", s);

let example = ExampleEnum::Struct {
x: tuple,
y: ExampleSimple {
x: vec![Felt::from(5), Felt::from(6)],
y: Felt::from(7),
z: 1729281360,
},
};

Expand Down
43 changes: 36 additions & 7 deletions examples/structs.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
use cainome::rs::abigen;

use starknet::core::types::Felt;

abigen!(
MyContract,
"./contracts/abi/gen.abi.json",
derives(Debug, Clone)
derives(Debug, Clone, serde::Serialize)
);

#[tokio::main]
async fn main() {
let s = MyStruct::<Felt> {
f1: Felt::ONE,
f2: Felt::TWO,
f3: Felt::THREE,
let s = PlainStruct {
f1: 1,
f2: 2,
f3: 3,
f4: 4,
f5: 5,
f6: Felt::from(6),
};

println!("{:?}", s);
println!("{}", serde_json::to_string(&s).unwrap());

let _s2 = s.clone();

let e = MyEnum::One(1_u8);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Two(1_u16);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Three(1_u32);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Four(1_u64);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Five(1_u128);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Six(Felt::from(6));
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Seven(-1_i32);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Eight(-1_i64);
println!("{}", serde_json::to_string(&e).unwrap());

let e = MyEnum::Nine(-1_i128);
println!("{}", serde_json::to_string(&e).unwrap());
}

0 comments on commit bbd8c99

Please sign in to comment.