1use borsh::{
4 BorshDeserialize,
5 BorshSerialize,
6};
7use bytemuck::{
8 cast_slice,
9 from_bytes,
10 try_cast_slice,
11 Pod,
12 PodCastError,
13 Zeroable,
14};
15use pyth_sdk::{
16 PriceIdentifier,
17 UnixTimestamp,
18};
19use solana_program::clock::Clock;
20use solana_program::pubkey::Pubkey;
21use std::cmp::min;
22use std::mem::size_of;
23
24pub use pyth_sdk::{
25 Price,
26 PriceFeed,
27};
28
29use crate::PythError;
30
31pub const MAGIC: u32 = 0xa1b2c3d4;
32pub const VERSION_2: u32 = 2;
33pub const VERSION: u32 = VERSION_2;
34pub const MAP_TABLE_SIZE: usize = 5000;
35pub const PROD_ACCT_SIZE: usize = 512;
36pub const PROD_HDR_SIZE: usize = 48;
37pub const PROD_ATTR_SIZE: usize = PROD_ACCT_SIZE - PROD_HDR_SIZE;
38
39#[derive(
41 Copy,
42 Clone,
43 Debug,
44 PartialEq,
45 Eq,
46 BorshSerialize,
47 BorshDeserialize,
48 serde::Serialize,
49 serde::Deserialize,
50 Default,
51)]
52#[repr(u8)]
53pub enum AccountType {
54 #[default]
55 Unknown,
56 Mapping,
57 Product,
58 Price,
59}
60
61#[derive(
64 Copy,
65 Clone,
66 Debug,
67 PartialEq,
68 Eq,
69 BorshSerialize,
70 BorshDeserialize,
71 serde::Serialize,
72 serde::Deserialize,
73 Default,
74)]
75#[repr(u8)]
76pub enum CorpAction {
77 #[default]
78 NoCorpAct,
79}
80
81#[derive(
84 Copy,
85 Clone,
86 Debug,
87 PartialEq,
88 Eq,
89 BorshSerialize,
90 BorshDeserialize,
91 serde::Serialize,
92 serde::Deserialize,
93 Default,
94)]
95#[repr(u8)]
96pub enum PriceType {
97 #[default]
98 Unknown,
99 Price,
100}
101
102#[derive(
104 Copy,
105 Clone,
106 Debug,
107 PartialEq,
108 Eq,
109 BorshSerialize,
110 BorshDeserialize,
111 serde::Serialize,
112 serde::Deserialize,
113 Default,
114)]
115#[repr(u8)]
116pub enum PriceStatus {
117 #[default]
119 Unknown,
120 Trading,
122 Halted,
124 Auction,
126 Ignored,
128}
129
130impl std::fmt::Display for PriceStatus {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(
133 f,
134 "{}",
135 match self {
136 Self::Unknown => "unknown",
137 Self::Trading => "trading",
138 Self::Halted => "halted",
139 Self::Auction => "auction",
140 Self::Ignored => "ignored",
141 }
142 )
143 }
144}
145
146#[derive(Copy, Clone, Debug, PartialEq, Eq)]
148#[repr(C)]
149pub struct MappingAccount {
150 pub magic: u32,
152 pub ver: u32,
154 pub atype: u32,
156 pub size: u32,
158 pub num: u32,
160 pub unused: u32,
161 pub next: Pubkey,
163 pub products: [Pubkey; MAP_TABLE_SIZE],
164}
165
166#[cfg(target_endian = "little")]
167unsafe impl Zeroable for MappingAccount {
168}
169
170#[cfg(target_endian = "little")]
171unsafe impl Pod for MappingAccount {
172}
173
174#[derive(Copy, Clone, Debug, PartialEq, Eq)]
177#[repr(C)]
178pub struct ProductAccount {
179 pub magic: u32,
181 pub ver: u32,
183 pub atype: u32,
185 pub size: u32,
187 pub px_acc: Pubkey,
189 pub attr: [u8; PROD_ATTR_SIZE],
191}
192
193impl ProductAccount {
194 pub fn iter(&self) -> AttributeIter {
195 AttributeIter {
196 attrs: &self.attr[..min(
197 (self.size as usize).saturating_sub(PROD_HDR_SIZE),
198 PROD_ATTR_SIZE,
199 )],
200 }
201 }
202}
203
204#[cfg(target_endian = "little")]
205unsafe impl Zeroable for ProductAccount {
206}
207
208#[cfg(target_endian = "little")]
209unsafe impl Pod for ProductAccount {
210}
211
212#[derive(
215 Copy,
216 Clone,
217 Debug,
218 Default,
219 PartialEq,
220 Eq,
221 BorshSerialize,
222 BorshDeserialize,
223 serde::Serialize,
224 serde::Deserialize,
225)]
226#[repr(C)]
227pub struct PriceInfo {
228 pub price: i64,
232 pub conf: u64,
236 pub status: PriceStatus,
238 pub corp_act: CorpAction,
240 pub pub_slot: u64,
241}
242
243#[derive(
245 Copy,
246 Clone,
247 Debug,
248 Default,
249 PartialEq,
250 Eq,
251 BorshSerialize,
252 BorshDeserialize,
253 serde::Serialize,
254 serde::Deserialize,
255)]
256#[repr(C)]
257pub struct PriceComp {
258 pub publisher: Pubkey,
260 pub agg: PriceInfo,
262 pub latest: PriceInfo,
265}
266
267#[deprecated = "Type is renamed to Rational, please use the new name."]
268pub type Ema = Rational;
269
270#[derive(
272 Copy,
273 Clone,
274 Debug,
275 Default,
276 PartialEq,
277 Eq,
278 BorshSerialize,
279 BorshDeserialize,
280 serde::Serialize,
281 serde::Deserialize,
282)]
283#[repr(C)]
284pub struct Rational {
285 pub val: i64,
286 pub numer: i64,
287 pub denom: i64,
288}
289
290#[repr(C)]
291#[derive(Copy, Clone, Debug, PartialEq, Eq)]
292pub struct GenericPriceAccount<const N: usize, T>
293where
294 T: Default,
295 T: Copy,
296{
297 pub magic: u32,
299 pub ver: u32,
301 pub atype: u32,
303 pub size: u32,
305 pub ptype: PriceType,
307 pub expo: i32,
309 pub num: u32,
311 pub num_qt: u32,
313 pub last_slot: u64,
315 pub valid_slot: u64,
317 pub ema_price: Rational,
319 pub ema_conf: Rational,
321 pub timestamp: i64,
323 pub min_pub: u8,
325 pub drv2: u8,
327 pub drv3: u16,
329 pub drv4: u32,
331 pub prod: Pubkey,
333 pub next: Pubkey,
335 pub prev_slot: u64,
337 pub prev_price: i64,
339 pub prev_conf: u64,
341 pub prev_timestamp: i64,
343 pub agg: PriceInfo,
345 pub comp: [PriceComp; N],
347 pub extended: T,
349}
350
351impl<const N: usize, T> Default for GenericPriceAccount<N, T>
352where
353 T: Default,
354 T: Copy,
355{
356 fn default() -> Self {
357 Self {
358 magic: Default::default(),
359 ver: Default::default(),
360 atype: Default::default(),
361 size: Default::default(),
362 ptype: Default::default(),
363 expo: Default::default(),
364 num: Default::default(),
365 num_qt: Default::default(),
366 last_slot: Default::default(),
367 valid_slot: Default::default(),
368 ema_price: Default::default(),
369 ema_conf: Default::default(),
370 timestamp: Default::default(),
371 min_pub: Default::default(),
372 drv2: Default::default(),
373 drv3: Default::default(),
374 drv4: Default::default(),
375 prod: Default::default(),
376 next: Default::default(),
377 prev_slot: Default::default(),
378 prev_price: Default::default(),
379 prev_conf: Default::default(),
380 prev_timestamp: Default::default(),
381 agg: Default::default(),
382 comp: [Default::default(); N],
383 extended: Default::default(),
384 }
385 }
386}
387
388impl<const N: usize, T> std::ops::Deref for GenericPriceAccount<N, T>
389where
390 T: Default,
391 T: Copy,
392{
393 type Target = T;
394 fn deref(&self) -> &Self::Target {
395 &self.extended
396 }
397}
398
399#[repr(C)]
400#[derive(Copy, Clone, Debug, Default, Pod, Zeroable, PartialEq, Eq)]
401pub struct PriceCumulative {
402 pub price: i128,
404 pub conf: u128,
406 pub num_down_slots: u64,
411 pub unused: u64,
413}
414
415#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
416pub struct PriceAccountExt {
417 pub price_cumulative: PriceCumulative,
418}
419
420#[deprecated(note = "use an explicit SolanaPriceAccount or PythnetPriceAccount to avoid ambiguity")]
422pub type PriceAccount = GenericPriceAccount<32, ()>;
423
424pub type SolanaPriceAccount = GenericPriceAccount<32, ()>;
426
427pub type PythnetPriceAccount = GenericPriceAccount<128, PriceAccountExt>;
429
430#[cfg(target_endian = "little")]
431unsafe impl<const N: usize, T: Default + Copy> Zeroable for GenericPriceAccount<N, T> {
432}
433
434#[cfg(target_endian = "little")]
435unsafe impl<const N: usize, T: Default + Copy + 'static> Pod for GenericPriceAccount<N, T> {
436}
437
438impl<const N: usize, T> GenericPriceAccount<N, T>
439where
440 T: Default,
441 T: Copy,
442{
443 pub fn get_publish_time(&self) -> UnixTimestamp {
444 match self.agg.status {
445 PriceStatus::Trading => self.timestamp,
446 _ => self.prev_timestamp,
447 }
448 }
449
450 pub fn get_price_no_older_than(&self, clock: &Clock, slot_threshold: u64) -> Option<Price> {
453 if self.agg.status == PriceStatus::Trading
454 && self.agg.pub_slot >= clock.slot - slot_threshold
455 {
456 return Some(Price {
457 conf: self.agg.conf,
458 expo: self.expo,
459 price: self.agg.price,
460 publish_time: self.timestamp,
461 });
462 }
463
464 if self.prev_slot >= clock.slot - slot_threshold {
465 return Some(Price {
466 conf: self.prev_conf,
467 expo: self.expo,
468 price: self.prev_price,
469 publish_time: self.prev_timestamp,
470 });
471 }
472
473 None
474 }
475
476 pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed {
477 let status = self.agg.status;
478
479 let price = match status {
480 PriceStatus::Trading => Price {
481 conf: self.agg.conf,
482 expo: self.expo,
483 price: self.agg.price,
484 publish_time: self.get_publish_time(),
485 },
486 _ => Price {
487 conf: self.prev_conf,
488 expo: self.expo,
489 price: self.prev_price,
490 publish_time: self.get_publish_time(),
491 },
492 };
493
494 let ema_price = Price {
495 conf: self.ema_conf.val as u64,
496 expo: self.expo,
497 price: self.ema_price.val,
498 publish_time: self.get_publish_time(),
499 };
500
501 PriceFeed::new(PriceIdentifier::new(price_key.to_bytes()), price, ema_price)
502 }
503}
504
505fn load<T: Pod>(data: &[u8]) -> Result<&T, PodCastError> {
506 let size = size_of::<T>();
507 if data.len() >= size {
508 Ok(from_bytes(cast_slice::<u8, u8>(try_cast_slice(
509 &data[0..size],
510 )?)))
511 } else {
512 Err(PodCastError::SizeMismatch)
513 }
514}
515
516pub fn load_mapping_account(data: &[u8]) -> Result<&MappingAccount, PythError> {
518 let pyth_mapping = load::<MappingAccount>(data).map_err(|_| PythError::InvalidAccountData)?;
519
520 if pyth_mapping.magic != MAGIC {
521 return Err(PythError::InvalidAccountData);
522 }
523 if pyth_mapping.ver != VERSION_2 {
524 return Err(PythError::BadVersionNumber);
525 }
526 if pyth_mapping.atype != AccountType::Mapping as u32 {
527 return Err(PythError::WrongAccountType);
528 }
529
530 Ok(pyth_mapping)
531}
532
533pub fn load_product_account(data: &[u8]) -> Result<&ProductAccount, PythError> {
535 let pyth_product = load::<ProductAccount>(data).map_err(|_| PythError::InvalidAccountData)?;
536
537 if pyth_product.magic != MAGIC {
538 return Err(PythError::InvalidAccountData);
539 }
540 if pyth_product.ver != VERSION_2 {
541 return Err(PythError::BadVersionNumber);
542 }
543 if pyth_product.atype != AccountType::Product as u32 {
544 return Err(PythError::WrongAccountType);
545 }
546
547 Ok(pyth_product)
548}
549
550pub fn load_price_account<const N: usize, T: Default + Copy + 'static>(
552 data: &[u8],
553) -> Result<&GenericPriceAccount<N, T>, PythError> {
554 let pyth_price =
555 load::<GenericPriceAccount<N, T>>(data).map_err(|_| PythError::InvalidAccountData)?;
556
557 if pyth_price.magic != MAGIC {
558 return Err(PythError::InvalidAccountData);
559 }
560 if pyth_price.ver != VERSION_2 {
561 return Err(PythError::BadVersionNumber);
562 }
563 if pyth_price.atype != AccountType::Price as u32 {
564 return Err(PythError::WrongAccountType);
565 }
566
567 Ok(pyth_price)
568}
569
570pub struct AttributeIter<'a> {
571 attrs: &'a [u8],
572}
573
574impl<'a> Iterator for AttributeIter<'a> {
575 type Item = (&'a str, &'a str);
576
577 fn next(&mut self) -> Option<Self::Item> {
578 if self.attrs.is_empty() {
579 return None;
580 }
581 let (key, data) = get_attr_str(self.attrs)?;
582 let (val, data) = get_attr_str(data)?;
583 self.attrs = data;
584 Some((key, val))
585 }
586}
587
588fn get_attr_str(buf: &[u8]) -> Option<(&str, &[u8])> {
589 if buf.is_empty() {
590 return Some(("", &[]));
591 }
592 let len = buf[0] as usize;
593 let str = std::str::from_utf8(buf.get(1..len + 1)?).ok()?;
594 let remaining_buf = &buf.get(len + 1..)?;
595 Some((str, remaining_buf))
596}
597
598#[cfg(test)]
599mod test {
600 use pyth_sdk::{
601 Identifier,
602 Price,
603 PriceFeed,
604 };
605 use solana_program::clock::Clock;
606 use solana_program::pubkey::Pubkey;
607
608 use crate::state::{
609 PROD_ACCT_SIZE,
610 PROD_HDR_SIZE,
611 };
612
613 use super::{
614 PriceInfo,
615 PriceStatus,
616 Rational,
617 SolanaPriceAccount,
618 };
619
620 #[test]
621 fn test_trading_price_to_price_feed() {
622 let price_account = SolanaPriceAccount {
623 expo: 5,
624 agg: PriceInfo {
625 price: 10,
626 conf: 20,
627 status: PriceStatus::Trading,
628 ..Default::default()
629 },
630 timestamp: 200,
631 prev_timestamp: 100,
632 ema_price: Rational {
633 val: 40,
634 ..Default::default()
635 },
636 ema_conf: Rational {
637 val: 50,
638 ..Default::default()
639 },
640 prev_price: 60,
641 prev_conf: 70,
642 ..Default::default()
643 };
644
645 let pubkey = Pubkey::new_from_array([3; 32]);
646 let price_feed = price_account.to_price_feed(&pubkey);
647
648 assert_eq!(
649 price_feed,
650 PriceFeed::new(
651 Identifier::new(pubkey.to_bytes()),
652 Price {
653 conf: 20,
654 price: 10,
655 expo: 5,
656 publish_time: 200,
657 },
658 Price {
659 conf: 50,
660 price: 40,
661 expo: 5,
662 publish_time: 200,
663 }
664 )
665 );
666 }
667
668 #[test]
669 fn test_non_trading_price_to_price_feed() {
670 let price_account = SolanaPriceAccount {
671 expo: 5,
672 agg: PriceInfo {
673 price: 10,
674 conf: 20,
675 status: PriceStatus::Unknown,
676 ..Default::default()
677 },
678 timestamp: 200,
679 prev_timestamp: 100,
680 ema_price: Rational {
681 val: 40,
682 ..Default::default()
683 },
684 ema_conf: Rational {
685 val: 50,
686 ..Default::default()
687 },
688 prev_price: 60,
689 prev_conf: 70,
690 ..Default::default()
691 };
692
693 let pubkey = Pubkey::new_from_array([3; 32]);
694 let price_feed = price_account.to_price_feed(&pubkey);
695
696 assert_eq!(
697 price_feed,
698 PriceFeed::new(
699 Identifier::new(pubkey.to_bytes()),
700 Price {
701 conf: 70,
702 price: 60,
703 expo: 5,
704 publish_time: 100,
705 },
706 Price {
707 conf: 50,
708 price: 40,
709 expo: 5,
710 publish_time: 100,
711 }
712 )
713 );
714 }
715
716 #[test]
717 fn test_happy_use_latest_price_in_price_no_older_than() {
718 let price_account = SolanaPriceAccount {
719 expo: 5,
720 agg: PriceInfo {
721 price: 10,
722 conf: 20,
723 status: PriceStatus::Trading,
724 pub_slot: 1,
725 ..Default::default()
726 },
727 timestamp: 200,
728 prev_timestamp: 100,
729 prev_price: 60,
730 prev_conf: 70,
731 ..Default::default()
732 };
733
734 let clock = Clock {
735 slot: 5,
736 ..Default::default()
737 };
738
739 assert_eq!(
740 price_account.get_price_no_older_than(&clock, 4),
741 Some(Price {
742 conf: 20,
743 expo: 5,
744 price: 10,
745 publish_time: 200,
746 })
747 );
748 }
749
750 #[test]
751 fn test_happy_use_prev_price_in_price_no_older_than() {
752 let price_account = SolanaPriceAccount {
753 expo: 5,
754 agg: PriceInfo {
755 price: 10,
756 conf: 20,
757 status: PriceStatus::Unknown,
758 pub_slot: 3,
759 ..Default::default()
760 },
761 timestamp: 200,
762 prev_timestamp: 100,
763 prev_price: 60,
764 prev_conf: 70,
765 prev_slot: 1,
766 ..Default::default()
767 };
768
769 let clock = Clock {
770 slot: 5,
771 ..Default::default()
772 };
773
774 assert_eq!(
775 price_account.get_price_no_older_than(&clock, 4),
776 Some(Price {
777 conf: 70,
778 expo: 5,
779 price: 60,
780 publish_time: 100,
781 })
782 );
783 }
784
785 #[test]
786 fn test_sad_cur_price_unknown_in_price_no_older_than() {
787 let price_account = SolanaPriceAccount {
788 expo: 5,
789 agg: PriceInfo {
790 price: 10,
791 conf: 20,
792 status: PriceStatus::Unknown,
793 pub_slot: 3,
794 ..Default::default()
795 },
796 timestamp: 200,
797 prev_timestamp: 100,
798 prev_price: 60,
799 prev_conf: 70,
800 prev_slot: 1,
801 ..Default::default()
802 };
803
804 let clock = Clock {
805 slot: 5,
806 ..Default::default()
807 };
808
809 assert_eq!(price_account.get_price_no_older_than(&clock, 3), None);
811 }
812
813 #[test]
814 fn test_sad_cur_price_stale_in_price_no_older_than() {
815 let price_account = SolanaPriceAccount {
816 expo: 5,
817 agg: PriceInfo {
818 price: 10,
819 conf: 20,
820 status: PriceStatus::Trading,
821 pub_slot: 3,
822 ..Default::default()
823 },
824 timestamp: 200,
825 prev_timestamp: 100,
826 prev_price: 60,
827 prev_conf: 70,
828 prev_slot: 1,
829 ..Default::default()
830 };
831
832 let clock = Clock {
833 slot: 5,
834 ..Default::default()
835 };
836
837 assert_eq!(price_account.get_price_no_older_than(&clock, 1), None);
838 }
839
840 #[test]
841 fn test_price_feed_representations_equal() {
842 #[repr(C)]
843 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
844 pub struct OldPriceAccount {
845 pub magic: u32,
846 pub ver: u32,
847 pub atype: u32,
848 pub size: u32,
849 pub ptype: crate::state::PriceType,
850 pub expo: i32,
851 pub num: u32,
852 pub num_qt: u32,
853 pub last_slot: u64,
854 pub valid_slot: u64,
855 pub ema_price: Rational,
856 pub ema_conf: Rational,
857 pub timestamp: i64,
858 pub min_pub: u8,
859 pub drv2: u8,
860 pub drv3: u16,
861 pub drv4: u32,
862 pub prod: Pubkey,
863 pub next: Pubkey,
864 pub prev_slot: u64,
865 pub prev_price: i64,
866 pub prev_conf: u64,
867 pub prev_timestamp: i64,
868 pub agg: PriceInfo,
869 pub comp: [crate::state::PriceComp; 32],
870 }
871
872 let old = OldPriceAccount {
874 magic: 1,
875 ver: 2,
876 atype: 3,
877 size: 4,
878 ptype: crate::state::PriceType::Price,
879 expo: 5,
880 num: 6,
881 num_qt: 7,
882 last_slot: 8,
883 valid_slot: 9,
884 ema_price: Rational {
885 val: 1,
886 numer: 2,
887 denom: 3,
888 },
889 ema_conf: Rational {
890 val: 1,
891 numer: 2,
892 denom: 3,
893 },
894 timestamp: 12,
895 min_pub: 13,
896 drv2: 14,
897 drv3: 15,
898 drv4: 16,
899 prod: Pubkey::new_from_array([1; 32]),
900 next: Pubkey::new_from_array([2; 32]),
901 prev_slot: 19,
902 prev_price: 20,
903 prev_conf: 21,
904 prev_timestamp: 22,
905 agg: PriceInfo {
906 price: 1,
907 conf: 2,
908 status: PriceStatus::Trading,
909 corp_act: crate::state::CorpAction::NoCorpAct,
910 pub_slot: 5,
911 },
912 comp: [Default::default(); 32],
913 };
914
915 let new = super::SolanaPriceAccount {
916 magic: 1,
917 ver: 2,
918 atype: 3,
919 size: 4,
920 ptype: crate::state::PriceType::Price,
921 expo: 5,
922 num: 6,
923 num_qt: 7,
924 last_slot: 8,
925 valid_slot: 9,
926 ema_price: Rational {
927 val: 1,
928 numer: 2,
929 denom: 3,
930 },
931 ema_conf: Rational {
932 val: 1,
933 numer: 2,
934 denom: 3,
935 },
936 timestamp: 12,
937 min_pub: 13,
938 drv2: 14,
939 drv3: 15,
940 drv4: 16,
941 prod: Pubkey::new_from_array([1; 32]),
942 next: Pubkey::new_from_array([2; 32]),
943 prev_slot: 19,
944 prev_price: 20,
945 prev_conf: 21,
946 prev_timestamp: 22,
947 agg: PriceInfo {
948 price: 1,
949 conf: 2,
950 status: PriceStatus::Trading,
951 corp_act: crate::state::CorpAction::NoCorpAct,
952 pub_slot: 5,
953 },
954 comp: [Default::default(); 32],
955 extended: (),
956 };
957
958 assert_eq!(
960 std::mem::size_of::<OldPriceAccount>(),
961 std::mem::size_of::<super::SolanaPriceAccount>(),
962 );
963
964 unsafe {
966 let old_b = std::slice::from_raw_parts(
967 &old as *const OldPriceAccount as *const u8,
968 std::mem::size_of::<OldPriceAccount>(),
969 );
970 let new_b = std::slice::from_raw_parts(
971 &new as *const super::SolanaPriceAccount as *const u8,
972 std::mem::size_of::<super::SolanaPriceAccount>(),
973 );
974 assert_eq!(old_b, new_b);
975 }
976 }
977
978 #[test]
979 fn test_product_account_iter_works() {
980 let mut product = super::ProductAccount {
981 magic: 1,
982 ver: 2,
983 atype: super::AccountType::Product as u32,
984 size: PROD_HDR_SIZE as u32 + 10,
985 px_acc: Pubkey::new_from_array([3; 32]),
986 attr: [0; super::PROD_ATTR_SIZE],
987 };
988
989 product.attr[0] = 3; product.attr[1..4].copy_from_slice(b"key");
992 product.attr[4] = 5; product.attr[5..10].copy_from_slice(b"value");
994
995 let mut iter = product.iter();
996 assert_eq!(iter.next(), Some(("key", "value")));
997 assert_eq!(iter.next(), None);
998
999 product.size = PROD_HDR_SIZE as u32 - 10; let mut iter = product.iter();
1002 assert_eq!(iter.next(), None); product.size = PROD_ACCT_SIZE as u32 + 10; let mut iter = product.iter();
1006 assert_eq!(iter.next(), Some(("key", "value")));
1007 while iter.next().is_some() {} product.attr[10] = 255;
1014 for i in 11..266 {
1015 product.attr[i] = b'a';
1016 }
1017 product.attr[266] = 255;
1018 for i in 267..super::PROD_ATTR_SIZE {
1019 product.attr[i] = b'b';
1020 }
1021 let mut iter = product.iter();
1022 assert_eq!(iter.next(), Some(("key", "value")));
1023 assert_eq!(iter.next(), None); product.attr[266] = 10;
1027 let mut iter = product.iter();
1028 assert_eq!(iter.next(), Some(("key", "value")));
1029 let (key, val) = iter.next().unwrap();
1030 assert_eq!(key.len(), 255);
1031 for byte in key.as_bytes() {
1032 assert_eq!(byte, &b'a');
1033 }
1034 assert_eq!(val, "bbbbbbbbbb"); product.attr[1..4].copy_from_slice(b"\xff\xfe\xfa");
1040 let mut iter = product.iter();
1041 assert_eq!(iter.next(), None); }
1043}