2424 } ,
2525 cosmwasm_std:: {
2626 entry_point,
27+ has_coins,
2728 to_binary,
2829 Binary ,
30+ Coin ,
2931 Deps ,
3032 DepsMut ,
3133 Env ,
@@ -120,13 +122,17 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
120122fn update_price_feeds (
121123 mut deps : DepsMut ,
122124 env : Env ,
123- _info : MessageInfo ,
125+ info : MessageInfo ,
124126 data : & Binary ,
125127) -> StdResult < Response > {
126128 let state = config_read ( deps. storage ) . load ( ) ?;
127129
128- let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
130+ let fee = Coin :: new ( state. fee . u128 ( ) , state. fee_denom . clone ( ) ) ;
131+ if fee. amount . u128 ( ) > 0 && !has_coins ( info. funds . as_ref ( ) , & fee) {
132+ return Err ( PythContractError :: InsufficientFee . into ( ) ) ;
133+ }
129134
135+ let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
130136 verify_vaa_from_data_source ( & state, & vaa) ?;
131137
132138 let data = & vaa. payload ;
@@ -139,26 +145,15 @@ fn update_price_feeds(
139145fn execute_governance_instruction (
140146 mut deps : DepsMut ,
141147 env : Env ,
142- info : MessageInfo ,
148+ _info : MessageInfo ,
143149 data : & Binary ,
144150) -> StdResult < Response > {
145151 let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
146-
147- execute_governance_instruction_from_vaa ( deps, env, info, & vaa)
148- }
149-
150- /// Helper function to improve testability of governance instructions (so we can unit test without wormhole).
151- fn execute_governance_instruction_from_vaa (
152- deps : DepsMut ,
153- _env : Env ,
154- _info : MessageInfo ,
155- vaa : & ParsedVAA ,
156- ) -> StdResult < Response > {
157152 let state = config_read ( deps. storage ) . load ( ) ?;
158153
159154 // store updates to the config as a result of this action in here.
160155 let mut updated_config: ConfigInfo = state. clone ( ) ;
161- verify_vaa_from_governance_source ( & state, vaa) ?;
156+ verify_vaa_from_governance_source ( & state, & vaa) ?;
162157
163158 if vaa. sequence <= state. governance_sequence_number {
164159 return Err ( PythContractError :: OldGovernanceMessage ) ?;
@@ -417,6 +412,8 @@ mod test {
417412 Target ,
418413 } ,
419414 cosmwasm_std:: {
415+ coins,
416+ from_binary,
420417 testing:: {
421418 mock_dependencies,
422419 mock_env,
@@ -426,16 +423,36 @@ mod test {
426423 MockStorage ,
427424 } ,
428425 Addr ,
426+ ContractResult ,
429427 OwnedDeps ,
428+ QuerierResult ,
429+ SystemError ,
430+ SystemResult ,
430431 } ,
431432 std:: time:: Duration ,
432433 } ;
433434
434435 /// Default valid time period for testing purposes.
435436 const VALID_TIME_PERIOD : Duration = Duration :: from_secs ( 3 * 60 ) ;
437+ const WORMHOLE_ADDR : & str = "Wormhole" ;
438+ const EMITTER_CHAIN : u16 = 3 ;
439+
440+ fn default_emitter_addr ( ) -> Vec < u8 > {
441+ vec ! [ 0 , 1 , 80 ]
442+ }
443+
444+ fn default_config_info ( ) -> ConfigInfo {
445+ ConfigInfo {
446+ wormhole_contract : Addr :: unchecked ( WORMHOLE_ADDR ) ,
447+ data_sources : create_data_sources ( default_emitter_addr ( ) , EMITTER_CHAIN ) ,
448+ ..create_zero_config_info ( )
449+ }
450+ }
436451
437452 fn setup_test ( ) -> ( OwnedDeps < MockStorage , MockApi , MockQuerier > , Env ) {
438453 let mut dependencies = mock_dependencies ( ) ;
454+ dependencies. querier . update_wasm ( handle_wasm_query) ;
455+
439456 let mut config = config ( dependencies. as_mut ( ) . storage ) ;
440457 config
441458 . save ( & ConfigInfo {
@@ -446,6 +463,47 @@ mod test {
446463 ( dependencies, mock_env ( ) )
447464 }
448465
466+ /// Mock handler for wormhole queries.
467+ /// Warning: the interface for the `VerifyVAA` action is slightly different than the real wormhole contract.
468+ /// In the mock, you pass in a binary-encoded `ParsedVAA`, and that exact vaa will be returned by wormhole.
469+ /// The real contract uses a different binary VAA format (see `ParsedVAA::deserialize`) which includes
470+ /// the guardian signatures.
471+ fn handle_wasm_query ( wasm_query : & WasmQuery ) -> QuerierResult {
472+ match wasm_query {
473+ WasmQuery :: Smart { contract_addr, msg } if * contract_addr == WORMHOLE_ADDR => {
474+ let query_msg = from_binary :: < WormholeQueryMsg > ( msg) ;
475+ match query_msg {
476+ Ok ( WormholeQueryMsg :: VerifyVAA { vaa, .. } ) => {
477+ SystemResult :: Ok ( ContractResult :: Ok ( vaa) )
478+ }
479+ Err ( _e) => SystemResult :: Err ( SystemError :: InvalidRequest {
480+ error : "Invalid message" . into ( ) ,
481+ request : msg. clone ( ) ,
482+ } ) ,
483+ _ => SystemResult :: Err ( SystemError :: NoSuchContract {
484+ addr : contract_addr. clone ( ) ,
485+ } ) ,
486+ }
487+ }
488+ WasmQuery :: Smart { contract_addr, .. } => {
489+ SystemResult :: Err ( SystemError :: NoSuchContract {
490+ addr : contract_addr. clone ( ) ,
491+ } )
492+ }
493+ WasmQuery :: Raw { contract_addr, .. } => {
494+ SystemResult :: Err ( SystemError :: NoSuchContract {
495+ addr : contract_addr. clone ( ) ,
496+ } )
497+ }
498+ WasmQuery :: ContractInfo { contract_addr, .. } => {
499+ SystemResult :: Err ( SystemError :: NoSuchContract {
500+ addr : contract_addr. clone ( ) ,
501+ } )
502+ }
503+ _ => unreachable ! ( ) ,
504+ }
505+ }
506+
449507 fn create_zero_vaa ( ) -> ParsedVAA {
450508 ParsedVAA {
451509 version : 0 ,
@@ -462,6 +520,20 @@ mod test {
462520 }
463521 }
464522
523+ fn create_price_update_msg ( emitter_address : & [ u8 ] , emitter_chain : u16 ) -> Binary {
524+ let batch_attestation = BatchPriceAttestation {
525+ // TODO: pass these in
526+ price_attestations : vec ! [ ] ,
527+ } ;
528+
529+ let mut vaa = create_zero_vaa ( ) ;
530+ vaa. emitter_address = emitter_address. to_vec ( ) ;
531+ vaa. emitter_chain = emitter_chain;
532+ vaa. payload = batch_attestation. serialize ( ) . unwrap ( ) ;
533+
534+ to_binary ( & vaa) . unwrap ( )
535+ }
536+
465537 fn create_zero_config_info ( ) -> ConfigInfo {
466538 ConfigInfo {
467539 owner : Addr :: unchecked ( String :: default ( ) ) ,
@@ -512,50 +584,91 @@ mod test {
512584 . unwrap ( )
513585 }
514586
515- #[ test]
516- fn test_verify_vaa_sender_ok ( ) {
517- let config_info = ConfigInfo {
518- data_sources : create_data_sources ( vec ! [ 1u8 ] , 3 ) ,
519- ..create_zero_config_info ( )
520- } ;
587+ fn apply_price_update (
588+ config_info : & ConfigInfo ,
589+ emitter_address : & [ u8 ] ,
590+ emitter_chain : u16 ,
591+ funds : & [ Coin ] ,
592+ ) -> StdResult < Response > {
593+ let ( mut deps, env) = setup_test ( ) ;
594+ config ( & mut deps. storage ) . save ( config_info) . unwrap ( ) ;
521595
522- let mut vaa = create_zero_vaa ( ) ;
523- vaa. emitter_address = vec ! [ 1u8 ] ;
524- vaa. emitter_chain = 3 ;
596+ let info = mock_info ( "123" , funds) ;
597+ let msg = create_price_update_msg ( emitter_address, emitter_chain) ;
598+ update_price_feeds ( deps. as_mut ( ) , env, info, & msg)
599+ }
525600
526- assert_eq ! ( verify_vaa_from_data_source( & config_info, & vaa) , Ok ( ( ) ) ) ;
601+ #[ test]
602+ fn test_verify_vaa_sender_ok ( ) {
603+ let result = apply_price_update (
604+ & default_config_info ( ) ,
605+ default_emitter_addr ( ) . as_slice ( ) ,
606+ EMITTER_CHAIN ,
607+ & [ ] ,
608+ ) ;
609+ assert ! ( result. is_ok( ) ) ;
527610 }
528611
529612 #[ test]
530613 fn test_verify_vaa_sender_fail_wrong_emitter_address ( ) {
531- let config_info = ConfigInfo {
532- data_sources : create_data_sources ( vec ! [ 1u8 ] , 3 ) ,
533- ..create_zero_config_info ( )
534- } ;
535-
536- let mut vaa = create_zero_vaa ( ) ;
537- vaa. emitter_address = vec ! [ 3u8 , 4u8 ] ;
538- vaa. emitter_chain = 3 ;
539- assert_eq ! (
540- verify_vaa_from_data_source( & config_info, & vaa) ,
541- Err ( PythContractError :: InvalidUpdateEmitter . into( ) )
614+ let emitter_address = [ 17 , 23 , 14 ] ;
615+ let result = apply_price_update (
616+ & default_config_info ( ) ,
617+ emitter_address. as_slice ( ) ,
618+ EMITTER_CHAIN ,
619+ & [ ] ,
542620 ) ;
621+ assert_eq ! ( result, Err ( PythContractError :: InvalidUpdateEmitter . into( ) ) ) ;
543622 }
544623
545624 #[ test]
546625 fn test_verify_vaa_sender_fail_wrong_emitter_chain ( ) {
547- let config_info = ConfigInfo {
548- data_sources : create_data_sources ( vec ! [ 1u8 ] , 3 ) ,
549- ..create_zero_config_info ( )
550- } ;
626+ let result = apply_price_update (
627+ & default_config_info ( ) ,
628+ default_emitter_addr ( ) . as_slice ( ) ,
629+ EMITTER_CHAIN + 1 ,
630+ & [ ] ,
631+ ) ;
632+ assert_eq ! ( result, Err ( PythContractError :: InvalidUpdateEmitter . into( ) ) ) ;
633+ }
551634
552- let mut vaa = create_zero_vaa ( ) ;
553- vaa. emitter_address = vec ! [ 1u8 ] ;
554- vaa. emitter_chain = 2 ;
555- assert_eq ! (
556- verify_vaa_from_data_source( & config_info, & vaa) ,
557- Err ( PythContractError :: InvalidUpdateEmitter . into( ) )
635+ #[ test]
636+ fn test_update_price_feeds_insufficient_fee ( ) {
637+ let mut config_info = default_config_info ( ) ;
638+ config_info. fee = Uint128 :: new ( 100 ) ;
639+ config_info. fee_denom = "foo" . into ( ) ;
640+
641+ let result = apply_price_update (
642+ & config_info,
643+ default_emitter_addr ( ) . as_slice ( ) ,
644+ EMITTER_CHAIN ,
645+ & [ ] ,
646+ ) ;
647+ assert_eq ! ( result, Err ( PythContractError :: InsufficientFee . into( ) ) ) ;
648+
649+ let result = apply_price_update (
650+ & config_info,
651+ default_emitter_addr ( ) . as_slice ( ) ,
652+ EMITTER_CHAIN ,
653+ coins ( 100 , "foo" ) . as_slice ( ) ,
654+ ) ;
655+ assert ! ( result. is_ok( ) ) ;
656+
657+ let result = apply_price_update (
658+ & config_info,
659+ default_emitter_addr ( ) . as_slice ( ) ,
660+ EMITTER_CHAIN ,
661+ coins ( 99 , "foo" ) . as_slice ( ) ,
662+ ) ;
663+ assert_eq ! ( result, Err ( PythContractError :: InsufficientFee . into( ) ) ) ;
664+
665+ let result = apply_price_update (
666+ & config_info,
667+ default_emitter_addr ( ) . as_slice ( ) ,
668+ EMITTER_CHAIN ,
669+ coins ( 100 , "bar" ) . as_slice ( ) ,
558670 ) ;
671+ assert_eq ! ( result, Err ( PythContractError :: InsufficientFee . into( ) ) ) ;
559672 }
560673
561674 #[ test]
@@ -905,13 +1018,14 @@ mod test {
9051018
9061019 let info = mock_info ( "123" , & [ ] ) ;
9071020
908- let result = execute_governance_instruction_from_vaa ( deps. as_mut ( ) , env, info, vaa) ;
1021+ let result = execute_governance_instruction ( deps. as_mut ( ) , env, info, & to_binary ( & vaa) ? ) ;
9091022
9101023 result. and_then ( |response| config_read ( & deps. storage ) . load ( ) . map ( |c| ( response, c) ) )
9111024 }
9121025
9131026 fn governance_test_config ( ) -> ConfigInfo {
9141027 ConfigInfo {
1028+ wormhole_contract : Addr :: unchecked ( WORMHOLE_ADDR ) ,
9151029 governance_source : PythDataSource {
9161030 emitter : Binary ( vec ! [ 1u8 , 2u8 ] ) ,
9171031 pyth_emitter_chain : 3 ,
0 commit comments