Skip to content

Commit 620ceca

Browse files
committed
introduce StableAbi extension to frozen-abi
1 parent c667d88 commit 620ceca

File tree

9 files changed

+224
-57
lines changed

9 files changed

+224
-57
lines changed

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ members = [
3232
"fee-structure",
3333
"file-download",
3434
"frozen-abi",
35-
"frozen-abi-macro",
35+
"frozen-abi-macro", "frozen-abi-test",
3636
"genesis-config",
3737
"hard-forks",
3838
"hash",

frozen-abi-macro/src/lib.rs

Lines changed: 143 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,42 @@ pub fn derive_abi_enum_visitor(_item: TokenStream) -> TokenStream {
2323
"".parse().unwrap()
2424
}
2525

26+
#[cfg(not(feature = "frozen-abi"))]
27+
#[proc_macro_derive(StableAbi)]
28+
pub fn derive_stable_abi(_item: TokenStream) -> TokenStream {
29+
"".parse().unwrap()
30+
}
31+
32+
#[cfg(feature = "frozen-abi")]
33+
#[proc_macro_derive(StableAbi)]
34+
pub fn derive_stable_abi(item: TokenStream) -> TokenStream {
35+
use quote::quote;
36+
use syn::{parse_macro_input, Error, Item};
37+
38+
let item = parse_macro_input!(item as Item);
39+
40+
let ident = match item {
41+
Item::Struct(ref s) => &s.ident,
42+
Item::Enum(ref e) => &e.ident,
43+
Item::Type(ref t) => &t.ident,
44+
_ => {
45+
return Error::new_spanned(
46+
item,
47+
"StableAbi can only be derived for struct, enum, or type alias",
48+
)
49+
.to_compile_error()
50+
.into();
51+
}
52+
};
53+
54+
let expanded = quote! {
55+
#[automatically_derived]
56+
impl ::solana_frozen_abi::stable_abi::StableAbi for #ident {}
57+
};
58+
59+
expanded.into()
60+
}
61+
2662
#[cfg(feature = "frozen-abi")]
2763
use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
2864
#[cfg(feature = "frozen-abi")]
@@ -156,30 +192,27 @@ fn derive_abi_sample_enum_type(input: ItemEnum) -> TokenStream {
156192
#[cfg(feature = "frozen-abi")]
157193
fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
158194
let type_name = &input.ident;
159-
let mut sample_fields = quote! {};
160195
let fields = &input.fields;
196+
let mut sample_fields = quote! {};
161197

162198
match fields {
163199
Fields::Named(_) => {
164200
for field in fields {
165201
let field_name = &field.ident;
202+
166203
sample_fields.extend(quote! {
167204
#field_name: AbiExample::example(),
168205
});
169206
}
170-
sample_fields = quote! {
171-
{ #sample_fields }
172-
}
207+
sample_fields = quote! {{ #sample_fields }};
173208
}
174209
Fields::Unnamed(_) => {
175210
for _ in fields {
176211
sample_fields.extend(quote! {
177212
AbiExample::example(),
178213
});
179214
}
180-
sample_fields = quote! {
181-
( #sample_fields )
182-
}
215+
sample_fields = quote! {( #sample_fields )};
183216
}
184217
_ => unimplemented!("fields: {:?}", fields),
185218
}
@@ -195,7 +228,7 @@ fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
195228
impl #impl_generics ::solana_frozen_abi::abi_example::AbiExample for #type_name #ty_generics #where_clause {
196229
fn example() -> Self {
197230
::std::println!(
198-
"AbiExample for struct: {}",
231+
"AbiExample::example for struct: {}",
199232
std::any::type_name::<#type_name #ty_generics>()
200233
);
201234
use ::solana_frozen_abi::abi_example::AbiExample;
@@ -277,39 +310,61 @@ pub fn derive_abi_enum_visitor(item: TokenStream) -> TokenStream {
277310
fn quote_for_test(
278311
test_mod_ident: &Ident,
279312
type_name: &Ident,
280-
expected_digest: &str,
313+
expected_api_digest: Option<&str>,
314+
expected_abi_digest: Option<&str>,
281315
) -> TokenStream2 {
282-
quote! {
283-
#[cfg(test)]
284-
mod #test_mod_ident {
285-
use super::*;
286-
use ::solana_frozen_abi::abi_example::{AbiExample, AbiEnumVisitor};
287-
316+
let test_api = if expected_api_digest.is_some() {
317+
quote! {
288318
#[test]
289-
fn test_abi_digest() {
319+
fn test_api_digest() {
320+
use ::solana_frozen_abi::abi_example::{AbiExample, AbiEnumVisitor};
290321
let mut digester = ::solana_frozen_abi::abi_digester::AbiDigester::create();
291322
let example = <#type_name>::example();
292323
let result = <_>::visit_for_abi(&&example, &mut digester);
293-
let mut hash = digester.finalize();
294-
if result.is_err() {
295-
::std::eprintln!("Error: digest error: {:#?}", result);
296-
}
297324
result.unwrap();
298-
let actual_digest = ::std::format!("{}", hash);
299-
if ::std::env::var("SOLANA_ABI_BULK_UPDATE").is_ok() {
300-
if #expected_digest != actual_digest {
301-
::std::eprintln!("sed -i -e 's/{}/{}/g' $(git grep --files-with-matches frozen_abi)", #expected_digest, hash);
302-
}
303-
::std::eprintln!("Warning: Not testing the abi digest under SOLANA_ABI_BULK_UPDATE!");
304-
} else {
305-
if let Ok(dir) = ::std::env::var("SOLANA_ABI_DUMP_DIR") {
306-
assert_eq!(#expected_digest, actual_digest, "Possibly ABI changed? Examine the diff in SOLANA_ABI_DUMP_DIR!: \n$ diff -u {}/*{}* {}/*{}*", dir, #expected_digest, dir, actual_digest);
307-
} else {
308-
assert_eq!(#expected_digest, actual_digest, "Possibly ABI changed? Confirm the diff by rerunning before and after this test failed with SOLANA_ABI_DUMP_DIR!");
309-
}
325+
let hash = digester.finalize();
326+
assert_eq!(#expected_api_digest, format!("{}", hash));
327+
}
328+
}
329+
} else {
330+
TokenStream2::new()
331+
};
332+
333+
let test_abi = if expected_abi_digest.is_some() {
334+
quote! {
335+
#[test]
336+
fn test_abi_digest() {
337+
use ::rand::{SeedableRng, RngCore};
338+
use ::rand_chacha::ChaCha8Rng;
339+
use ::bincode;
340+
use ::solana_frozen_abi::stable_abi::StableAbi;
341+
342+
let mut rng = ChaCha8Rng::seed_from_u64(20666175621446498);
343+
let mut digester = ::solana_frozen_abi::hash::Hasher::default();
344+
345+
for _ in 0..100_000 {
346+
let val = <#type_name>::random(&mut rng);
347+
digester.hash(&bincode::serialize(&val).unwrap());
310348
}
349+
assert_eq!(#expected_abi_digest, format!("{}", digester.result()));
311350
}
312351
}
352+
} else {
353+
TokenStream2::new()
354+
};
355+
356+
let no_tests = test_api.is_empty() && test_abi.is_empty();
357+
if no_tests {
358+
return TokenStream2::new();
359+
}
360+
361+
quote! {
362+
#[cfg(test)]
363+
mod #test_mod_ident {
364+
use super::*;
365+
#test_api
366+
#test_abi
367+
}
313368
}
314369
}
315370

@@ -319,9 +374,18 @@ fn test_mod_name(type_name: &Ident) -> Ident {
319374
}
320375

321376
#[cfg(feature = "frozen-abi")]
322-
fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream {
377+
fn frozen_abi_type_alias(
378+
input: ItemType,
379+
expected_api_digest: Option<&str>,
380+
expected_abi_digest: Option<&str>,
381+
) -> TokenStream {
323382
let type_name = &input.ident;
324-
let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
383+
let test = quote_for_test(
384+
&test_mod_name(type_name),
385+
type_name,
386+
expected_api_digest,
387+
expected_abi_digest,
388+
);
325389
let result = quote! {
326390
#input
327391
#test
@@ -330,9 +394,18 @@ fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream
330394
}
331395

332396
#[cfg(feature = "frozen-abi")]
333-
fn frozen_abi_struct_type(input: ItemStruct, expected_digest: &str) -> TokenStream {
397+
fn frozen_abi_struct_type(
398+
input: ItemStruct,
399+
expected_api_digest: Option<&str>,
400+
expected_abi_digest: Option<&str>,
401+
) -> TokenStream {
334402
let type_name = &input.ident;
335-
let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
403+
let test = quote_for_test(
404+
&test_mod_name(type_name),
405+
type_name,
406+
expected_api_digest,
407+
expected_abi_digest,
408+
);
336409
let result = quote! {
337410
#input
338411
#test
@@ -387,9 +460,18 @@ fn quote_sample_variant(
387460
}
388461

389462
#[cfg(feature = "frozen-abi")]
390-
fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
463+
fn frozen_abi_enum_type(
464+
input: ItemEnum,
465+
expected_api_digest: Option<&str>,
466+
expected_abi_digest: Option<&str>,
467+
) -> TokenStream {
391468
let type_name = &input.ident;
392-
let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
469+
let test = quote_for_test(
470+
&test_mod_name(type_name),
471+
type_name,
472+
expected_api_digest,
473+
expected_abi_digest,
474+
);
393475
let result = quote! {
394476
#input
395477
#test
@@ -400,31 +482,40 @@ fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
400482
#[cfg(feature = "frozen-abi")]
401483
#[proc_macro_attribute]
402484
pub fn frozen_abi(attrs: TokenStream, item: TokenStream) -> TokenStream {
403-
let mut expected_digest: Option<String> = None;
485+
let mut api_expected_digest: Option<String> = None;
486+
let mut abi_expected_digest: Option<String> = None;
487+
404488
let attrs_parser = syn::meta::parser(|meta| {
405489
if meta.path.is_ident("digest") {
406-
expected_digest = Some(meta.value()?.parse::<LitStr>()?.value());
490+
api_expected_digest = Some(meta.value()?.parse::<LitStr>()?.value());
491+
Ok(())
492+
} else if meta.path.is_ident("abi_digest") {
493+
abi_expected_digest = Some(meta.value()?.parse::<LitStr>()?.value());
407494
Ok(())
408495
} else {
409496
Err(meta.error("unsupported \"frozen_abi\" property"))
410497
}
411498
});
412499
parse_macro_input!(attrs with attrs_parser);
413500

414-
let Some(expected_digest) = expected_digest else {
415-
return Error::new_spanned(
416-
TokenStream2::from(item),
417-
"the required \"digest\" = ... attribute is missing.",
418-
)
419-
.to_compile_error()
420-
.into();
421-
};
422-
423501
let item = parse_macro_input!(item as Item);
502+
424503
match item {
425-
Item::Struct(input) => frozen_abi_struct_type(input, &expected_digest),
426-
Item::Enum(input) => frozen_abi_enum_type(input, &expected_digest),
427-
Item::Type(input) => frozen_abi_type_alias(input, &expected_digest),
504+
Item::Struct(input) => frozen_abi_struct_type(
505+
input,
506+
api_expected_digest.as_deref(),
507+
abi_expected_digest.as_deref(),
508+
),
509+
Item::Enum(input) => frozen_abi_enum_type(
510+
input,
511+
api_expected_digest.as_deref(),
512+
abi_expected_digest.as_deref(),
513+
),
514+
Item::Type(input) => frozen_abi_type_alias(
515+
input,
516+
api_expected_digest.as_deref(),
517+
abi_expected_digest.as_deref(),
518+
),
428519
_ => Error::new_spanned(
429520
item,
430521
"frozen_abi isn't applicable; only for struct, enum and type",

frozen-abi-test/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "frozen-abi-test"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde = { version = "1", features = ["derive"] }
8+
bincode = { workspace = true }
9+
rand = { workspace = true }
10+
rand_chacha = "0.3"
11+
sha2 = { workspace = true }
12+
13+
solana-frozen-abi = { path = "../frozen-abi", features = ["frozen-abi"] }
14+
solana-frozen-abi-macro = { path = "../frozen-abi-macro", features = ["frozen-abi"] }
15+
16+
[features]
17+
frozen-abi = []

frozen-abi-test/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
2+
3+
use rand::distributions::{Distribution, Standard};
4+
use rand::Rng;
5+
use serde::{Deserialize, Serialize};
6+
use solana_frozen_abi_macro::frozen_abi;
7+
use solana_frozen_abi_macro::AbiExample;
8+
use solana_frozen_abi_macro::StableAbi;
9+
10+
#[cfg_attr(feature = "frozen-abi", derive(StableAbi), derive(AbiExample))]
11+
#[cfg_attr(
12+
feature = "frozen-abi",
13+
frozen_abi(
14+
api_digest = "H4pHPLGaqzQVAnMDKCgHD3R5sqWNxZwpCuZpmukAQSa",
15+
abi_digest = "5RoDSUMyqu38JmXiZBKXzAGRuku7JfHtKamyGqnYN7ND"
16+
)
17+
)]
18+
#[derive(Serialize, Deserialize)]
19+
pub struct BlockhashQueue {
20+
pub last_hash_index: u64,
21+
pub a: u16,
22+
}
23+
24+
impl Distribution<BlockhashQueue> for Standard {
25+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> BlockhashQueue {
26+
BlockhashQueue {
27+
last_hash_index: rng.gen(),
28+
a: rng.gen(),
29+
}
30+
}
31+
}

frozen-abi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ serde_with = { workspace = true }
3333
sha2 = { workspace = true }
3434
solana-frozen-abi-macro = { workspace = true }
3535
thiserror = { workspace = true }
36+
rand = { workspace = true }
3637

3738
[target.'cfg(not(target_os = "solana"))'.dependencies]
3839
im = { workspace = true, features = ["rayon", "serde"] }

0 commit comments

Comments
 (0)