11use anchor_lang_v0_21:: { AccountDeserialize , AnchorDeserialize } ;
2- use arrayref:: array_ref;
32use mpl_candy_machine:: {
43 CandyMachine , CollectionPDA , ConfigLine , CONFIG_ARRAY_START , CONFIG_LINE_SIZE ,
54} ;
@@ -15,13 +14,13 @@ const COLLECTION_PDA_SIZE: usize = 8 + 64;
1514/// returns a vector containing tuples: (`config_line`, index, taken)
1615///
1716/// it is important that this not be called if the candy machine has hidden settings
18- #[ must_use]
1917pub fn parse_cm_config_lines (
2018 data : & [ u8 ] ,
2119 items_available : usize ,
22- ) -> Vec < ( ConfigLine , usize , bool ) > {
23- let config_line_start = CONFIG_ARRAY_START + 4 ;
24- let available_bitmask_start = CONFIG_ARRAY_START + 4 + ( items_available * CONFIG_LINE_SIZE ) + 4 ;
20+ ) -> Result < Vec < ( ConfigLine , usize , bool ) > > {
21+ const CONFIG_LINE_START : usize = CONFIG_ARRAY_START + 4 ;
22+ let available_bitmask_start = CONFIG_LINE_START + ( items_available * CONFIG_LINE_SIZE ) + 4 ;
23+
2524 // NOTE(will): you would think that (items_available / 8) incorrectly computes the length of the
2625 // "available" bitmask. i.e. if there are 7 items available, this value would be 0.
2726 // however, this is how metaplex has coded it. It ends up working because there are 4 bytes of padding
@@ -35,11 +34,15 @@ pub fn parse_cm_config_lines(
3534 // Sanity check to make sure we aren't going to overflow data
3635 // This could occur if this function is called on a candy machine that uses hiddensettings instead of
3736 // config lines
38- let bytes_needed_for_taken_bitmask =
39- ( items_available / 8 ) + if items_available % 8 == 0 { 0 } else { 1 } ;
40- if taken_bitmask_start + bytes_needed_for_taken_bitmask >= data. len ( ) {
41- // TODO(will): Log warning
42- return Vec :: new ( ) ;
37+ let bytes_needed_for_taken_bitmask = items_available / 8 + ( items_available % 8 ) . min ( 1 ) ;
38+ let expected_taken_len = taken_bitmask_start + bytes_needed_for_taken_bitmask;
39+
40+ if expected_taken_len >= data. len ( ) {
41+ bail ! (
42+ "Config line bytes would overflow available data ({} vs {})" ,
43+ expected_taken_len,
44+ data. len( )
45+ ) ;
4346 }
4447
4548 // (config_line, index, taken)
@@ -52,23 +55,22 @@ pub fn parse_cm_config_lines(
5255
5356 // NOTE(will): if the config line is not available, we simply ignore it
5457 if available {
55- let config_line_byte_offset = config_line_start + ( idx * CONFIG_LINE_SIZE ) ;
56- let config_line_data = array_ref ! [ data, config_line_byte_offset, CONFIG_LINE_SIZE ] ;
57- let config_line_result = ConfigLine :: deserialize ( & mut config_line_data. as_slice ( ) ) ;
58+ let config_line_byte_offset = CONFIG_LINE_START + ( idx * CONFIG_LINE_SIZE ) ;
59+ let config_line = ConfigLine :: deserialize (
60+ & mut & data[ config_line_byte_offset..config_line_byte_offset + CONFIG_LINE_SIZE ] ,
61+ )
62+ . with_context ( || format ! ( "Failed to deserialize config line at index {}" , idx) ) ?;
63+
5864 let taken_bitmask_byte_offset = idx / 8 ;
5965 let taken_bitmask_bit_offset = 7 - ( idx % 8 ) ;
6066 let taken_bitmask_value = data[ taken_bitmask_start + taken_bitmask_byte_offset] ;
6167 let taken = taken_bitmask_value & ( 1 << taken_bitmask_bit_offset) != 0 ;
6268
63- if let Ok ( config_line) = config_line_result {
64- config_lines. push ( ( config_line, idx, taken) ) ;
65- } else {
66- // TODO(will): log some warning here that might alert us to a problem with this code?
67- }
69+ config_lines. push ( ( config_line, idx, taken) ) ;
6870 }
6971 }
7072
71- config_lines
73+ Ok ( config_lines)
7274}
7375
7476pub async fn process_collection_pda ( client : & Client , update : AccountUpdate ) -> Result < ( ) > {
@@ -82,19 +84,19 @@ pub async fn process_cm(client: &Client, update: AccountUpdate) -> Result<()> {
8284 let candy_machine: CandyMachine = CandyMachine :: try_deserialize ( & mut update. data . as_slice ( ) )
8385 . context ( "Failed to deserialize candy_machine" ) ?;
8486
85- let items_available = usize:: try_from ( candy_machine. data . items_available ) ;
86- // TODO(will): log warning if conversion fails
87-
88- match (
89- items_available ,
90- candy_machine . data . hidden_settings . is_none ( ) ,
91- ) {
92- ( Ok ( items_available ) , true ) => {
93- let config_lines = parse_cm_config_lines ( & update . data , items_available ) ;
94- candy_machine :: process ( client , update . key , candy_machine , Some ( config_lines ) ) . await
95- } ,
96- _ => candy_machine :: process ( client , update . key , candy_machine , None ) . await ,
97- }
87+ let items_available = usize:: try_from ( candy_machine. data . items_available )
88+ . context ( "Failed to convert available item count" ) ? ;
89+
90+ let lines = if candy_machine . data . hidden_settings . is_some ( ) {
91+ Some (
92+ parse_cm_config_lines ( & update . data , items_available )
93+ . context ( "Failed to parse candy machine lines" ) ? ,
94+ )
95+ } else {
96+ None
97+ } ;
98+
99+ candy_machine :: process ( client , update . key , candy_machine , lines ) . await
98100}
99101
100102pub ( crate ) async fn process ( client : & Client , update : AccountUpdate ) -> Result < ( ) > {
@@ -106,32 +108,27 @@ pub(crate) async fn process(client: &Client, update: AccountUpdate) -> Result<()
106108
107109#[ cfg( test) ]
108110mod tests {
109- use std:: { env, fs, io:: Read , path:: Path } ;
111+ use std:: { env, fs, io:: prelude :: * , path:: Path } ;
110112
111113 use anchor_lang_v0_21:: AccountDeserialize ;
112114 use mpl_candy_machine:: CandyMachine ;
113115
114- use crate :: geyser:: programs:: candy_machine:: parse_cm_config_lines;
116+ use super :: parse_cm_config_lines;
117+ use crate :: prelude:: * ;
115118
116- fn get_file_as_byte_vec < P : AsRef < Path > > ( filename : P ) -> Vec < u8 > {
117- let mut f = fs:: File :: open ( & filename) . expect ( "no file found" ) ;
118- let mut buffer = vec ! [ ] ;
119- f. read_to_end ( & mut buffer) . expect ( "File read failed" ) ;
120- buffer
121- }
119+ fn load_account_dump ( filename : impl AsRef < Path > ) -> Result < Vec < u8 > > {
120+ let mut path = env:: current_dir ( ) . context ( "Failed to get working dir" ) ?;
121+ path. extend ( [ "tests" , "data" ] ) ;
122+ path. push ( filename) ;
122123
123- fn get_data_dir ( ) -> std :: path :: PathBuf {
124- let mut data_dir = env :: current_dir ( ) . unwrap ( ) ;
125- data_dir . push ( "tests" ) ;
126- data_dir . push ( "data" ) ;
127- data_dir . to_owned ( )
128- }
124+ println ! ( "Loading: {:?}" , path ) ;
125+
126+ let mut f = fs :: File :: open ( & path ) . with_context ( || format ! ( "Failed to open {:?}" , path ) ) ? ;
127+ let mut buffer = vec ! [ ] ;
128+ f . read_to_end ( & mut buffer )
129+ . with_context ( || format ! ( "Failed to read {:?}" , path ) ) ? ;
129130
130- fn load_account_dump < P : AsRef < Path > > ( filename : P ) -> Vec < u8 > {
131- let data_dir = get_data_dir ( ) ;
132- let full_path = data_dir. join ( filename) ;
133- println ! ( "Loading: {:?}" , full_path) ;
134- return get_file_as_byte_vec ( full_path) ;
131+ Ok ( buffer)
135132 }
136133
137134 #[ test]
@@ -146,15 +143,16 @@ mod tests {
146143
147144 for filename in filenames {
148145 println ! ( "Reading Candy Machine {:?}" , filename) ;
149- let data = load_account_dump ( filename) ;
146+ let data = load_account_dump ( filename) . unwrap ( ) ;
150147 let cm = CandyMachine :: try_deserialize ( & mut data. as_slice ( ) ) . unwrap ( ) ;
151148 println ! ( "Candy Machine: {}" , filename) ;
152149
153- let results = parse_cm_config_lines ( & data, cm. data . items_available as usize ) ;
150+ let avail = usize:: try_from ( cm. data . items_available ) . unwrap ( ) ;
151+ let results = parse_cm_config_lines ( & data, avail) . unwrap ( ) ;
154152
155153 let available_count = results. len ( ) ;
156154 let mut taken_count = 0 ;
157- for ( _, _, taken) in results. iter ( ) {
155+ for ( _, _, taken) in & results {
158156 if * taken {
159157 taken_count += 1 ;
160158 }
@@ -164,7 +162,7 @@ mod tests {
164162 assert_eq ! ( available_count, 0 ) ;
165163 assert_eq ! ( taken_count, 0 ) ;
166164 } else {
167- assert_eq ! ( available_count, cm . data . items_available as usize ) ;
165+ assert_eq ! ( available_count, avail ) ;
168166 assert_eq ! ( taken_count, cm. items_redeemed) ;
169167 }
170168 }
0 commit comments