@@ -15,20 +15,20 @@ use crate::error::ExecutorError;
1515
1616pub const MAGIC_NUMBER : u32 = 0x4d475450 ; // Reverse order of the solidity contract because borsh uses little endian numbers (the solidity contract uses 0x5054474d)
1717
18- #[ derive( AnchorDeserialize , AnchorSerialize ) ]
18+ #[ derive( AnchorDeserialize , AnchorSerialize , Debug , PartialEq , Eq ) ]
1919pub struct ExecutorPayload {
2020 pub header : GovernanceHeader ,
2121
2222 pub instructions : Vec < InstructionData > ,
2323}
2424
25- #[ derive( AnchorDeserialize , AnchorSerialize , PartialEq , Eq ) ]
25+ #[ derive( AnchorDeserialize , AnchorSerialize , PartialEq , Eq , Debug ) ]
2626pub enum Module {
2727 Executor = 0 ,
2828 Target ,
2929}
3030
31- #[ derive( AnchorDeserialize , AnchorSerialize , PartialEq , Eq ) ]
31+ #[ derive( AnchorDeserialize , AnchorSerialize , PartialEq , Eq , Debug ) ]
3232pub enum Action {
3333 ExecutePostedVaa = 0 ,
3434}
@@ -38,7 +38,7 @@ pub enum Action {
3838/// - A one byte module variant (0 for Executor and 1 for Target contracts)
3939/// - A one byte action variant (for Executor only 0 is currently valid)
4040/// - A bigendian 2 bytes u16 chain id
41- #[ derive( AnchorDeserialize , AnchorSerialize ) ]
41+ #[ derive( AnchorDeserialize , AnchorSerialize , Eq , PartialEq , Debug ) ]
4242pub struct GovernanceHeader {
4343 pub magic_number : u32 ,
4444 pub module : Module ,
@@ -47,6 +47,7 @@ pub struct GovernanceHeader {
4747}
4848
4949/// Hack to get Borsh to deserialize, serialize this number with big endian order
50+ #[ derive( Eq , PartialEq , Debug ) ]
5051pub struct BigEndianU16 {
5152 pub value : u16 ,
5253}
@@ -119,6 +120,24 @@ impl From<&InstructionData> for Instruction {
119120 }
120121}
121122
123+ impl From < & Instruction > for InstructionData {
124+ fn from ( instruction : & Instruction ) -> Self {
125+ InstructionData {
126+ program_id : instruction. program_id ,
127+ accounts : instruction
128+ . accounts
129+ . iter ( )
130+ . map ( |a| AccountMetaData {
131+ pubkey : a. pubkey ,
132+ is_signer : a. is_signer ,
133+ is_writable : a. is_writable ,
134+ } )
135+ . collect ( ) ,
136+ data : instruction. data . clone ( ) ,
137+ }
138+ }
139+ }
140+
122141impl ExecutorPayload {
123142 const MODULE : Module = Module :: Executor ;
124143 const ACTION : Action = Action :: ExecutePostedVaa ;
@@ -127,10 +146,130 @@ impl ExecutorPayload {
127146 ( self . header . magic_number == MAGIC_NUMBER )
128147 . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidMagicNumber ) ) ?;
129148 ( self . header . module == ExecutorPayload :: MODULE )
130- . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidMagicNumber ) ) ?;
149+ . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidModule ) ) ?;
131150 ( self . header . action == ExecutorPayload :: ACTION )
132- . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidMagicNumber ) ) ?;
151+ . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidAction ) ) ?;
133152 ( Chain :: from ( self . header . chain . value ) == Chain :: Pythnet )
134- . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidMagicNumber ) )
153+ . ok_or ( error ! ( ExecutorError :: GovernanceHeaderInvalidReceiverChain ) )
154+ }
155+ }
156+
157+ #[ cfg( test) ]
158+ pub mod tests {
159+ use crate :: {
160+ error,
161+ error:: ExecutorError ,
162+ state:: governance_payload:: InstructionData ,
163+ } ;
164+
165+ use super :: {
166+ Action ,
167+ BigEndianU16 ,
168+ ExecutorPayload ,
169+ Module ,
170+ MAGIC_NUMBER ,
171+ } ;
172+ use anchor_lang:: {
173+ prelude:: Pubkey ,
174+ AnchorDeserialize ,
175+ AnchorSerialize ,
176+ } ;
177+ use wormhole:: Chain ;
178+
179+ #[ test]
180+ fn test_check_deserialization_serialization ( ) {
181+ // No instructions
182+ let payload = ExecutorPayload {
183+ header : super :: GovernanceHeader {
184+ magic_number : MAGIC_NUMBER ,
185+ module : Module :: Executor ,
186+ action : Action :: ExecutePostedVaa ,
187+ chain : BigEndianU16 {
188+ value : Chain :: Pythnet . try_into ( ) . unwrap ( ) ,
189+ } ,
190+ } ,
191+ instructions : vec ! [ ] ,
192+ } ;
193+
194+ assert ! ( payload. check_header( ) . is_ok( ) ) ;
195+
196+ let payload_bytes = payload. try_to_vec ( ) . unwrap ( ) ;
197+ assert_eq ! ( payload_bytes, vec![ 80 , 84 , 71 , 77 , 0 , 0 , 0 , 26 , 0 , 0 , 0 , 0 ] ) ;
198+
199+ let deserialized_payload =
200+ ExecutorPayload :: try_from_slice ( payload_bytes. as_slice ( ) ) . unwrap ( ) ;
201+ assert_eq ! ( payload, deserialized_payload) ;
202+
203+ // One instruction
204+ let payload = ExecutorPayload {
205+ header : super :: GovernanceHeader {
206+ magic_number : MAGIC_NUMBER ,
207+ module : Module :: Executor ,
208+ action : Action :: ExecutePostedVaa ,
209+ chain : BigEndianU16 {
210+ value : Chain :: Pythnet . try_into ( ) . unwrap ( ) ,
211+ } ,
212+ } ,
213+ instructions : vec ! [ InstructionData :: from(
214+ & anchor_lang:: solana_program:: system_instruction:: create_account(
215+ & Pubkey :: new_unique( ) ,
216+ & Pubkey :: new_unique( ) ,
217+ 1 ,
218+ 1 ,
219+ & Pubkey :: new_unique( ) ,
220+ ) ,
221+ ) ] ,
222+ } ;
223+
224+ assert ! ( payload. check_header( ) . is_ok( ) ) ;
225+
226+ let payload_bytes = payload. try_to_vec ( ) . unwrap ( ) ;
227+ assert_eq ! (
228+ payload_bytes[ ..12 ] ,
229+ vec![ 80 , 84 , 71 , 77 , 0 , 0 , 0 , 26 , 1 , 0 , 0 , 0 ]
230+ ) ;
231+
232+ let deserialized_payload =
233+ ExecutorPayload :: try_from_slice ( payload_bytes. as_slice ( ) ) . unwrap ( ) ;
234+ assert_eq ! ( payload, deserialized_payload) ;
235+
236+ // Module outside of range
237+ let payload_bytes = vec ! [ 80 , 84 , 71 , 77 , 3 , 0 , 0 , 26 , 0 , 0 , 0 , 0 , 0 ] ;
238+ assert ! ( ExecutorPayload :: try_from_slice( payload_bytes. as_slice( ) ) . is_err( ) ) ;
239+
240+ // Wrong module
241+ let payload_bytes = vec ! [ 80 , 84 , 71 , 77 , 1 , 0 , 0 , 26 , 0 , 0 , 0 , 0 ] ;
242+ let deserialized_payload =
243+ ExecutorPayload :: try_from_slice ( payload_bytes. as_slice ( ) ) . unwrap ( ) ;
244+ assert_eq ! (
245+ deserialized_payload. check_header( ) ,
246+ Err ( error!( ExecutorError :: GovernanceHeaderInvalidModule ) )
247+ ) ;
248+
249+ // Wrong magic
250+ let payload_bytes = vec ! [ 81 , 84 , 71 , 77 , 1 , 0 , 0 , 26 , 0 , 0 , 0 , 0 ] ;
251+ let deserialized_payload =
252+ ExecutorPayload :: try_from_slice ( payload_bytes. as_slice ( ) ) . unwrap ( ) ;
253+ assert_eq ! (
254+ deserialized_payload. check_header( ) ,
255+ Err ( error!( ExecutorError :: GovernanceHeaderInvalidMagicNumber ) )
256+ ) ;
257+
258+ // Action outside of range
259+ let payload_bytes = vec ! [ 80 , 84 , 71 , 77 , 0 , 1 , 0 , 26 , 0 , 0 , 0 , 0 ] ;
260+ assert ! ( ExecutorPayload :: try_from_slice( payload_bytes. as_slice( ) ) . is_err( ) ) ;
261+
262+ // Wrong receiver chain endianess
263+ let payload_bytes = vec ! [ 80 , 84 , 71 , 77 , 0 , 0 , 26 , 0 , 0 , 0 , 0 , 0 ] ;
264+ let deserialized_payload =
265+ ExecutorPayload :: try_from_slice ( payload_bytes. as_slice ( ) ) . unwrap ( ) ;
266+ assert_eq ! (
267+ deserialized_payload. check_header( ) ,
268+ Err ( error!( ExecutorError :: GovernanceHeaderInvalidReceiverChain ) )
269+ ) ;
270+
271+ // Wrong vector format
272+ let payload_bytes = vec ! [ 80 , 84 , 71 , 77 , 0 , 0 , 0 , 26 , 1 , 0 , 0 , 0 ] ;
273+ assert ! ( ExecutorPayload :: try_from_slice( payload_bytes. as_slice( ) ) . is_err( ) ) ;
135274 }
136275}
0 commit comments