24
24
} ,
25
25
cosmwasm_std:: {
26
26
entry_point,
27
+ has_coins,
27
28
to_binary,
28
29
Binary ,
30
+ Coin ,
29
31
Deps ,
30
32
DepsMut ,
31
33
Env ,
@@ -120,13 +122,17 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
120
122
fn update_price_feeds (
121
123
mut deps : DepsMut ,
122
124
env : Env ,
123
- _info : MessageInfo ,
125
+ info : MessageInfo ,
124
126
data : & Binary ,
125
127
) -> StdResult < Response > {
126
128
let state = config_read ( deps. storage ) . load ( ) ?;
127
129
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
+ }
129
134
135
+ let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
130
136
verify_vaa_from_data_source ( & state, & vaa) ?;
131
137
132
138
let data = & vaa. payload ;
@@ -139,26 +145,15 @@ fn update_price_feeds(
139
145
fn execute_governance_instruction (
140
146
mut deps : DepsMut ,
141
147
env : Env ,
142
- info : MessageInfo ,
148
+ _info : MessageInfo ,
143
149
data : & Binary ,
144
150
) -> StdResult < Response > {
145
151
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 > {
157
152
let state = config_read ( deps. storage ) . load ( ) ?;
158
153
159
154
// store updates to the config as a result of this action in here.
160
155
let mut updated_config: ConfigInfo = state. clone ( ) ;
161
- verify_vaa_from_governance_source ( & state, vaa) ?;
156
+ verify_vaa_from_governance_source ( & state, & vaa) ?;
162
157
163
158
if vaa. sequence <= state. governance_sequence_number {
164
159
return Err ( PythContractError :: OldGovernanceMessage ) ?;
@@ -417,6 +412,8 @@ mod test {
417
412
Target ,
418
413
} ,
419
414
cosmwasm_std:: {
415
+ coins,
416
+ from_binary,
420
417
testing:: {
421
418
mock_dependencies,
422
419
mock_env,
@@ -426,16 +423,36 @@ mod test {
426
423
MockStorage ,
427
424
} ,
428
425
Addr ,
426
+ ContractResult ,
429
427
OwnedDeps ,
428
+ QuerierResult ,
429
+ SystemError ,
430
+ SystemResult ,
430
431
} ,
431
432
std:: time:: Duration ,
432
433
} ;
433
434
434
435
/// Default valid time period for testing purposes.
435
436
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
+ }
436
451
437
452
fn setup_test ( ) -> ( OwnedDeps < MockStorage , MockApi , MockQuerier > , Env ) {
438
453
let mut dependencies = mock_dependencies ( ) ;
454
+ dependencies. querier . update_wasm ( handle_wasm_query) ;
455
+
439
456
let mut config = config ( dependencies. as_mut ( ) . storage ) ;
440
457
config
441
458
. save ( & ConfigInfo {
@@ -446,6 +463,47 @@ mod test {
446
463
( dependencies, mock_env ( ) )
447
464
}
448
465
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
+
449
507
fn create_zero_vaa ( ) -> ParsedVAA {
450
508
ParsedVAA {
451
509
version : 0 ,
@@ -462,6 +520,20 @@ mod test {
462
520
}
463
521
}
464
522
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
+
465
537
fn create_zero_config_info ( ) -> ConfigInfo {
466
538
ConfigInfo {
467
539
owner : Addr :: unchecked ( String :: default ( ) ) ,
@@ -512,50 +584,91 @@ mod test {
512
584
. unwrap ( )
513
585
}
514
586
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 ( ) ;
521
595
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
+ }
525
600
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( ) ) ;
527
610
}
528
611
529
612
#[ test]
530
613
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
+ & [ ] ,
542
620
) ;
621
+ assert_eq ! ( result, Err ( PythContractError :: InvalidUpdateEmitter . into( ) ) ) ;
543
622
}
544
623
545
624
#[ test]
546
625
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
+ }
551
634
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 ( ) ,
558
670
) ;
671
+ assert_eq ! ( result, Err ( PythContractError :: InsufficientFee . into( ) ) ) ;
559
672
}
560
673
561
674
#[ test]
@@ -905,13 +1018,14 @@ mod test {
905
1018
906
1019
let info = mock_info ( "123" , & [ ] ) ;
907
1020
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) ? ) ;
909
1022
910
1023
result. and_then ( |response| config_read ( & deps. storage ) . load ( ) . map ( |c| ( response, c) ) )
911
1024
}
912
1025
913
1026
fn governance_test_config ( ) -> ConfigInfo {
914
1027
ConfigInfo {
1028
+ wormhole_contract : Addr :: unchecked ( WORMHOLE_ADDR ) ,
915
1029
governance_source : PythDataSource {
916
1030
emitter : Binary ( vec ! [ 1u8 , 2u8 ] ) ,
917
1031
pyth_emitter_chain : 3 ,
0 commit comments