@@ -23,6 +23,7 @@ use star_frame::{
2323/// It validates the account data on validate and provides cheap accessor methods for accessing fields
2424/// without deserializing the entire account data.
2525#[ derive( AccountSet , Debug , Clone ) ]
26+ #[ account_set( skip_default_idl) ]
2627#[ validate( extra_validation = self . validate( ) ) ]
2728#[ validate(
2829 id = "validate_mint" , arg = ValidateMint <' a>, generics = [ <' a>] ,
@@ -46,8 +47,19 @@ impl HasInnerType for MintAccount {
4647
4748/// See [`spl_token_interface::state::Mint`].
4849#[ derive(
49- Debug , Clone , PartialEq , Eq , Copy , Default , Zeroable , CheckedBitPattern , Align1 , NoUninit ,
50+ Debug ,
51+ Clone ,
52+ PartialEq ,
53+ Eq ,
54+ Copy ,
55+ Default ,
56+ Zeroable ,
57+ CheckedBitPattern ,
58+ Align1 ,
59+ NoUninit ,
60+ TypeToIdl ,
5061) ]
62+ #[ type_to_idl( program = crate :: token:: Token ) ]
5163#[ repr( C , packed) ]
5264pub struct MintAccountData {
5365 pub mint_authority : PodOption < Pubkey > ,
@@ -267,6 +279,7 @@ where
267279/// It validates the account data on validate and provides cheap accessor methods for accessing fields
268280/// without deserializing the entire account data, although it does provide full deserialization methods.
269281#[ derive( AccountSet , Debug , Clone ) ]
282+ #[ account_set( skip_default_idl) ]
270283#[ validate( extra_validation = self . validate( ) ) ]
271284#[ validate(
272285 id = "validate_token" ,
@@ -292,8 +305,19 @@ impl HasInnerType for TokenAccount {
292305
293306/// See [`spl_token_interface::state::AccountState`].
294307#[ derive(
295- Debug , Clone , PartialEq , Eq , Copy , Default , Zeroable , CheckedBitPattern , Align1 , NoUninit ,
308+ Debug ,
309+ Clone ,
310+ PartialEq ,
311+ Eq ,
312+ Copy ,
313+ Default ,
314+ Zeroable ,
315+ CheckedBitPattern ,
316+ Align1 ,
317+ NoUninit ,
318+ TypeToIdl ,
296319) ]
320+ #[ type_to_idl( program = crate :: token:: Token ) ]
297321#[ repr( u8 ) ]
298322pub enum AccountState {
299323 /// Account is not yet initialized
@@ -308,7 +332,10 @@ pub enum AccountState {
308332}
309333
310334/// See [`spl_token_interface::state::Account`].
311- #[ derive( Clone , Copy , Debug , Default , PartialEq , CheckedBitPattern , Zeroable , NoUninit ) ]
335+ #[ derive(
336+ Clone , Copy , Debug , Default , PartialEq , CheckedBitPattern , Zeroable , NoUninit , TypeToIdl ,
337+ ) ]
338+ #[ type_to_idl( program = crate :: token:: Token ) ]
312339#[ repr( C , packed) ]
313340pub struct TokenAccountData {
314341 pub mint : KeyFor < MintAccount > ,
@@ -493,9 +520,267 @@ where
493520 }
494521}
495522
523+ #[ cfg( all( feature = "idl" , not( target_os = "solana" ) ) ) ]
524+ mod idl_impl {
525+ use super :: * ;
526+ use star_frame:: {
527+ idl:: { AccountSetToIdl , AccountToIdl , ProgramToIdl , TypeToIdl } ,
528+ star_frame_idl:: {
529+ account:: { IdlAccount , IdlAccountId } ,
530+ account_set:: IdlAccountSetDef ,
531+ item_source, IdlDefinition ,
532+ } ,
533+ } ;
534+
535+ fn register_spl_account < T : TypeToIdl > (
536+ idl_definition : & mut IdlDefinition ,
537+ ) -> star_frame:: IdlResult < IdlAccountId > {
538+ let type_def = T :: type_to_idl ( idl_definition) ?;
539+ let type_id = type_def. assert_defined ( ) ?. clone ( ) ;
540+ let namespace = <T :: AssociatedProgram as ProgramToIdl >:: crate_metadata ( ) . name ;
541+ let idl_account = IdlAccount {
542+ discriminant : Vec :: new ( ) ,
543+ type_id,
544+ seeds : None ,
545+ } ;
546+ let namespace = idl_definition. add_account ( idl_account, namespace) ?;
547+ Ok ( IdlAccountId {
548+ namespace,
549+ source : item_source :: < T > ( ) ,
550+ } )
551+ }
552+
553+ impl AccountToIdl for MintAccountData {
554+ fn account_to_idl (
555+ idl_definition : & mut IdlDefinition ,
556+ ) -> star_frame:: IdlResult < IdlAccountId > {
557+ register_spl_account :: < Self > ( idl_definition)
558+ }
559+ }
560+
561+ impl AccountToIdl for TokenAccountData {
562+ fn account_to_idl (
563+ idl_definition : & mut IdlDefinition ,
564+ ) -> star_frame:: IdlResult < IdlAccountId > {
565+ register_spl_account :: < Self > ( idl_definition)
566+ }
567+ }
568+
569+ impl < A > AccountSetToIdl < A > for MintAccount
570+ where
571+ AccountInfo : AccountSetToIdl < A > ,
572+ {
573+ fn account_set_to_idl (
574+ idl_definition : & mut IdlDefinition ,
575+ arg : A ,
576+ ) -> star_frame:: IdlResult < IdlAccountSetDef > {
577+ let mut set = AccountInfo :: account_set_to_idl ( idl_definition, arg) ?;
578+ set. single ( ) ?
579+ . program_accounts
580+ . push ( MintAccountData :: account_to_idl ( idl_definition) ?) ;
581+ Ok ( set)
582+ }
583+ }
584+
585+ impl < A > AccountSetToIdl < A > for TokenAccount
586+ where
587+ AccountInfo : AccountSetToIdl < A > ,
588+ {
589+ fn account_set_to_idl (
590+ idl_definition : & mut IdlDefinition ,
591+ arg : A ,
592+ ) -> star_frame:: IdlResult < IdlAccountSetDef > {
593+ let mut set = AccountInfo :: account_set_to_idl ( idl_definition, arg) ?;
594+ set. single ( ) ?
595+ . program_accounts
596+ . push ( TokenAccountData :: account_to_idl ( idl_definition) ?) ;
597+ Ok ( set)
598+ }
599+ }
600+ }
601+
496602#[ cfg( test) ]
497603mod tests {
498604 use super :: * ;
605+ #[ cfg( all( feature = "idl" , not( target_os = "solana" ) ) ) ]
606+ use star_frame:: empty_star_frame_instruction;
607+
608+ #[ cfg( all( feature = "idl" , not( target_os = "solana" ) ) ) ]
609+ mod mini_program {
610+ use super :: * ;
611+ use star_frame:: star_frame_idl:: { CrateMetadata , Version } ;
612+
613+ #[ derive(
614+ Copy , Clone , Debug , Eq , PartialEq , InstructionArgs , BorshDeserialize , BorshSerialize ,
615+ ) ]
616+ #[ type_to_idl( program = crate :: token:: state:: tests:: mini_program:: MintTokenTestProgram ) ]
617+ pub struct TouchSplAccounts ;
618+
619+ #[ derive( Debug , Clone , AccountSet ) ]
620+ pub struct TouchSplAccountsAccounts {
621+ pub mint : MintAccount ,
622+ pub token : TokenAccount ,
623+ }
624+ empty_star_frame_instruction ! ( TouchSplAccounts , TouchSplAccountsAccounts ) ;
625+
626+ #[ derive( Copy , Debug , Clone , PartialEq , Eq , InstructionSet ) ]
627+ #[ ix_set( use_repr) ]
628+ #[ repr( u8 ) ]
629+ pub enum MintTokenInstructionSet {
630+ TouchSplAccounts ( TouchSplAccounts ) ,
631+ }
632+
633+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
634+ pub struct MintTokenTestProgram ;
635+
636+ impl StarFrameProgram for MintTokenTestProgram {
637+ type InstructionSet = MintTokenInstructionSet ;
638+ type AccountDiscriminant = ( ) ;
639+ const ID : Pubkey = pubkey ! ( "11111111111111111111111111111111" ) ;
640+ }
641+
642+ impl ProgramToIdl for MintTokenTestProgram {
643+ type Errors = ( ) ;
644+ fn crate_metadata ( ) -> CrateMetadata {
645+ CrateMetadata {
646+ name : "mint_token_test_program" . to_string ( ) ,
647+ version : Version :: new ( 0 , 0 , 1 ) ,
648+ ..Default :: default ( )
649+ }
650+ }
651+ }
652+ }
653+
654+ #[ cfg( all( feature = "idl" , not( target_os = "solana" ) ) ) ]
655+ #[ test]
656+ fn mint_and_token_accounts_emit_idl_entries ( ) -> Result < ( ) > {
657+ use star_frame:: {
658+ idl:: { AccountSetToIdl , AccountToIdl , ProgramToIdl } ,
659+ star_frame_idl:: { account_set:: IdlAccountSetDef , IdlDefinition , IdlMetadata } ,
660+ } ;
661+
662+ let mut idl = IdlDefinition {
663+ address : Token :: ID ,
664+ metadata : IdlMetadata {
665+ crate_metadata : Token :: crate_metadata ( ) ,
666+ ..Default :: default ( )
667+ } ,
668+ ..Default :: default ( )
669+ } ;
670+
671+ let mint_account_id = MintAccountData :: account_to_idl ( & mut idl) ?;
672+ assert ! (
673+ idl. accounts. contains_key( & mint_account_id. source) ,
674+ "MintAccountData definition missing from IDL"
675+ ) ;
676+ assert ! (
677+ idl. types. contains_key( & mint_account_id. source) ,
678+ "MintAccountData TypeToIdl definition missing"
679+ ) ;
680+
681+ let token_account_id = TokenAccountData :: account_to_idl ( & mut idl) ?;
682+ assert ! (
683+ idl. accounts. contains_key( & token_account_id. source) ,
684+ "TokenAccountData definition missing from IDL"
685+ ) ;
686+ assert ! (
687+ idl. types. contains_key( & token_account_id. source) ,
688+ "TokenAccountData TypeToIdl definition missing"
689+ ) ;
690+
691+ match MintAccount :: account_set_to_idl ( & mut idl, ( ) ) ? {
692+ IdlAccountSetDef :: Single ( single) => {
693+ assert ! (
694+ single. program_accounts. contains( & mint_account_id) ,
695+ "MintAccount did not register its program account entry" ,
696+ ) ;
697+ }
698+ other => panic ! ( "MintAccount should produce a single account set, got {other:?}" ) ,
699+ }
700+
701+ match TokenAccount :: account_set_to_idl ( & mut idl, ( ) ) ? {
702+ IdlAccountSetDef :: Single ( single) => {
703+ assert ! (
704+ single. program_accounts. contains( & token_account_id) ,
705+ "TokenAccount did not register its program account entry" ,
706+ ) ;
707+ }
708+ other => panic ! ( "TokenAccount should produce a single account set, got {other:?}" ) ,
709+ }
710+
711+ Ok ( ( ) )
712+ }
713+
714+ #[ cfg( all( feature = "idl" , not( target_os = "solana" ) ) ) ]
715+ #[ test]
716+ fn mini_program_uses_spl_accounts_in_idl ( ) -> Result < ( ) > {
717+ use mini_program:: * ;
718+ use star_frame:: {
719+ idl:: ProgramToIdl ,
720+ star_frame_idl:: {
721+ account_set:: { IdlAccountSetDef , IdlAccountSetStructField } ,
722+ item_source, IdlDefinition ,
723+ } ,
724+ } ;
725+
726+ fn struct_fields < ' a > (
727+ idl : & ' a IdlDefinition ,
728+ def : & ' a IdlAccountSetDef ,
729+ ) -> & ' a [ IdlAccountSetStructField ] {
730+ let set = def
731+ . get_defined ( idl)
732+ . expect ( "Missing referenced account set" ) ;
733+ match & set. account_set_def {
734+ IdlAccountSetDef :: Struct ( fields) => fields. as_slice ( ) ,
735+ other => panic ! ( "Expected struct account set, found {other:?}" ) ,
736+ }
737+ }
738+
739+ fn expect_field < ' a > (
740+ fields : & ' a [ IdlAccountSetStructField ] ,
741+ name : & str ,
742+ ) -> & ' a IdlAccountSetStructField {
743+ fields
744+ . iter ( )
745+ . find ( |field| field. path . as_deref ( ) == Some ( name) )
746+ . unwrap_or_else ( || panic ! ( "Missing field `{name}` in account set" ) )
747+ }
748+
749+ fn assert_has_program_account ( field : & IdlAccountSetStructField , source : & str ) {
750+ match & field. account_set_def {
751+ IdlAccountSetDef :: Single ( single) => {
752+ assert ! (
753+ single
754+ . program_accounts
755+ . iter( )
756+ . any( |account| account. source == source) ,
757+ "Expected `{source}` in program account list, found {:?}" ,
758+ single. program_accounts
759+ ) ;
760+ }
761+ other => panic ! ( "Expected single account set, found {other:?}" ) ,
762+ }
763+ }
764+
765+ let idl = MintTokenTestProgram :: program_to_idl ( ) ?;
766+ let instruction = idl
767+ . instructions
768+ . values ( )
769+ . find ( |ix| ix. definition . type_id . source . ends_with ( "TouchSplAccounts" ) )
770+ . expect ( "Instruction not found in IDL" ) ;
771+ let fields = struct_fields ( & idl, & instruction. definition . account_set ) ;
772+
773+ let mint_source = item_source :: < MintAccountData > ( ) ;
774+ let token_source = item_source :: < TokenAccountData > ( ) ;
775+
776+ let mint_field = expect_field ( fields, "mint" ) ;
777+ assert_has_program_account ( mint_field, & mint_source) ;
778+
779+ let token_field = expect_field ( fields, "token" ) ;
780+ assert_has_program_account ( token_field, & token_source) ;
781+
782+ Ok ( ( ) )
783+ }
499784
500785 #[ test]
501786 fn test_mint_accessors ( ) -> Result < ( ) > {
0 commit comments