@@ -8,7 +8,8 @@ use crate::{
8
8
use byteorder:: ByteOrder ;
9
9
use byteorder:: { LittleEndian , ReadBytesExt } ;
10
10
use itertools:: Itertools ;
11
- use pyth_oracle:: solana_program:: account_info:: AccountInfo ;
11
+ use pyth_oracle:: PythOracleSerialize ;
12
+ use pyth_oracle:: { solana_program:: account_info:: AccountInfo , PriceAccountFlags } ;
12
13
use pyth_oracle:: { PriceAccount , PythAccount } ;
13
14
use pythnet_sdk:: {
14
15
accumulators:: { merkle:: MerkleAccumulator , Accumulator } ,
@@ -131,7 +132,7 @@ fn test_update_accumulator_sysvar() {
131
132
let ( price_feed_key, _bump) = Pubkey :: find_program_address ( & [ b"123" ] , & ORACLE_PUBKEY ) ;
132
133
let mut price_feed_account =
133
134
AccountSharedData :: new ( 42 , size_of :: < PriceAccount > ( ) , & ORACLE_PUBKEY ) ;
134
- PriceAccount :: initialize (
135
+ let _ = PriceAccount :: initialize (
135
136
& AccountInfo :: new (
136
137
& price_feed_key. to_bytes ( ) . into ( ) ,
137
138
false ,
@@ -669,6 +670,202 @@ fn test_update_accumulator_end_of_block() {
669
670
) ;
670
671
}
671
672
673
+ // This test will
674
+ #[ test]
675
+ fn test_accumulator_v2 ( ) {
676
+ let leader_pubkey = solana_sdk:: pubkey:: new_rand ( ) ;
677
+ let GenesisConfigInfo {
678
+ mut genesis_config, ..
679
+ } = create_genesis_config_with_leader ( 5 , & leader_pubkey, 3 ) ;
680
+
681
+ // Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here
682
+ // due to slot 0 having special handling.
683
+ let slots_in_epoch = 32 ;
684
+ genesis_config. epoch_schedule = EpochSchedule :: new ( slots_in_epoch) ;
685
+ let mut bank = Bank :: new_for_tests ( & genesis_config) ;
686
+
687
+ bank = new_from_parent ( & Arc :: new ( bank) ) ; // Advance slot 1.
688
+ bank = new_from_parent ( & Arc :: new ( bank) ) ; // Advance slot 2.
689
+
690
+ let generate_price = |seeds, generate_buffers : bool | {
691
+ let ( price_feed_key, _bump) = Pubkey :: find_program_address ( & [ seeds] , & ORACLE_PUBKEY ) ;
692
+ let mut price_feed_account =
693
+ AccountSharedData :: new ( 42 , size_of :: < PriceAccount > ( ) , & ORACLE_PUBKEY ) ;
694
+
695
+ let messages = {
696
+ let price_feed_info_key = & price_feed_key. to_bytes ( ) . into ( ) ;
697
+ let price_feed_info_lamports = & mut 0 ;
698
+ let price_feed_info_owner = & ORACLE_PUBKEY . to_bytes ( ) . into ( ) ;
699
+ let price_feed_info_data = price_feed_account. data_mut ( ) ;
700
+ let price_feed_info = AccountInfo :: new (
701
+ price_feed_info_key,
702
+ false ,
703
+ true ,
704
+ price_feed_info_lamports,
705
+ price_feed_info_data,
706
+ price_feed_info_owner,
707
+ false ,
708
+ Epoch :: default ( ) ,
709
+ ) ;
710
+
711
+ let mut price_account = PriceAccount :: initialize ( & price_feed_info, 0 ) . unwrap ( ) ;
712
+ if !generate_buffers {
713
+ price_account. flags . insert (
714
+ PriceAccountFlags :: ACCUMULATOR_V2 | PriceAccountFlags :: MESSAGE_BUFFER_CLEARED ,
715
+ ) ;
716
+ }
717
+
718
+ vec ! [
719
+ price_account
720
+ . as_price_feed_message( & price_feed_key. to_bytes( ) . into( ) )
721
+ . to_bytes( ) ,
722
+ price_account
723
+ . as_twap_message( & price_feed_key. to_bytes( ) . into( ) )
724
+ . to_bytes( ) ,
725
+ ]
726
+ } ;
727
+
728
+ bank. store_account ( & price_feed_key, & price_feed_account) ;
729
+
730
+ if generate_buffers {
731
+ // Insert into message buffer in reverse order to test that accumulator
732
+ // sorts first.
733
+ let message_buffer_bytes = create_message_buffer_bytes ( messages. clone ( ) ) ;
734
+
735
+ // Create a Message account.
736
+ let price_message_key = keypair_from_seed ( & [ 1u8 ; 32 ] ) . unwrap ( ) ;
737
+ let mut price_message_account = bank
738
+ . get_account ( & price_message_key. pubkey ( ) )
739
+ . unwrap_or_default ( ) ;
740
+
741
+ price_message_account. set_lamports ( 1_000_000_000 ) ;
742
+ price_message_account
743
+ . set_owner ( Pubkey :: new_from_array ( pythnet_sdk:: MESSAGE_BUFFER_PID ) ) ;
744
+ price_message_account. set_data ( message_buffer_bytes) ;
745
+
746
+ // Store Message account so the accumulator sysvar updater can find it.
747
+ bank. store_account ( & price_message_key. pubkey ( ) , & price_message_account) ;
748
+ }
749
+
750
+ ( price_feed_key, messages)
751
+ } ;
752
+
753
+ // TODO: New test functionality here.
754
+ // 1. Create Price Feed Accounts owned by ORACLE_PUBKEY
755
+ // 2. Populate Price Feed Accounts
756
+ // 3. Call update_v2()
757
+ // - Cases:
758
+ // - No V1 Messages, Only Price Accounts with no V2
759
+ // - No V1 Messages, Some Price Accounts with no V2
760
+ // - Some V1 Messages, No Price Accounts with no V2
761
+ // - Some V1 Messages, Some Price Accounts with no V2
762
+ // - Simulate PriceUpdate that WOULD trigger a real V1 aggregate before End of Slot
763
+ // - Simulate PriceUpdate that doesn't trigger a real V1 aggregate, only V2.
764
+
765
+ assert ! ( bank
766
+ . feature_set
767
+ . is_active( & feature_set:: enable_accumulator_sysvar:: id( ) ) ) ;
768
+ assert ! ( bank
769
+ . feature_set
770
+ . is_active( & feature_set:: move_accumulator_to_end_of_block:: id( ) ) ) ;
771
+ assert ! ( bank
772
+ . feature_set
773
+ . is_active( & feature_set:: undo_move_accumulator_to_end_of_block:: id( ) ) ) ;
774
+ assert ! ( bank
775
+ . feature_set
776
+ . is_active( & feature_set:: redo_move_accumulator_to_end_of_block:: id( ) ) ) ;
777
+
778
+ let prices_with_messages = [
779
+ generate_price ( b"seeds_1" , false ) ,
780
+ generate_price ( b"seeds_2" , false ) ,
781
+ generate_price ( b"seeds_3" , false ) ,
782
+ generate_price ( b"seeds_4" , false ) ,
783
+ ] ;
784
+
785
+ let messages = prices_with_messages
786
+ . iter ( )
787
+ . map ( |( _, messages) | messages)
788
+ . flatten ( )
789
+ . map ( |message| & message[ ..] ) ;
790
+
791
+ // Trigger Aggregation. We freeze instead of new_from_parent so
792
+ // we can keep access to the bank.
793
+ let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker ( & bank) ;
794
+ bank. freeze ( ) ;
795
+
796
+ // Get the wormhole message generated by freezed. We don't need
797
+ // to offset the ring index as our test is always below 10K slots.
798
+ let wormhole_message_account = get_wormhole_message_account ( & bank, bank. slot ( ) as u32 ) ;
799
+ assert_ne ! ( wormhole_message_account. data( ) . len( ) , 0 ) ;
800
+ PostedMessageUnreliableData :: deserialize ( & mut wormhole_message_account. data ( ) ) . unwrap ( ) ;
801
+
802
+ // Create MerkleAccumulator by hand to verify that the Wormhole message
803
+ // contents are correctg.
804
+ let expected_accumulator =
805
+ MerkleAccumulator :: < Keccak160 > :: from_set ( messages. clone ( ) . sorted_unstable ( ) . dedup ( ) )
806
+ . unwrap ( ) ;
807
+
808
+ let expected_wormhole_message_payload =
809
+ expected_accumulator. serialize ( bank. slot ( ) , ACCUMULATOR_RING_SIZE ) ;
810
+
811
+ let expected_wormhole_message = PostedMessageUnreliableData {
812
+ message : MessageData {
813
+ vaa_version : 1 ,
814
+ consistency_level : 1 ,
815
+ submission_time : bank. clock ( ) . unix_timestamp as u32 ,
816
+ sequence : sequence_tracker_before_bank_freeze. sequence , // sequence is incremented after the message is processed
817
+ emitter_chain : 26 ,
818
+ emitter_address : ACCUMULATOR_EMITTER_ADDRESS ,
819
+ payload : expected_wormhole_message_payload,
820
+ ..Default :: default ( )
821
+ } ,
822
+ } ;
823
+
824
+ assert_eq ! (
825
+ wormhole_message_account. data( ) . to_vec( ) ,
826
+ expected_wormhole_message. try_to_vec( ) . unwrap( )
827
+ ) ;
828
+
829
+ // Verify hashes in accumulator.
830
+ for msg in messages {
831
+ let msg_hash = Keccak160 :: hashv ( & [ [ 0u8 ] . as_ref ( ) , msg] ) ;
832
+ let msg_proof = expected_accumulator. prove ( msg) . unwrap ( ) ;
833
+ assert ! ( expected_accumulator. nodes. contains( & msg_hash) ) ;
834
+ assert ! ( expected_accumulator. check( msg_proof, msg) ) ;
835
+ }
836
+
837
+ // Verify accumulator state account.
838
+ let accumulator_state = get_accumulator_state ( & bank, bank. slot ( ) as u32 ) ;
839
+ let acc_state_magic = & accumulator_state[ ..4 ] ;
840
+ let acc_state_slot = LittleEndian :: read_u64 ( & accumulator_state[ 4 ..12 ] ) ;
841
+ let acc_state_ring_size = LittleEndian :: read_u32 ( & accumulator_state[ 12 ..16 ] ) ;
842
+
843
+ assert_eq ! ( acc_state_magic, b"PAS1" ) ;
844
+ assert_eq ! ( acc_state_slot, bank. slot( ) ) ;
845
+ assert_eq ! ( acc_state_ring_size, ACCUMULATOR_RING_SIZE ) ;
846
+
847
+ // Verify the messages within the accumulator state account
848
+ // were in the accumulator as well.
849
+ let mut cursor = std:: io:: Cursor :: new ( & accumulator_state[ 16 ..] ) ;
850
+ let num_elems = cursor. read_u32 :: < LittleEndian > ( ) . unwrap ( ) ;
851
+ for _ in 0 ..( num_elems as usize ) {
852
+ let element_len = cursor. read_u32 :: < LittleEndian > ( ) . unwrap ( ) ;
853
+ let mut element_data = vec ! [ 0u8 ; element_len as usize ] ;
854
+ cursor. read_exact ( & mut element_data) . unwrap ( ) ;
855
+
856
+ let elem_hash = Keccak160 :: hashv ( & [ [ 0u8 ] . as_ref ( ) , element_data. as_slice ( ) ] ) ;
857
+ let elem_proof = expected_accumulator. prove ( element_data. as_slice ( ) ) . unwrap ( ) ;
858
+
859
+ assert ! ( expected_accumulator. nodes. contains( & elem_hash) ) ;
860
+ assert ! ( expected_accumulator. check( elem_proof, element_data. as_slice( ) ) ) ;
861
+ }
862
+
863
+ // Verify sequence_tracker increments for wormhole to accept it.
864
+ assert_eq ! (
865
+ get_acc_sequence_tracker( & bank) . sequence,
866
+ sequence_tracker_before_bank_freeze. sequence + 1
867
+ ) ;
868
+ }
672
869
#[ test]
673
870
fn test_get_accumulator_keys ( ) {
674
871
use pythnet_sdk:: { pythnet, ACCUMULATOR_EMITTER_ADDRESS , MESSAGE_BUFFER_PID } ;
0 commit comments