Skip to content

Commit 6b29d97

Browse files
jayantkJayant Krishnamurthyali-behjati
authored
[cosmos] Pay fee + mock wormhole for testing (#433)
* added handler * create binary for vaas * updating tests * add fee test * blah * simplify * simplify * cleanup5 * Add fees to the terra relayer Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com> Co-authored-by: Ali Behjati <bahjatia@gmail.com>
1 parent f6ad2d6 commit 6b29d97

File tree

4 files changed

+177
-56
lines changed

4 files changed

+177
-56
lines changed

cosmwasm/contracts/pyth/src/contract.rs

Lines changed: 161 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ use {
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
120122
fn 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(
139145
fn 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,

cosmwasm/contracts/pyth/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub enum PythContractError {
4040
/// The sequence number of the governance message is too old.
4141
#[error("OldGovernanceMessage")]
4242
OldGovernanceMessage,
43+
44+
/// The message did not include a sufficient fee.
45+
#[error("InsufficientFee")]
46+
InsufficientFee,
4347
}
4448

4549
impl From<PythContractError> for StdError {

third_party/pyth/p2w-relay/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ for (let idx = 0; idx < process.argv.length; ++idx) {
3737
nodeUrl: helpers.envOrErr("TERRA_NODE_URL"),
3838
terraChainId: helpers.envOrErr("TERRA_CHAIN_ID"),
3939
walletPrivateKey: helpers.envOrErr("TERRA_PRIVATE_KEY"),
40-
coin: helpers.envOrErr("TERRA_COIN"),
40+
coinDenom: helpers.envOrErr("TERRA_COIN"),
4141
contractAddress: helpers.envOrErr("TERRA_PYTH_CONTRACT_ADDRESS"),
4242
});
4343
logger.info("Relaying to Terra");

0 commit comments

Comments
 (0)