Skip to content

Commit e66e020

Browse files
update lightdiscriminator macro
1 parent 8634ec1 commit e66e020

File tree

4 files changed

+240
-5
lines changed

4 files changed

+240
-5
lines changed

sdk-libs/macros/src/discriminator.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ use quote::quote;
44
use syn::{ItemStruct, Result};
55

66
pub(crate) fn discriminator(input: ItemStruct) -> Result<TokenStream> {
7+
discriminator_with_hasher(input, false)
8+
}
9+
10+
pub(crate) fn discriminator_sha(input: ItemStruct) -> Result<TokenStream> {
11+
discriminator_with_hasher(input, true)
12+
}
13+
14+
fn discriminator_with_hasher(input: ItemStruct, is_sha: bool) -> Result<TokenStream> {
715
let account_name = &input.ident;
816

917
let (impl_gen, type_gen, where_clause) = input.generics.split_for_impl();
@@ -12,6 +20,10 @@ pub(crate) fn discriminator(input: ItemStruct) -> Result<TokenStream> {
1220
discriminator.copy_from_slice(&Sha256::hash(account_name.to_string().as_bytes()).unwrap()[..8]);
1321
let discriminator: proc_macro2::TokenStream = format!("{discriminator:?}").parse().unwrap();
1422

23+
// For SHA256 variant, we could add specific logic here if needed
24+
// Currently both variants work the same way since discriminator is just based on struct name
25+
let _variant_marker = if is_sha { "sha256" } else { "poseidon" };
26+
1527
Ok(quote! {
1628
impl #impl_gen LightDiscriminator for #account_name #type_gen #where_clause {
1729
const LIGHT_DISCRIMINATOR: [u8; 8] = #discriminator;
@@ -47,4 +59,46 @@ mod tests {
4759
assert!(output.contains("impl LightDiscriminator for MyAccount"));
4860
assert!(output.contains("[181 , 255 , 112 , 42 , 17 , 188 , 66 , 199]"));
4961
}
62+
63+
#[test]
64+
fn test_discriminator_sha() {
65+
let input: ItemStruct = parse_quote! {
66+
struct MyAccount {
67+
a: u32,
68+
b: i32,
69+
c: u64,
70+
d: i64,
71+
}
72+
};
73+
74+
let output = discriminator_sha(input).unwrap();
75+
let output = output.to_string();
76+
77+
assert!(output.contains("impl LightDiscriminator for MyAccount"));
78+
assert!(output.contains("[181 , 255 , 112 , 42 , 17 , 188 , 66 , 199]"));
79+
}
80+
81+
#[test]
82+
fn test_discriminator_sha_large_struct() {
83+
// Test that SHA256 discriminator can handle large structs (that would fail with regular hasher)
84+
let input: ItemStruct = parse_quote! {
85+
struct LargeAccount {
86+
pub field1: u64, pub field2: u64, pub field3: u64, pub field4: u64,
87+
pub field5: u64, pub field6: u64, pub field7: u64, pub field8: u64,
88+
pub field9: u64, pub field10: u64, pub field11: u64, pub field12: u64,
89+
pub field13: u64, pub field14: u64, pub field15: u64,
90+
pub owner: solana_program::pubkey::Pubkey,
91+
pub authority: solana_program::pubkey::Pubkey,
92+
}
93+
};
94+
95+
let result = discriminator_sha(input);
96+
assert!(
97+
result.is_ok(),
98+
"SHA256 discriminator should handle large structs"
99+
);
100+
101+
let output = result.unwrap().to_string();
102+
assert!(output.contains("impl LightDiscriminator for LargeAccount"));
103+
}
50104
}

sdk-libs/macros/src/hasher/light_hasher.rs

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl ::light_hasher::DataHasher for TruncateOptionStruct {
276276
#[cfg(debug_assertions)]
277277
{
278278
if std::env::var("RUST_BACKTRACE").is_ok() {
279-
let debug_prints: Vec<[u8; 32]> = vec![
279+
let debug_prints: Vec<[u8;32]> = vec![
280280
if let Some(a) = & self.a { let result = a.hash_to_field_size() ?; if
281281
result == [0u8; 32] { return
282282
Err(::light_hasher::errors::HasherError::OptionHashToFieldSizeZero); }
@@ -560,4 +560,154 @@ impl ::light_hasher::DataHasher for OuterStruct {
560560
"Should mention SHA256 limitation"
561561
);
562562
}
563+
564+
#[test]
565+
fn test_sha256_with_discriminator_integration() {
566+
// Test that shows LightHasherSha works with LightDiscriminatorSha for large structs
567+
// This would be impossible with regular Poseidon-based macros
568+
let input: ItemStruct = parse_quote! {
569+
struct LargeIntegratedAccount {
570+
pub field1: u64, pub field2: u64, pub field3: u64, pub field4: u64,
571+
pub field5: u64, pub field6: u64, pub field7: u64, pub field8: u64,
572+
pub field9: u64, pub field10: u64, pub field11: u64, pub field12: u64,
573+
pub field13: u64, pub field14: u64, pub field15: u64, pub field16: u64,
574+
pub field17: u64, pub field18: u64, pub field19: u64, pub field20: u64,
575+
// Pubkeys without #[hash] attribute
576+
pub owner: solana_program::pubkey::Pubkey,
577+
pub authority: solana_program::pubkey::Pubkey,
578+
pub delegate: solana_program::pubkey::Pubkey,
579+
}
580+
};
581+
582+
// Both SHA256 hasher and discriminator should work
583+
let sha_hasher_result = derive_light_hasher_sha(input.clone());
584+
assert!(
585+
sha_hasher_result.is_ok(),
586+
"SHA256 hasher should work with large structs"
587+
);
588+
589+
let sha_discriminator_result = crate::discriminator::discriminator_sha(input.clone());
590+
assert!(
591+
sha_discriminator_result.is_ok(),
592+
"SHA256 discriminator should work with large structs"
593+
);
594+
595+
// Regular Poseidon variants should fail
596+
let poseidon_hasher_result = derive_light_hasher(input);
597+
assert!(
598+
poseidon_hasher_result.is_err(),
599+
"Poseidon hasher should fail with large structs"
600+
);
601+
602+
// Verify the generated code contains expected patterns
603+
let sha_hasher_code = sha_hasher_result.unwrap().to_string();
604+
assert!(
605+
sha_hasher_code.contains("try_to_vec"),
606+
"Should use serialization approach"
607+
);
608+
assert!(
609+
sha_hasher_code.contains("BorshSerialize"),
610+
"Should use Borsh serialization"
611+
);
612+
613+
let sha_discriminator_code = sha_discriminator_result.unwrap().to_string();
614+
assert!(
615+
sha_discriminator_code.contains("LightDiscriminator"),
616+
"Should implement LightDiscriminator"
617+
);
618+
assert!(
619+
sha_discriminator_code.contains("LIGHT_DISCRIMINATOR"),
620+
"Should provide discriminator constant"
621+
);
622+
}
623+
624+
#[test]
625+
fn test_complete_sha256_ecosystem_practical_example() {
626+
// Demonstrates a real-world scenario where SHA256 variants are essential
627+
// This struct would be impossible with Poseidon due to:
628+
// 1. >12 fields (23+ fields)
629+
// 2. Multiple Pubkeys without #[hash] attribute
630+
// 3. Large data structures
631+
let input: ItemStruct = parse_quote! {
632+
pub struct ComplexGameState {
633+
// Game metadata (13 fields)
634+
pub game_id: u64,
635+
pub round: u32,
636+
pub turn: u8,
637+
pub phase: u8,
638+
pub start_time: i64,
639+
pub end_time: i64,
640+
pub max_players: u8,
641+
pub current_players: u8,
642+
pub entry_fee: u64,
643+
pub prize_pool: u64,
644+
pub game_mode: u32,
645+
pub difficulty: u8,
646+
pub status: u8,
647+
648+
// Player information (6 Pubkey fields - would require #[hash] with Poseidon)
649+
pub creator: solana_program::pubkey::Pubkey,
650+
pub winner: solana_program::pubkey::Pubkey,
651+
pub current_player: solana_program::pubkey::Pubkey,
652+
pub authority: solana_program::pubkey::Pubkey,
653+
pub treasury: solana_program::pubkey::Pubkey,
654+
pub program_id: solana_program::pubkey::Pubkey,
655+
656+
// Game state data (4+ more fields)
657+
pub board_state: [u8; 64], // Large array
658+
pub player_scores: [u32; 8], // Array of scores
659+
pub moves_history: [u16; 32], // Move history
660+
pub special_flags: u32,
661+
662+
// This gives us 23+ fields total - way beyond Poseidon's 12-field limit
663+
}
664+
};
665+
666+
// SHA256 variants should handle this complex struct effortlessly
667+
let sha_hasher_result = derive_light_hasher_sha(input.clone());
668+
assert!(
669+
sha_hasher_result.is_ok(),
670+
"SHA256 hasher must handle complex real-world structs"
671+
);
672+
673+
let sha_discriminator_result = crate::discriminator::discriminator_sha(input.clone());
674+
assert!(
675+
sha_discriminator_result.is_ok(),
676+
"SHA256 discriminator must handle complex real-world structs"
677+
);
678+
679+
// Poseidon would fail with this struct
680+
let poseidon_result = derive_light_hasher(input);
681+
assert!(
682+
poseidon_result.is_err(),
683+
"Poseidon cannot handle structs with >12 fields and unhashed Pubkeys"
684+
);
685+
686+
// Verify SHA256 generates efficient serialization-based code
687+
let hasher_code = sha_hasher_result.unwrap().to_string();
688+
assert!(
689+
hasher_code.contains("try_to_vec"),
690+
"Should serialize entire struct efficiently"
691+
);
692+
assert!(
693+
hasher_code.contains("BorshSerialize"),
694+
"Should use Borsh for serialization"
695+
);
696+
assert!(
697+
hasher_code.contains("result [0] = 0") || hasher_code.contains("result[0] = 0"),
698+
"Should apply field size truncation. Actual code: {}",
699+
hasher_code
700+
);
701+
702+
// Verify discriminator works correctly
703+
let discriminator_code = sha_discriminator_result.unwrap().to_string();
704+
assert!(
705+
discriminator_code.contains("ComplexGameState"),
706+
"Should target correct struct"
707+
);
708+
assert!(
709+
discriminator_code.contains("LIGHT_DISCRIMINATOR"),
710+
"Should provide discriminator constant"
711+
);
712+
}
563713
}

sdk-libs/macros/src/lib.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extern crate proc_macro;
22
use accounts::{process_light_accounts, process_light_system_accounts};
3+
use discriminator::{discriminator, discriminator_sha};
34
use hasher::{derive_light_hasher, derive_light_hasher_sha};
45
use proc_macro::TokenStream;
56
use syn::{parse_macro_input, DeriveInput, ItemMod, ItemStruct};
@@ -135,7 +136,35 @@ pub fn light_traits_derive(input: TokenStream) -> TokenStream {
135136
#[proc_macro_derive(LightDiscriminator)]
136137
pub fn light_discriminator(input: TokenStream) -> TokenStream {
137138
let input = parse_macro_input!(input as ItemStruct);
138-
discriminator::discriminator(input)
139+
discriminator(input)
140+
.unwrap_or_else(|err| err.to_compile_error())
141+
.into()
142+
}
143+
144+
/// SHA256 variant of the LightDiscriminator derive macro.
145+
///
146+
/// This derive macro provides the same discriminator functionality as LightDiscriminator
147+
/// but is designed to be used with SHA256-based hashing for consistency.
148+
///
149+
/// ## Example
150+
///
151+
/// ```ignore
152+
/// use light_sdk::sha::{LightHasher, LightDiscriminator};
153+
///
154+
/// #[derive(LightHasher, LightDiscriminator)]
155+
/// pub struct LargeGameState {
156+
/// pub field1: u64, pub field2: u64, pub field3: u64, pub field4: u64,
157+
/// pub field5: u64, pub field6: u64, pub field7: u64, pub field8: u64,
158+
/// pub field9: u64, pub field10: u64, pub field11: u64, pub field12: u64,
159+
/// pub field13: u64, pub field14: u64, pub field15: u64,
160+
/// pub owner: Pubkey,
161+
/// pub authority: Pubkey,
162+
/// }
163+
/// ```
164+
#[proc_macro_derive(LightDiscriminatorSha)]
165+
pub fn light_discriminator_sha(input: TokenStream) -> TokenStream {
166+
let input = parse_macro_input!(input as ItemStruct);
167+
discriminator_sha(input)
139168
.unwrap_or_else(|err| err.to_compile_error())
140169
.into()
141170
}

sdk-libs/sdk/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ pub use account::LightAccount;
107107

108108
/// SHA256-based variants
109109
pub mod sha {
110-
pub use light_sdk_macros::LightHasherSha as LightHasher;
110+
pub use light_sdk_macros::{
111+
LightDiscriminatorSha as LightDiscriminator, LightHasherSha as LightHasher,
112+
};
111113

112114
pub use crate::account::sha::LightAccount;
113115
}
@@ -132,8 +134,8 @@ use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSeria
132134
pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator};
133135
pub use light_hasher;
134136
pub use light_sdk_macros::{
135-
derive_light_cpi_signer, light_system_accounts, LightDiscriminator, LightHasher,
136-
LightHasherSha, LightTraits,
137+
derive_light_cpi_signer, light_system_accounts, LightDiscriminator, LightDiscriminatorSha,
138+
LightHasher, LightHasherSha, LightTraits,
137139
};
138140
pub use light_sdk_types::constants;
139141
use solana_account_info::AccountInfo;

0 commit comments

Comments
 (0)