contract.rs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311
  1. use {
  2. crate::{
  3. error::PythContractError,
  4. governance::{
  5. GovernanceAction::{
  6. AuthorizeGovernanceDataSourceTransfer,
  7. RequestGovernanceDataSourceTransfer,
  8. SetDataSources,
  9. SetFee,
  10. SetValidPeriod,
  11. UpgradeContract,
  12. },
  13. GovernanceInstruction,
  14. },
  15. msg::{
  16. ExecuteMsg,
  17. InstantiateMsg,
  18. MigrateMsg,
  19. PriceFeedResponse,
  20. QueryMsg,
  21. },
  22. state::{
  23. config,
  24. config_read,
  25. price_info,
  26. price_info_read,
  27. ConfigInfo,
  28. PriceInfo,
  29. PythDataSource,
  30. },
  31. },
  32. cosmwasm_std::{
  33. coin,
  34. entry_point,
  35. has_coins,
  36. to_binary,
  37. Addr,
  38. Binary,
  39. Coin,
  40. CosmosMsg,
  41. Deps,
  42. DepsMut,
  43. Env,
  44. MessageInfo,
  45. OverflowError,
  46. OverflowOperation,
  47. QueryRequest,
  48. Response,
  49. StdResult,
  50. Timestamp,
  51. WasmMsg,
  52. WasmQuery,
  53. },
  54. p2w_sdk::BatchPriceAttestation,
  55. pyth_sdk_cw::{
  56. PriceFeed,
  57. PriceIdentifier,
  58. PriceStatus,
  59. ProductIdentifier,
  60. },
  61. std::{
  62. collections::HashSet,
  63. convert::TryFrom,
  64. iter::FromIterator,
  65. time::Duration,
  66. },
  67. wormhole::{
  68. msg::QueryMsg as WormholeQueryMsg,
  69. state::ParsedVAA,
  70. },
  71. };
  72. /// Migration code that runs once when the contract is upgraded. On upgrade, the migrate
  73. /// function in the *new* code version is run, which allows the new code to update the on-chain
  74. /// state before any of its other functions are invoked.
  75. ///
  76. /// After the upgrade is complete, the code in this function can be deleted (and replaced with
  77. /// different code for the next migration).
  78. ///
  79. /// Most upgrades won't require any special migration logic. In those cases,
  80. /// this function can safely be implemented as:
  81. /// `Ok(Response::default())`
  82. #[cfg_attr(not(feature = "library"), entry_point)]
  83. pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
  84. Ok(Response::default())
  85. }
  86. #[cfg_attr(not(feature = "library"), entry_point)]
  87. pub fn instantiate(
  88. deps: DepsMut,
  89. _env: Env,
  90. info: MessageInfo,
  91. msg: InstantiateMsg,
  92. ) -> StdResult<Response> {
  93. // Save general wormhole and pyth info
  94. let state = ConfigInfo {
  95. owner: info.sender,
  96. wormhole_contract: deps.api.addr_validate(msg.wormhole_contract.as_ref())?,
  97. data_sources: msg.data_sources.iter().cloned().collect(),
  98. chain_id: msg.chain_id,
  99. governance_source: msg.governance_source.clone(),
  100. governance_source_index: msg.governance_source_index,
  101. governance_sequence_number: msg.governance_sequence_number,
  102. valid_time_period: Duration::from_secs(msg.valid_time_period_secs as u64),
  103. fee: msg.fee,
  104. };
  105. config(deps.storage).save(&state)?;
  106. Ok(Response::default())
  107. }
  108. pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<ParsedVAA> {
  109. let cfg = config_read(deps.storage).load()?;
  110. let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
  111. contract_addr: cfg.wormhole_contract.to_string(),
  112. msg: to_binary(&WormholeQueryMsg::VerifyVAA {
  113. vaa: data.clone(),
  114. block_time,
  115. })?,
  116. }))?;
  117. Ok(vaa)
  118. }
  119. #[cfg_attr(not(feature = "library"), entry_point)]
  120. pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
  121. match msg {
  122. ExecuteMsg::UpdatePriceFeeds { data } => update_price_feeds(deps, env, info, &data),
  123. ExecuteMsg::ExecuteGovernanceInstruction { data } => {
  124. execute_governance_instruction(deps, env, info, &data)
  125. }
  126. }
  127. }
  128. fn update_price_feeds(
  129. mut deps: DepsMut,
  130. env: Env,
  131. info: MessageInfo,
  132. data: &[Binary],
  133. ) -> StdResult<Response> {
  134. let state = config_read(deps.storage).load()?;
  135. if state.fee.amount.u128() > 0
  136. && !has_coins(info.funds.as_ref(), &get_update_fee(&deps.as_ref(), data)?)
  137. {
  138. return Err(PythContractError::InsufficientFee.into());
  139. }
  140. let mut total_attestations: usize = 0;
  141. let mut new_attestations: usize = 0;
  142. for datum in data {
  143. let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), datum)?;
  144. verify_vaa_from_data_source(&state, &vaa)?;
  145. let data = &vaa.payload;
  146. let batch_attestation = BatchPriceAttestation::deserialize(&data[..])
  147. .map_err(|_| PythContractError::InvalidUpdatePayload)?;
  148. let (num_updates, num_new) =
  149. process_batch_attestation(&mut deps, &env, &batch_attestation)?;
  150. total_attestations += num_updates;
  151. new_attestations += num_new;
  152. }
  153. Ok(Response::new()
  154. .add_attribute("action", "update_price_feeds")
  155. .add_attribute("num_attestations", format!("{total_attestations}"))
  156. .add_attribute("num_updated", format!("{new_attestations}")))
  157. }
  158. fn execute_governance_instruction(
  159. mut deps: DepsMut,
  160. env: Env,
  161. _info: MessageInfo,
  162. data: &Binary,
  163. ) -> StdResult<Response> {
  164. let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
  165. let state = config_read(deps.storage).load()?;
  166. // store updates to the config as a result of this action in here.
  167. let mut updated_config: ConfigInfo = state.clone();
  168. verify_vaa_from_governance_source(&state, &vaa)?;
  169. if vaa.sequence <= state.governance_sequence_number {
  170. return Err(PythContractError::OldGovernanceMessage)?;
  171. } else {
  172. updated_config.governance_sequence_number = vaa.sequence;
  173. }
  174. let data = &vaa.payload;
  175. let instruction = GovernanceInstruction::deserialize(&data[..])
  176. .map_err(|_| PythContractError::InvalidGovernancePayload)?;
  177. if instruction.target_chain_id != state.chain_id && instruction.target_chain_id != 0 {
  178. return Err(PythContractError::InvalidGovernancePayload)?;
  179. }
  180. let response = match instruction.action {
  181. UpgradeContract { code_id } => {
  182. if instruction.target_chain_id == 0 {
  183. Err(PythContractError::InvalidGovernancePayload)?
  184. }
  185. upgrade_contract(&env.contract.address, code_id)?
  186. }
  187. AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
  188. let parsed_claim_vaa = parse_vaa(deps.branch(), env.block.time.seconds(), &claim_vaa)?;
  189. transfer_governance(&mut updated_config, &state, &parsed_claim_vaa)?
  190. }
  191. SetDataSources { data_sources } => {
  192. updated_config.data_sources = HashSet::from_iter(data_sources.iter().cloned());
  193. Response::new()
  194. .add_attribute("action", "set_data_sources")
  195. .add_attribute("new_data_sources", format!("{data_sources:?}"))
  196. }
  197. SetFee { val, expo } => {
  198. let new_fee_amount: u128 = (val as u128)
  199. .checked_mul(
  200. 10_u128
  201. .checked_pow(
  202. u32::try_from(expo)
  203. .map_err(|_| PythContractError::InvalidGovernancePayload)?,
  204. )
  205. .ok_or(PythContractError::InvalidGovernancePayload)?,
  206. )
  207. .ok_or(PythContractError::InvalidGovernancePayload)?;
  208. updated_config.fee = Coin::new(new_fee_amount, updated_config.fee.denom.clone());
  209. Response::new()
  210. .add_attribute("action", "set_fee")
  211. .add_attribute("new_fee", format!("{}", updated_config.fee))
  212. }
  213. SetValidPeriod { valid_seconds } => {
  214. updated_config.valid_time_period = Duration::from_secs(valid_seconds);
  215. Response::new()
  216. .add_attribute("action", "set_valid_period")
  217. .add_attribute("new_valid_seconds", format!("{valid_seconds}"))
  218. }
  219. RequestGovernanceDataSourceTransfer { .. } => {
  220. // RequestGovernanceDataSourceTransfer can only be part of the
  221. // AuthorizeGovernanceDataSourceTransfer message.
  222. Err(PythContractError::InvalidGovernancePayload)?
  223. }
  224. };
  225. config(deps.storage).save(&updated_config)?;
  226. Ok(response)
  227. }
  228. /// Transfers governance to the data source provided in `parsed_claim_vaa`. Stores the new
  229. /// governance parameters in `next_config`.
  230. fn transfer_governance(
  231. next_config: &mut ConfigInfo,
  232. current_config: &ConfigInfo,
  233. parsed_claim_vaa: &ParsedVAA,
  234. ) -> StdResult<Response> {
  235. let claim_vaa_instruction =
  236. GovernanceInstruction::deserialize(parsed_claim_vaa.payload.as_slice())
  237. .map_err(|_| PythContractError::InvalidGovernancePayload)?;
  238. if claim_vaa_instruction.target_chain_id != current_config.chain_id
  239. && claim_vaa_instruction.target_chain_id != 0
  240. {
  241. Err(PythContractError::InvalidGovernancePayload)?
  242. }
  243. match claim_vaa_instruction.action {
  244. RequestGovernanceDataSourceTransfer {
  245. governance_data_source_index,
  246. } => {
  247. if current_config.governance_source_index >= governance_data_source_index {
  248. Err(PythContractError::OldGovernanceMessage)?
  249. }
  250. next_config.governance_source_index = governance_data_source_index;
  251. let new_governance_source = PythDataSource {
  252. emitter: Binary::from(parsed_claim_vaa.emitter_address.clone()),
  253. chain_id: parsed_claim_vaa.emitter_chain,
  254. };
  255. next_config.governance_source = new_governance_source;
  256. next_config.governance_sequence_number = parsed_claim_vaa.sequence;
  257. Ok(Response::new()
  258. .add_attribute("action", "authorize_governance_data_source_transfer")
  259. .add_attribute(
  260. "new_governance_emitter_address",
  261. format!("{:?}", parsed_claim_vaa.emitter_address),
  262. )
  263. .add_attribute(
  264. "new_governance_emitter_chain",
  265. format!("{}", parsed_claim_vaa.emitter_chain),
  266. )
  267. .add_attribute(
  268. "new_governance_sequence_number",
  269. format!("{}", parsed_claim_vaa.sequence),
  270. ))
  271. }
  272. _ => Err(PythContractError::InvalidGovernancePayload)?,
  273. }
  274. }
  275. /// Upgrades the contract at `address` to `new_code_id` (by sending a `Migrate` message). The
  276. /// migration will fail unless this contract is the admin of the contract being upgraded.
  277. /// (Typically, `address` is this contract's address, and the contract is its own admin.)
  278. fn upgrade_contract(address: &Addr, new_code_id: u64) -> StdResult<Response> {
  279. Ok(Response::new()
  280. .add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
  281. contract_addr: address.to_string(),
  282. new_code_id,
  283. msg: to_binary(&MigrateMsg {})?,
  284. }))
  285. .add_attribute("action", "upgrade_contract")
  286. .add_attribute("new_code_id", format!("{new_code_id}")))
  287. }
  288. /// Check that `vaa` is from a valid data source (and hence is a legitimate price update message).
  289. fn verify_vaa_from_data_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
  290. let vaa_data_source = PythDataSource {
  291. emitter: vaa.emitter_address.clone().into(),
  292. chain_id: vaa.emitter_chain,
  293. };
  294. if !state.data_sources.contains(&vaa_data_source) {
  295. return Err(PythContractError::InvalidUpdateEmitter)?;
  296. }
  297. Ok(())
  298. }
  299. /// Check that `vaa` is from a valid governance source (and hence is a legitimate governance instruction).
  300. fn verify_vaa_from_governance_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
  301. let vaa_data_source = PythDataSource {
  302. emitter: vaa.emitter_address.clone().into(),
  303. chain_id: vaa.emitter_chain,
  304. };
  305. if state.governance_source != vaa_data_source {
  306. return Err(PythContractError::InvalidUpdateEmitter)?;
  307. }
  308. Ok(())
  309. }
  310. fn process_batch_attestation(
  311. deps: &mut DepsMut,
  312. env: &Env,
  313. batch_attestation: &BatchPriceAttestation,
  314. ) -> StdResult<(usize, usize)> {
  315. let mut new_attestations_cnt: usize = 0;
  316. // Update prices
  317. for price_attestation in batch_attestation.price_attestations.iter() {
  318. let price_feed = PriceFeed::new(
  319. PriceIdentifier::new(price_attestation.price_id.to_bytes()),
  320. price_attestation.status,
  321. price_attestation.publish_time,
  322. price_attestation.expo,
  323. price_attestation.max_num_publishers,
  324. price_attestation.num_publishers,
  325. ProductIdentifier::new(price_attestation.product_id.to_bytes()),
  326. price_attestation.price,
  327. price_attestation.conf,
  328. price_attestation.ema_price,
  329. price_attestation.ema_conf,
  330. price_attestation.prev_price,
  331. price_attestation.prev_conf,
  332. price_attestation.prev_publish_time,
  333. );
  334. let attestation_time = Timestamp::from_seconds(price_attestation.attestation_time as u64);
  335. if update_price_feed_if_new(deps, env, price_feed, attestation_time)? {
  336. new_attestations_cnt += 1;
  337. }
  338. }
  339. Ok((
  340. batch_attestation.price_attestations.len(),
  341. new_attestations_cnt,
  342. ))
  343. }
  344. /// Returns true if the price_feed is newer than the stored one.
  345. ///
  346. /// This function returns error only if there be issues in ser/de when it reads from the bucket.
  347. /// Such an example would be upgrades which migration is not handled carefully so the binary stored
  348. /// in the bucket won't be parsed.
  349. fn update_price_feed_if_new(
  350. deps: &mut DepsMut,
  351. env: &Env,
  352. price_feed: PriceFeed,
  353. attestation_time: Timestamp,
  354. ) -> StdResult<bool> {
  355. let mut is_new_price = true;
  356. price_info(deps.storage).update(
  357. price_feed.id.as_ref(),
  358. |maybe_price_info| -> StdResult<PriceInfo> {
  359. match maybe_price_info {
  360. Some(price_info) => {
  361. // This check ensures that a price won't be updated with the same or older
  362. // message. Attestation_time is guaranteed increasing in
  363. // solana
  364. if price_info.attestation_time < attestation_time {
  365. Ok(PriceInfo {
  366. arrival_time: env.block.time,
  367. arrival_block: env.block.height,
  368. price_feed,
  369. attestation_time,
  370. })
  371. } else {
  372. is_new_price = false;
  373. Ok(price_info)
  374. }
  375. }
  376. None => Ok(PriceInfo {
  377. arrival_time: env.block.time,
  378. arrival_block: env.block.height,
  379. price_feed,
  380. attestation_time,
  381. }),
  382. }
  383. },
  384. )?;
  385. Ok(is_new_price)
  386. }
  387. #[cfg_attr(not(feature = "library"), entry_point)]
  388. pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
  389. match msg {
  390. QueryMsg::PriceFeed { id } => to_binary(&query_price_feed(&deps, env, id.as_ref())?),
  391. QueryMsg::GetUpdateFee { vaas } => to_binary(&get_update_fee(&deps, &vaas)?),
  392. QueryMsg::GetValidTimePeriod => to_binary(&get_valid_time_period(&deps)?),
  393. }
  394. }
  395. pub fn query_price_feed(deps: &Deps, env: Env, address: &[u8]) -> StdResult<PriceFeedResponse> {
  396. let config = config_read(deps.storage).load()?;
  397. match price_info_read(deps.storage).load(address) {
  398. Ok(mut price_info) => {
  399. let env_time_sec = env.block.time.seconds();
  400. let price_pub_time_sec = price_info.price_feed.publish_time as u64;
  401. // Cases that it will cover:
  402. // - This will ensure to set status unknown if the price has become very old and hasn't
  403. // updated yet.
  404. // - If a price has arrived very late to this chain it will set the status to unknown.
  405. // - If a price is coming from future it's tolerated up to VALID_TIME_PERIOD seconds
  406. // (using abs diff) but more than that is set to unknown, the reason could be the
  407. // clock time drift between the source and target chains.
  408. let time_abs_diff = if env_time_sec > price_pub_time_sec {
  409. env_time_sec - price_pub_time_sec
  410. } else {
  411. price_pub_time_sec - env_time_sec
  412. };
  413. if time_abs_diff > config.valid_time_period.as_secs() {
  414. price_info.price_feed.status = PriceStatus::Unknown;
  415. }
  416. Ok(PriceFeedResponse {
  417. price_feed: price_info.price_feed,
  418. })
  419. }
  420. Err(_) => Err(PythContractError::PriceFeedNotFound)?,
  421. }
  422. }
  423. pub fn get_update_fee(deps: &Deps, vaas: &[Binary]) -> StdResult<Coin> {
  424. let config = config_read(deps.storage).load()?;
  425. Ok(coin(
  426. config
  427. .fee
  428. .amount
  429. .u128()
  430. .checked_mul(vaas.len() as u128)
  431. .ok_or(OverflowError::new(
  432. OverflowOperation::Mul,
  433. config.fee.amount,
  434. vaas.len(),
  435. ))?,
  436. config.fee.denom,
  437. ))
  438. }
  439. pub fn get_valid_time_period(deps: &Deps) -> StdResult<Duration> {
  440. Ok(config_read(deps.storage).load()?.valid_time_period)
  441. }
  442. #[cfg(test)]
  443. mod test {
  444. use {
  445. super::*,
  446. crate::governance::GovernanceModule::{
  447. Executor,
  448. Target,
  449. },
  450. cosmwasm_std::{
  451. coins,
  452. from_binary,
  453. testing::{
  454. mock_dependencies,
  455. mock_env,
  456. mock_info,
  457. MockApi,
  458. MockQuerier,
  459. MockStorage,
  460. },
  461. Addr,
  462. ContractResult,
  463. OwnedDeps,
  464. QuerierResult,
  465. SystemError,
  466. SystemResult,
  467. Uint128,
  468. },
  469. std::time::Duration,
  470. };
  471. /// Default valid time period for testing purposes.
  472. const VALID_TIME_PERIOD: Duration = Duration::from_secs(3 * 60);
  473. const WORMHOLE_ADDR: &str = "Wormhole";
  474. const EMITTER_CHAIN: u16 = 3;
  475. fn default_emitter_addr() -> Vec<u8> {
  476. vec![0, 1, 80]
  477. }
  478. fn default_config_info() -> ConfigInfo {
  479. ConfigInfo {
  480. wormhole_contract: Addr::unchecked(WORMHOLE_ADDR),
  481. data_sources: create_data_sources(default_emitter_addr(), EMITTER_CHAIN),
  482. ..create_zero_config_info()
  483. }
  484. }
  485. fn setup_test() -> (OwnedDeps<MockStorage, MockApi, MockQuerier>, Env) {
  486. let mut dependencies = mock_dependencies();
  487. dependencies.querier.update_wasm(handle_wasm_query);
  488. let mut config = config(dependencies.as_mut().storage);
  489. config
  490. .save(&ConfigInfo {
  491. valid_time_period: VALID_TIME_PERIOD,
  492. ..create_zero_config_info()
  493. })
  494. .unwrap();
  495. (dependencies, mock_env())
  496. }
  497. /// Mock handler for wormhole queries.
  498. /// Warning: the interface for the `VerifyVAA` action is slightly different than the real wormhole contract.
  499. /// In the mock, you pass in a binary-encoded `ParsedVAA`, and that exact vaa will be returned by wormhole.
  500. /// The real contract uses a different binary VAA format (see `ParsedVAA::deserialize`) which includes
  501. /// the guardian signatures.
  502. fn handle_wasm_query(wasm_query: &WasmQuery) -> QuerierResult {
  503. match wasm_query {
  504. WasmQuery::Smart { contract_addr, msg } if *contract_addr == WORMHOLE_ADDR => {
  505. let query_msg = from_binary::<WormholeQueryMsg>(msg);
  506. match query_msg {
  507. Ok(WormholeQueryMsg::VerifyVAA { vaa, .. }) => {
  508. SystemResult::Ok(ContractResult::Ok(vaa))
  509. }
  510. Err(_e) => SystemResult::Err(SystemError::InvalidRequest {
  511. error: "Invalid message".into(),
  512. request: msg.clone(),
  513. }),
  514. _ => SystemResult::Err(SystemError::NoSuchContract {
  515. addr: contract_addr.clone(),
  516. }),
  517. }
  518. }
  519. WasmQuery::Smart { contract_addr, .. } => {
  520. SystemResult::Err(SystemError::NoSuchContract {
  521. addr: contract_addr.clone(),
  522. })
  523. }
  524. WasmQuery::Raw { contract_addr, .. } => {
  525. SystemResult::Err(SystemError::NoSuchContract {
  526. addr: contract_addr.clone(),
  527. })
  528. }
  529. WasmQuery::ContractInfo { contract_addr, .. } => {
  530. SystemResult::Err(SystemError::NoSuchContract {
  531. addr: contract_addr.clone(),
  532. })
  533. }
  534. _ => unreachable!(),
  535. }
  536. }
  537. fn create_zero_vaa() -> ParsedVAA {
  538. ParsedVAA {
  539. version: 0,
  540. guardian_set_index: 0,
  541. timestamp: 0,
  542. nonce: 0,
  543. len_signers: 0,
  544. emitter_chain: 0,
  545. emitter_address: vec![],
  546. sequence: 0,
  547. consistency_level: 0,
  548. payload: vec![],
  549. hash: vec![],
  550. }
  551. }
  552. fn create_price_update_msg(emitter_address: &[u8], emitter_chain: u16) -> Binary {
  553. let batch_attestation = BatchPriceAttestation {
  554. // TODO: pass these in
  555. price_attestations: vec![],
  556. };
  557. let mut vaa = create_zero_vaa();
  558. vaa.emitter_address = emitter_address.to_vec();
  559. vaa.emitter_chain = emitter_chain;
  560. vaa.payload = batch_attestation.serialize().unwrap();
  561. to_binary(&vaa).unwrap()
  562. }
  563. fn create_zero_config_info() -> ConfigInfo {
  564. ConfigInfo {
  565. owner: Addr::unchecked(String::default()),
  566. wormhole_contract: Addr::unchecked(String::default()),
  567. data_sources: HashSet::default(),
  568. governance_source: PythDataSource {
  569. emitter: Binary(vec![]),
  570. chain_id: 0,
  571. },
  572. governance_source_index: 0,
  573. governance_sequence_number: 0,
  574. chain_id: 0,
  575. valid_time_period: Duration::new(0, 0),
  576. fee: Coin::new(0, ""),
  577. }
  578. }
  579. fn create_price_feed(expo: i32) -> PriceFeed {
  580. let mut price_feed = PriceFeed::default();
  581. price_feed.expo = expo;
  582. price_feed
  583. }
  584. fn create_data_sources(
  585. pyth_emitter: Vec<u8>,
  586. pyth_emitter_chain: u16,
  587. ) -> HashSet<PythDataSource> {
  588. HashSet::from([PythDataSource {
  589. emitter: pyth_emitter.into(),
  590. chain_id: pyth_emitter_chain,
  591. }])
  592. }
  593. /// Updates the price feed with the given attestation time stamp and
  594. /// returns the update status (true means updated, false means ignored)
  595. fn do_update_price_feed(
  596. deps: &mut DepsMut,
  597. env: &Env,
  598. price_feed: PriceFeed,
  599. attestation_time_seconds: u64,
  600. ) -> bool {
  601. update_price_feed_if_new(
  602. deps,
  603. env,
  604. price_feed,
  605. Timestamp::from_seconds(attestation_time_seconds),
  606. )
  607. .unwrap()
  608. }
  609. fn apply_price_update(
  610. config_info: &ConfigInfo,
  611. emitter_address: &[u8],
  612. emitter_chain: u16,
  613. funds: &[Coin],
  614. ) -> StdResult<Response> {
  615. let (mut deps, env) = setup_test();
  616. config(&mut deps.storage).save(config_info).unwrap();
  617. let info = mock_info("123", funds);
  618. let msg = create_price_update_msg(emitter_address, emitter_chain);
  619. update_price_feeds(deps.as_mut(), env, info, &[msg])
  620. }
  621. #[test]
  622. fn test_verify_vaa_sender_ok() {
  623. let result = apply_price_update(
  624. &default_config_info(),
  625. default_emitter_addr().as_slice(),
  626. EMITTER_CHAIN,
  627. &[],
  628. );
  629. assert!(result.is_ok());
  630. }
  631. #[test]
  632. fn test_verify_vaa_sender_fail_wrong_emitter_address() {
  633. let emitter_address = [17, 23, 14];
  634. let result = apply_price_update(
  635. &default_config_info(),
  636. emitter_address.as_slice(),
  637. EMITTER_CHAIN,
  638. &[],
  639. );
  640. assert_eq!(result, Err(PythContractError::InvalidUpdateEmitter.into()));
  641. }
  642. #[test]
  643. fn test_verify_vaa_sender_fail_wrong_emitter_chain() {
  644. let result = apply_price_update(
  645. &default_config_info(),
  646. default_emitter_addr().as_slice(),
  647. EMITTER_CHAIN + 1,
  648. &[],
  649. );
  650. assert_eq!(result, Err(PythContractError::InvalidUpdateEmitter.into()));
  651. }
  652. #[test]
  653. fn test_update_price_feeds_insufficient_fee() {
  654. let mut config_info = default_config_info();
  655. config_info.fee = Coin::new(100, "foo");
  656. let result = apply_price_update(
  657. &config_info,
  658. default_emitter_addr().as_slice(),
  659. EMITTER_CHAIN,
  660. &[],
  661. );
  662. assert_eq!(result, Err(PythContractError::InsufficientFee.into()));
  663. let result = apply_price_update(
  664. &config_info,
  665. default_emitter_addr().as_slice(),
  666. EMITTER_CHAIN,
  667. coins(100, "foo").as_slice(),
  668. );
  669. assert!(result.is_ok());
  670. let result = apply_price_update(
  671. &config_info,
  672. default_emitter_addr().as_slice(),
  673. EMITTER_CHAIN,
  674. coins(99, "foo").as_slice(),
  675. );
  676. assert_eq!(result, Err(PythContractError::InsufficientFee.into()));
  677. let result = apply_price_update(
  678. &config_info,
  679. default_emitter_addr().as_slice(),
  680. EMITTER_CHAIN,
  681. coins(100, "bar").as_slice(),
  682. );
  683. assert_eq!(result, Err(PythContractError::InsufficientFee.into()));
  684. }
  685. #[test]
  686. fn test_update_price_feed_if_new_first_price_ok() {
  687. let (mut deps, env) = setup_test();
  688. let price_feed = create_price_feed(3);
  689. let changed = do_update_price_feed(&mut deps.as_mut(), &env, price_feed, 100);
  690. assert!(changed);
  691. let stored_price_feed = price_info(&mut deps.storage)
  692. .load(price_feed.id.as_ref())
  693. .unwrap()
  694. .price_feed;
  695. assert_eq!(stored_price_feed, price_feed);
  696. }
  697. #[test]
  698. fn test_update_price_feed_if_new_ignore_duplicate_time() {
  699. let (mut deps, env) = setup_test();
  700. let time = 100;
  701. let first_price_feed = create_price_feed(3);
  702. let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, time);
  703. assert!(changed);
  704. let second_price_feed = create_price_feed(4);
  705. let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, time);
  706. assert!(!changed);
  707. let stored_price_feed = price_info(&mut deps.storage)
  708. .load(first_price_feed.id.as_ref())
  709. .unwrap()
  710. .price_feed;
  711. assert_eq!(stored_price_feed, first_price_feed);
  712. }
  713. #[test]
  714. fn test_update_price_feed_if_new_ignore_older() {
  715. let (mut deps, env) = setup_test();
  716. let first_price_feed = create_price_feed(3);
  717. let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, 100);
  718. assert!(changed);
  719. let second_price_feed = create_price_feed(4);
  720. let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, 90);
  721. assert!(!changed);
  722. let stored_price_feed = price_info(&mut deps.storage)
  723. .load(first_price_feed.id.as_ref())
  724. .unwrap()
  725. .price_feed;
  726. assert_eq!(stored_price_feed, first_price_feed);
  727. }
  728. #[test]
  729. fn test_update_price_feed_if_new_accept_newer() {
  730. let (mut deps, env) = setup_test();
  731. let first_price_feed = create_price_feed(3);
  732. let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, 100);
  733. assert!(changed);
  734. let second_price_feed = create_price_feed(4);
  735. let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, 110);
  736. assert!(changed);
  737. let stored_price_feed = price_info(&mut deps.storage)
  738. .load(first_price_feed.id.as_ref())
  739. .unwrap()
  740. .price_feed;
  741. assert_eq!(stored_price_feed, second_price_feed);
  742. }
  743. #[test]
  744. fn test_query_price_info_ok_trading() {
  745. let (mut deps, mut env) = setup_test();
  746. let address = b"123".as_ref();
  747. let mut dummy_price_info = PriceInfo::default();
  748. dummy_price_info.price_feed.publish_time = 80;
  749. dummy_price_info.price_feed.status = PriceStatus::Trading;
  750. price_info(&mut deps.storage)
  751. .save(address, &dummy_price_info)
  752. .unwrap();
  753. env.block.time = Timestamp::from_seconds(80 + VALID_TIME_PERIOD.as_secs());
  754. let price_feed = query_price_feed(&deps.as_ref(), env, address)
  755. .unwrap()
  756. .price_feed;
  757. assert_eq!(price_feed.status, PriceStatus::Trading);
  758. }
  759. #[test]
  760. fn test_query_price_info_ok_stale_past() {
  761. let (mut deps, mut env) = setup_test();
  762. let address = b"123".as_ref();
  763. let mut dummy_price_info = PriceInfo::default();
  764. dummy_price_info.price_feed.publish_time = 500;
  765. dummy_price_info.price_feed.status = PriceStatus::Trading;
  766. price_info(&mut deps.storage)
  767. .save(address, &dummy_price_info)
  768. .unwrap();
  769. env.block.time = Timestamp::from_seconds(500 + VALID_TIME_PERIOD.as_secs() + 1);
  770. let price_feed = query_price_feed(&deps.as_ref(), env, address)
  771. .unwrap()
  772. .price_feed;
  773. assert_eq!(price_feed.status, PriceStatus::Unknown);
  774. }
  775. #[test]
  776. fn test_query_price_info_ok_trading_future() {
  777. let (mut deps, mut env) = setup_test();
  778. let address = b"123".as_ref();
  779. let mut dummy_price_info = PriceInfo::default();
  780. dummy_price_info.price_feed.publish_time = 500;
  781. dummy_price_info.price_feed.status = PriceStatus::Trading;
  782. price_info(&mut deps.storage)
  783. .save(address, &dummy_price_info)
  784. .unwrap();
  785. env.block.time = Timestamp::from_seconds(500 - VALID_TIME_PERIOD.as_secs());
  786. let price_feed = query_price_feed(&deps.as_ref(), env, address)
  787. .unwrap()
  788. .price_feed;
  789. assert_eq!(price_feed.status, PriceStatus::Trading);
  790. }
  791. #[test]
  792. fn test_query_price_info_ok_stale_future() {
  793. let (mut deps, mut env) = setup_test();
  794. let address = b"123".as_ref();
  795. let mut dummy_price_info = PriceInfo::default();
  796. dummy_price_info.price_feed.publish_time = 500;
  797. dummy_price_info.price_feed.status = PriceStatus::Trading;
  798. price_info(&mut deps.storage)
  799. .save(address, &dummy_price_info)
  800. .unwrap();
  801. env.block.time = Timestamp::from_seconds(500 - VALID_TIME_PERIOD.as_secs() - 1);
  802. let price_feed = query_price_feed(&deps.as_ref(), env, address)
  803. .unwrap()
  804. .price_feed;
  805. assert_eq!(price_feed.status, PriceStatus::Unknown);
  806. }
  807. #[test]
  808. fn test_query_price_info_err_not_found() {
  809. let (deps, env) = setup_test();
  810. assert_eq!(
  811. query_price_feed(&deps.as_ref(), env, b"123".as_ref()),
  812. Err(PythContractError::PriceFeedNotFound.into())
  813. );
  814. }
  815. #[test]
  816. fn test_get_update_fee() {
  817. let (mut deps, _env) = setup_test();
  818. let fee_denom: String = "test".into();
  819. config(&mut deps.storage)
  820. .save(&ConfigInfo {
  821. fee: Coin::new(10, fee_denom.clone()),
  822. ..create_zero_config_info()
  823. })
  824. .unwrap();
  825. let updates = vec![Binary::from([1u8]), Binary::from([2u8])];
  826. assert_eq!(
  827. get_update_fee(&deps.as_ref(), &updates[0..0]),
  828. Ok(Coin::new(0, fee_denom.clone()))
  829. );
  830. assert_eq!(
  831. get_update_fee(&deps.as_ref(), &updates[0..1]),
  832. Ok(Coin::new(10, fee_denom.clone()))
  833. );
  834. assert_eq!(
  835. get_update_fee(&deps.as_ref(), &updates[0..2]),
  836. Ok(Coin::new(20, fee_denom.clone()))
  837. );
  838. let big_fee: u128 = (u128::MAX / 4) * 3;
  839. config(&mut deps.storage)
  840. .save(&ConfigInfo {
  841. fee: Coin::new(big_fee, fee_denom.clone()),
  842. ..create_zero_config_info()
  843. })
  844. .unwrap();
  845. assert_eq!(
  846. get_update_fee(&deps.as_ref(), &updates[0..1]),
  847. Ok(Coin::new(big_fee, fee_denom))
  848. );
  849. assert!(get_update_fee(&deps.as_ref(), &updates[0..2]).is_err());
  850. }
  851. #[test]
  852. fn test_get_valid_time_period() {
  853. let (mut deps, _env) = setup_test();
  854. config(&mut deps.storage)
  855. .save(&ConfigInfo {
  856. valid_time_period: Duration::from_secs(10),
  857. ..create_zero_config_info()
  858. })
  859. .unwrap();
  860. assert_eq!(
  861. get_valid_time_period(&deps.as_ref()),
  862. Ok(Duration::from_secs(10))
  863. );
  864. }
  865. /// Initialize the contract with `initial_config` then execute `vaa` as a governance instruction
  866. /// against it. Returns the response of the governance instruction along with the resulting config.
  867. fn apply_governance_vaa(
  868. initial_config: &ConfigInfo,
  869. vaa: &ParsedVAA,
  870. ) -> StdResult<(Response, ConfigInfo)> {
  871. let (mut deps, env) = setup_test();
  872. config(&mut deps.storage).save(initial_config).unwrap();
  873. let info = mock_info("123", &[]);
  874. let result = execute_governance_instruction(deps.as_mut(), env, info, &to_binary(&vaa)?);
  875. result.and_then(|response| config_read(&deps.storage).load().map(|c| (response, c)))
  876. }
  877. fn governance_test_config() -> ConfigInfo {
  878. ConfigInfo {
  879. wormhole_contract: Addr::unchecked(WORMHOLE_ADDR),
  880. governance_source: PythDataSource {
  881. emitter: Binary(vec![1u8, 2u8]),
  882. chain_id: 3,
  883. },
  884. governance_sequence_number: 4,
  885. chain_id: 5,
  886. ..create_zero_config_info()
  887. }
  888. }
  889. fn governance_vaa(instruction: &GovernanceInstruction) -> ParsedVAA {
  890. let mut vaa = create_zero_vaa();
  891. vaa.emitter_address = vec![1u8, 2u8];
  892. vaa.emitter_chain = 3;
  893. vaa.sequence = 7;
  894. vaa.payload = instruction.serialize().unwrap();
  895. vaa
  896. }
  897. #[test]
  898. fn test_governance_authorization() {
  899. let test_config = governance_test_config();
  900. let test_instruction = GovernanceInstruction {
  901. module: Target,
  902. target_chain_id: 5,
  903. action: SetFee { val: 6, expo: 0 },
  904. };
  905. let test_vaa = governance_vaa(&test_instruction);
  906. // First check that a valid VAA is accepted (to ensure that no one accidentally breaks the following test cases).
  907. assert!(apply_governance_vaa(&test_config, &test_vaa).is_ok());
  908. // Wrong emitter address
  909. let mut vaa_copy = test_vaa.clone();
  910. vaa_copy.emitter_address = vec![2u8, 3u8];
  911. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  912. // wrong source chain
  913. let mut vaa_copy = test_vaa.clone();
  914. vaa_copy.emitter_chain = 4;
  915. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  916. // sequence number too low
  917. let mut vaa_copy = test_vaa.clone();
  918. vaa_copy.sequence = 4;
  919. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  920. // wrong magic number
  921. let mut vaa_copy = test_vaa.clone();
  922. vaa_copy.payload[0] = 0;
  923. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  924. // wrong target chain
  925. let mut instruction_copy = test_instruction.clone();
  926. instruction_copy.target_chain_id = 6;
  927. let mut vaa_copy = test_vaa.clone();
  928. vaa_copy.payload = instruction_copy.serialize().unwrap();
  929. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  930. // target chain == 0 is allowed
  931. let mut instruction_copy = test_instruction.clone();
  932. instruction_copy.target_chain_id = 0;
  933. let mut vaa_copy = test_vaa.clone();
  934. vaa_copy.payload = instruction_copy.serialize().unwrap();
  935. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_ok());
  936. // wrong module
  937. let mut instruction_copy = test_instruction.clone();
  938. instruction_copy.module = Executor;
  939. let mut vaa_copy = test_vaa;
  940. vaa_copy.payload = instruction_copy.serialize().unwrap();
  941. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  942. // invalid action index
  943. let _instruction_copy = test_instruction;
  944. vaa_copy.payload[9] = 100;
  945. assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
  946. }
  947. #[test]
  948. fn test_authorize_governance_transfer_success() {
  949. let source_2 = PythDataSource {
  950. emitter: Binary::from([2u8; 32]),
  951. chain_id: 4,
  952. };
  953. let test_config = governance_test_config();
  954. let test_instruction = GovernanceInstruction {
  955. module: Target,
  956. target_chain_id: test_config.chain_id,
  957. action: AuthorizeGovernanceDataSourceTransfer {
  958. claim_vaa: to_binary(&ParsedVAA {
  959. emitter_address: source_2.emitter.to_vec(),
  960. emitter_chain: source_2.chain_id,
  961. sequence: 12,
  962. payload: GovernanceInstruction {
  963. module: Target,
  964. target_chain_id: test_config.chain_id,
  965. action: RequestGovernanceDataSourceTransfer {
  966. governance_data_source_index: 11,
  967. },
  968. }
  969. .serialize()
  970. .unwrap(),
  971. ..create_zero_vaa()
  972. })
  973. .unwrap(),
  974. },
  975. };
  976. let test_vaa = governance_vaa(&test_instruction);
  977. let (_response, result_config) = apply_governance_vaa(&test_config, &test_vaa).unwrap();
  978. assert_eq!(result_config.governance_source, source_2);
  979. assert_eq!(result_config.governance_source_index, 11);
  980. assert_eq!(result_config.governance_sequence_number, 12);
  981. }
  982. #[test]
  983. fn test_authorize_governance_transfer_bad_source_index() {
  984. let source_2 = PythDataSource {
  985. emitter: Binary::from([2u8; 32]),
  986. chain_id: 4,
  987. };
  988. let mut test_config = governance_test_config();
  989. test_config.governance_source_index = 10;
  990. let test_instruction = GovernanceInstruction {
  991. module: Target,
  992. target_chain_id: test_config.chain_id,
  993. action: AuthorizeGovernanceDataSourceTransfer {
  994. claim_vaa: to_binary(&ParsedVAA {
  995. emitter_address: source_2.emitter.to_vec(),
  996. emitter_chain: source_2.chain_id,
  997. sequence: 12,
  998. payload: GovernanceInstruction {
  999. module: Target,
  1000. target_chain_id: test_config.chain_id,
  1001. action: RequestGovernanceDataSourceTransfer {
  1002. governance_data_source_index: 10,
  1003. },
  1004. }
  1005. .serialize()
  1006. .unwrap(),
  1007. ..create_zero_vaa()
  1008. })
  1009. .unwrap(),
  1010. },
  1011. };
  1012. let test_vaa = governance_vaa(&test_instruction);
  1013. assert_eq!(
  1014. apply_governance_vaa(&test_config, &test_vaa),
  1015. Err(PythContractError::OldGovernanceMessage.into())
  1016. );
  1017. }
  1018. #[test]
  1019. fn test_authorize_governance_transfer_bad_target_chain() {
  1020. let source_2 = PythDataSource {
  1021. emitter: Binary::from([2u8; 32]),
  1022. chain_id: 4,
  1023. };
  1024. let test_config = governance_test_config();
  1025. let test_instruction = GovernanceInstruction {
  1026. module: Target,
  1027. target_chain_id: test_config.chain_id,
  1028. action: AuthorizeGovernanceDataSourceTransfer {
  1029. claim_vaa: to_binary(&ParsedVAA {
  1030. emitter_address: source_2.emitter.to_vec(),
  1031. emitter_chain: source_2.chain_id,
  1032. sequence: 12,
  1033. payload: GovernanceInstruction {
  1034. module: Target,
  1035. target_chain_id: test_config.chain_id + 1,
  1036. action: RequestGovernanceDataSourceTransfer {
  1037. governance_data_source_index: 11,
  1038. },
  1039. }
  1040. .serialize()
  1041. .unwrap(),
  1042. ..create_zero_vaa()
  1043. })
  1044. .unwrap(),
  1045. },
  1046. };
  1047. let test_vaa = governance_vaa(&test_instruction);
  1048. assert_eq!(
  1049. apply_governance_vaa(&test_config, &test_vaa),
  1050. Err(PythContractError::InvalidGovernancePayload.into())
  1051. );
  1052. }
  1053. #[test]
  1054. fn test_set_data_sources() {
  1055. let source_1 = PythDataSource {
  1056. emitter: Binary::from([1u8; 32]),
  1057. chain_id: 2,
  1058. };
  1059. let source_2 = PythDataSource {
  1060. emitter: Binary::from([2u8; 32]),
  1061. chain_id: 4,
  1062. };
  1063. let source_3 = PythDataSource {
  1064. emitter: Binary::from([3u8; 32]),
  1065. chain_id: 6,
  1066. };
  1067. let mut test_config = governance_test_config();
  1068. test_config.data_sources = HashSet::from([source_1]);
  1069. let test_instruction = GovernanceInstruction {
  1070. module: Target,
  1071. target_chain_id: test_config.chain_id,
  1072. action: SetDataSources {
  1073. data_sources: vec![source_2.clone(), source_3.clone()],
  1074. },
  1075. };
  1076. let test_vaa = governance_vaa(&test_instruction);
  1077. assert_eq!(
  1078. apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.data_sources),
  1079. Ok([source_2, source_3].iter().cloned().collect())
  1080. );
  1081. let test_instruction = GovernanceInstruction {
  1082. module: Target,
  1083. target_chain_id: test_config.chain_id,
  1084. action: SetDataSources {
  1085. data_sources: vec![],
  1086. },
  1087. };
  1088. let test_vaa = governance_vaa(&test_instruction);
  1089. assert_eq!(
  1090. apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.data_sources),
  1091. Ok(HashSet::new())
  1092. );
  1093. }
  1094. #[test]
  1095. fn test_set_fee() {
  1096. let mut test_config = governance_test_config();
  1097. test_config.fee = Coin::new(1, "foo");
  1098. let test_instruction = GovernanceInstruction {
  1099. module: Target,
  1100. target_chain_id: 5,
  1101. action: SetFee { val: 6, expo: 1 },
  1102. };
  1103. let test_vaa = governance_vaa(&test_instruction);
  1104. assert_eq!(
  1105. apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.fee.amount),
  1106. Ok(Uint128::new(60))
  1107. );
  1108. let test_instruction = GovernanceInstruction {
  1109. module: Target,
  1110. target_chain_id: 5,
  1111. action: SetFee { val: 6, expo: 0 },
  1112. };
  1113. let test_vaa = governance_vaa(&test_instruction);
  1114. assert_eq!(
  1115. apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.fee.amount),
  1116. Ok(Uint128::new(6))
  1117. );
  1118. }
  1119. #[test]
  1120. fn test_set_valid_period() {
  1121. let mut test_config = governance_test_config();
  1122. test_config.valid_time_period = Duration::from_secs(10);
  1123. let test_instruction = GovernanceInstruction {
  1124. module: Target,
  1125. target_chain_id: 5,
  1126. action: SetValidPeriod { valid_seconds: 20 },
  1127. };
  1128. let test_vaa = governance_vaa(&test_instruction);
  1129. assert_eq!(
  1130. apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.valid_time_period),
  1131. Ok(Duration::from_secs(20))
  1132. );
  1133. }
  1134. #[test]
  1135. fn test_request_governance_transfer() {
  1136. let test_config = governance_test_config();
  1137. let test_instruction = GovernanceInstruction {
  1138. module: Target,
  1139. target_chain_id: test_config.chain_id,
  1140. action: RequestGovernanceDataSourceTransfer {
  1141. governance_data_source_index: 7,
  1142. },
  1143. };
  1144. let test_vaa = governance_vaa(&test_instruction);
  1145. assert!(apply_governance_vaa(&test_config, &test_vaa).is_err());
  1146. }
  1147. }