11use cosmwasm_std:: testing:: { message_info, mock_dependencies, mock_env} ;
22use cosmwasm_std:: { from_json, Binary , Coin , Uint256 } ;
33
4- use crate :: contract:: { instantiate, query} ;
4+ use crate :: contract:: { instantiate, execute , query} ;
55use crate :: msg:: {
6- GetAddressHexResponse , GetStateResponse , GuardianSetInfoResponse , InstantiateMsg , QueryMsg ,
6+ ExecuteMsg , GetAddressHexResponse , GetStateResponse , GuardianSetInfoResponse , InstantiateMsg ,
7+ QueryMsg ,
78} ;
8- use crate :: state:: { GuardianAddress , GuardianSetInfo } ;
9+ use crate :: state:: { GovernancePacket , GuardianAddress , GuardianSetInfo , ParsedVAA } ;
910
1011const GOV_ADDRESS : & str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE" ;
1112const GUARDIAN_ADDR : & str = "54dbb737eac5007103e729e9ab7ce64a6850a310" ;
@@ -179,12 +180,12 @@ fn test_quorum_calculation() {
179180 } ;
180181 assert_eq ! ( three. quorum( ) , 3 ) ;
181182
182- // Empty set: quorum = 0
183+ // Empty set: quorum = 1 (never allow zero quorum)
183184 let empty = GuardianSetInfo {
184185 addresses : vec ! [ ] ,
185186 expiration_time : 0 ,
186187 } ;
187- assert_eq ! ( empty. quorum( ) , 0 ) ;
188+ assert_eq ! ( empty. quorum( ) , 1 ) ;
188189
189190 // 19 guardians (mainnet-like): quorum = 13
190191 let nineteen = GuardianSetInfo {
@@ -195,3 +196,189 @@ fn test_quorum_calculation() {
195196 } ;
196197 assert_eq ! ( nineteen. quorum( ) , 13 ) ;
197198}
199+
200+ // ---------------------------------------------------------------------------
201+ // H-1: ParsedVAA::deserialize rejects short input instead of panicking
202+ // ---------------------------------------------------------------------------
203+
204+ #[ test]
205+ fn test_deserialize_vaa_empty_input ( ) {
206+ let result = ParsedVAA :: deserialize ( & [ ] ) ;
207+ assert ! ( result. is_err( ) ) ;
208+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
209+ }
210+
211+ #[ test]
212+ fn test_deserialize_vaa_short_input ( ) {
213+ // 3 bytes — shorter than HEADER_LEN (6)
214+ let result = ParsedVAA :: deserialize ( & [ 0x01 , 0x02 , 0x03 ] ) ;
215+ assert ! ( result. is_err( ) ) ;
216+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
217+ }
218+
219+ #[ test]
220+ fn test_deserialize_vaa_exactly_header_len ( ) {
221+ // 6 bytes = HEADER_LEN, but body_offset will exceed data.len() with 0 signers
222+ // version=1, guardian_set_index=0, len_signers=0 → body_offset=6, data.len()=6
223+ // body_offset >= data.len() → InvalidVAA (existing check)
224+ let data = [ 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ] ;
225+ let result = ParsedVAA :: deserialize ( & data) ;
226+ assert ! ( result. is_err( ) ) ;
227+ }
228+
229+ #[ test]
230+ fn test_short_vaa_via_execute ( ) {
231+ let mut deps = mock_dependencies ( ) ;
232+ let env = mock_env ( ) ;
233+ let info = message_info ( & deps. api . addr_make ( "creator" ) , & [ ] ) ;
234+ let msg = mock_instantiate_msg ( 0 ) ;
235+ instantiate ( deps. as_mut ( ) , env. clone ( ) , info. clone ( ) , msg) . unwrap ( ) ;
236+
237+ let short_vaa = Binary :: from ( vec ! [ 0x01 , 0x02 , 0x03 ] ) ;
238+ let res = execute (
239+ deps. as_mut ( ) ,
240+ env,
241+ info,
242+ ExecuteMsg :: SubmitVAA { vaa : short_vaa } ,
243+ ) ;
244+ assert ! ( res. is_err( ) ) ;
245+ assert ! ( res. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
246+ }
247+
248+ #[ test]
249+ fn test_short_vaa_via_query ( ) {
250+ let mut deps = mock_dependencies ( ) ;
251+ let env = mock_env ( ) ;
252+ let info = message_info ( & deps. api . addr_make ( "creator" ) , & [ ] ) ;
253+ let msg = mock_instantiate_msg ( 0 ) ;
254+ instantiate ( deps. as_mut ( ) , env. clone ( ) , info, msg) . unwrap ( ) ;
255+
256+ let short_vaa = Binary :: from ( vec ! [ 0x01 , 0x02 , 0x03 ] ) ;
257+ let res = query (
258+ deps. as_ref ( ) ,
259+ env,
260+ QueryMsg :: VerifyVAA {
261+ vaa : short_vaa,
262+ block_time : 0 ,
263+ } ,
264+ ) ;
265+ assert ! ( res. is_err( ) ) ;
266+ assert ! ( res. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
267+ }
268+
269+ // ---------------------------------------------------------------------------
270+ // M-3: Empty guardian set now requires quorum of 1 (blocks unsigned VAAs)
271+ // ---------------------------------------------------------------------------
272+
273+ #[ test]
274+ fn test_empty_guardian_set_rejects_unsigned_vaa ( ) {
275+ let mut deps = mock_dependencies ( ) ;
276+ let env = mock_env ( ) ;
277+ let info = message_info ( & deps. api . addr_make ( "creator" ) , & [ ] ) ;
278+
279+ // Instantiate with an empty guardian set
280+ let msg = InstantiateMsg {
281+ gov_chain : 1 ,
282+ gov_address : Binary :: from ( hex:: decode ( GOV_ADDRESS ) . unwrap ( ) ) ,
283+ initial_guardian_set : GuardianSetInfo {
284+ addresses : vec ! [ ] ,
285+ expiration_time : 0 ,
286+ } ,
287+ guardian_set_index : 0 ,
288+ guardian_set_expirity : 86400 ,
289+ chain_id : 32 ,
290+ fee_denom : "uakt" . to_string ( ) ,
291+ } ;
292+ instantiate ( deps. as_mut ( ) , env. clone ( ) , info. clone ( ) , msg) . unwrap ( ) ;
293+
294+ // Build a minimal valid-structure VAA with 0 signatures:
295+ // version(1) + guardian_set_index(4) + len_signers(1) = 6 bytes header
296+ // body needs at least VAA_PAYLOAD_POS(51) bytes
297+ let mut vaa = Vec :: new ( ) ;
298+ vaa. push ( 0x01 ) ; // version
299+ vaa. extend_from_slice ( & 0u32 . to_be_bytes ( ) ) ; // guardian_set_index = 0
300+ vaa. push ( 0x00 ) ; // len_signers = 0
301+ // body: 51 bytes minimum (timestamp + nonce + emitter_chain + emitter_address + sequence + consistency_level)
302+ vaa. extend_from_slice ( & [ 0u8 ; 51 ] ) ;
303+
304+ let res = execute (
305+ deps. as_mut ( ) ,
306+ env,
307+ info,
308+ ExecuteMsg :: SubmitVAA { vaa : Binary :: from ( vaa) } ,
309+ ) ;
310+ assert ! ( res. is_err( ) ) ;
311+ assert ! ( res. unwrap_err( ) . to_string( ) . contains( "NoQuorum" ) ) ;
312+ }
313+
314+ // ---------------------------------------------------------------------------
315+ // M-4: GovernancePacket returns error on invalid UTF-8 instead of panicking
316+ // ---------------------------------------------------------------------------
317+
318+ #[ test]
319+ fn test_governance_packet_invalid_utf8_module ( ) {
320+ // 32-byte module with invalid UTF-8, followed by action + chain
321+ let mut data = vec ! [ 0xFF ; 32 ] ; // invalid UTF-8 bytes
322+ data. push ( 0x01 ) ; // action
323+ data. extend_from_slice ( & 0u16 . to_be_bytes ( ) ) ; // chain
324+
325+ let packet = GovernancePacket :: deserialize ( & data) ;
326+ // Deserialization succeeds — it just stores raw bytes
327+ assert ! ( packet. is_ok( ) ) ;
328+
329+ // The panic was in contract.rs where String::from_utf8().unwrap() was called.
330+ // Verify that from_utf8 on these bytes would fail:
331+ let packet = packet. unwrap ( ) ;
332+ assert ! ( String :: from_utf8( packet. module) . is_err( ) ) ;
333+ }
334+
335+ // ---------------------------------------------------------------------------
336+ // M-6: GovernancePacket::deserialize rejects short input instead of panicking
337+ // ---------------------------------------------------------------------------
338+
339+ #[ test]
340+ fn test_governance_packet_empty_input ( ) {
341+ let result = GovernancePacket :: deserialize ( & [ ] ) ;
342+ assert ! ( result. is_err( ) ) ;
343+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
344+ }
345+
346+ #[ test]
347+ fn test_governance_packet_short_input ( ) {
348+ // 10 bytes — shorter than MIN_LEN (35)
349+ let result = GovernancePacket :: deserialize ( & [ 0u8 ; 10 ] ) ;
350+ assert ! ( result. is_err( ) ) ;
351+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "InvalidVAA" ) ) ;
352+ }
353+
354+ #[ test]
355+ fn test_governance_packet_exactly_min_len ( ) {
356+ // Exactly 35 bytes — should succeed with an empty payload
357+ let mut data = vec ! [ 0u8 ; 32 ] ; // module (all zeros = null-padded)
358+ data. push ( 0x02 ) ; // action
359+ data. extend_from_slice ( & 0u16 . to_be_bytes ( ) ) ; // chain = 0
360+
361+ assert_eq ! ( data. len( ) , 35 ) ;
362+ let result = GovernancePacket :: deserialize ( & data) ;
363+ assert ! ( result. is_ok( ) ) ;
364+ let packet = result. unwrap ( ) ;
365+ assert_eq ! ( packet. action, 2 ) ;
366+ assert_eq ! ( packet. chain, 0 ) ;
367+ assert ! ( packet. payload. is_empty( ) ) ;
368+ }
369+
370+ #[ test]
371+ fn test_governance_packet_with_payload ( ) {
372+ // 35 + 4 bytes of payload
373+ let mut data = vec ! [ 0u8 ; 32 ] ; // module
374+ data. push ( 0x01 ) ; // action
375+ data. extend_from_slice ( & 32u16 . to_be_bytes ( ) ) ; // chain = 32
376+ data. extend_from_slice ( & [ 0xDE , 0xAD , 0xBE , 0xEF ] ) ; // payload
377+
378+ let result = GovernancePacket :: deserialize ( & data) ;
379+ assert ! ( result. is_ok( ) ) ;
380+ let packet = result. unwrap ( ) ;
381+ assert_eq ! ( packet. action, 1 ) ;
382+ assert_eq ! ( packet. chain, 32 ) ;
383+ assert_eq ! ( packet. payload, vec![ 0xDE , 0xAD , 0xBE , 0xEF ] ) ;
384+ }
0 commit comments