@@ -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" ) ]
2763use 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" ) ]
157193fn 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 {
277310fn 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]
402484pub 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" ,
0 commit comments