workspaces.rs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. use {
  2. near_sdk::json_types::{I64, U128, U64},
  3. near_workspaces::types::{Gas, NearToken},
  4. pyth_near::{
  5. governance::{GovernanceAction, GovernanceInstruction, GovernanceModule},
  6. state::{Chain, Price, PriceIdentifier, Source},
  7. },
  8. pythnet_sdk::legacy::{
  9. BatchPriceAttestation, Identifier, PriceAttestation, PriceStatus,
  10. },
  11. pythnet_sdk::test_utils::{
  12. create_accumulator_message, create_dummy_price_feed_message, create_vaa_from_payload,
  13. DEFAULT_DATA_SOURCE, DEFAULT_GOVERNANCE_SOURCE, DEFAULT_VALID_TIME_PERIOD,
  14. SECONDARY_DATA_SOURCE, SECONDARY_GOVERNANCE_SOURCE,
  15. },
  16. serde_json::json,
  17. std::collections::HashMap,
  18. wormhole_sdk::Chain as WormholeChain,
  19. };
  20. async fn initialize_chain() -> (
  21. near_workspaces::Worker<near_workspaces::network::Sandbox>,
  22. near_workspaces::Contract,
  23. near_workspaces::Contract,
  24. ) {
  25. let worker = near_workspaces::sandbox().await.expect("Workspaces Failed");
  26. // Deploy Pyth
  27. let contract = worker
  28. .dev_deploy(&std::fs::read("pyth_near.wasm").expect("Failed to find pyth_near.wasm"))
  29. .await
  30. .expect("Failed to deploy pyth_near.wasm");
  31. // Deploy Wormhole Stub, this is a dummy contract that always verifies VAA's correctly so we
  32. // can test the ext_wormhole API.
  33. let wormhole = worker
  34. .dev_deploy(
  35. &std::fs::read("wormhole_stub.wasm").expect("Failed to find wormhole_stub.wasm"),
  36. )
  37. .await
  38. .expect("Failed to deploy wormhole_stub.wasm");
  39. // Initialize Wormhole.
  40. let _ = wormhole
  41. .call("new")
  42. .args_json(json!({}))
  43. .gas(Gas::from_gas(300_000_000_000_000))
  44. .transact_async()
  45. .await
  46. .expect("Failed to initialize Wormhole")
  47. .await
  48. .unwrap();
  49. // Initialize Pyth, one time operation that sets the Wormhole contract address.
  50. let codehash = [0u8; 32];
  51. let _ = contract
  52. .call("new")
  53. .args_json(json!({
  54. "wormhole": wormhole.id(),
  55. "codehash": codehash,
  56. "initial_source": Source {
  57. emitter: DEFAULT_DATA_SOURCE.address.0,
  58. chain: Chain::from(WormholeChain::from(u16::from(DEFAULT_DATA_SOURCE.chain))),
  59. },
  60. "gov_source": Source {
  61. emitter: DEFAULT_GOVERNANCE_SOURCE.address.0,
  62. chain: Chain::from(WormholeChain::from(u16::from(DEFAULT_GOVERNANCE_SOURCE.chain))),
  63. },
  64. "update_fee": U128::from(1u128),
  65. "stale_threshold": DEFAULT_VALID_TIME_PERIOD,
  66. }))
  67. .gas(Gas::from_gas(300_000_000_000_000))
  68. .transact_async()
  69. .await
  70. .expect("Failed to initialize Pyth")
  71. .await
  72. .unwrap();
  73. (worker, contract, wormhole)
  74. }
  75. #[tokio::test]
  76. async fn test_set_sources() {
  77. let (_, contract, _) = initialize_chain().await;
  78. // Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
  79. let vaa = create_vaa_from_payload(
  80. &GovernanceInstruction {
  81. target: Chain::from(WormholeChain::Any),
  82. module: GovernanceModule::Target,
  83. action: GovernanceAction::SetDataSources {
  84. data_sources: vec![
  85. Source {
  86. emitter: DEFAULT_DATA_SOURCE.address.0,
  87. chain: Chain::from(WormholeChain::from(u16::from(
  88. DEFAULT_DATA_SOURCE.chain,
  89. ))),
  90. },
  91. Source {
  92. emitter: SECONDARY_DATA_SOURCE.address.0,
  93. chain: Chain::from(WormholeChain::from(u16::from(
  94. SECONDARY_DATA_SOURCE.chain,
  95. ))),
  96. },
  97. ],
  98. },
  99. }
  100. .serialize()
  101. .unwrap(),
  102. DEFAULT_GOVERNANCE_SOURCE.address,
  103. DEFAULT_GOVERNANCE_SOURCE.chain,
  104. 1,
  105. );
  106. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  107. assert!(contract
  108. .call("execute_governance_instruction")
  109. .gas(Gas::from_gas(300_000_000_000_000))
  110. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  111. .args_json(json!({
  112. "vaa": vaa,
  113. }))
  114. .transact_async()
  115. .await
  116. .expect("Failed to submit VAA")
  117. .await
  118. .unwrap()
  119. .failures()
  120. .is_empty());
  121. // There should now be a two sources in the contract state.
  122. assert_eq!(
  123. serde_json::from_slice::<Vec<Source>>(&contract.view("get_sources").await.unwrap().result)
  124. .unwrap(),
  125. &[
  126. Source {
  127. emitter: DEFAULT_DATA_SOURCE.address.0,
  128. chain: Chain::from(WormholeChain::from(u16::from(DEFAULT_DATA_SOURCE.chain))),
  129. },
  130. Source {
  131. emitter: SECONDARY_DATA_SOURCE.address.0,
  132. chain: Chain::from(WormholeChain::from(u16::from(SECONDARY_DATA_SOURCE.chain))),
  133. },
  134. ]
  135. );
  136. }
  137. #[tokio::test]
  138. async fn test_set_governance_source() {
  139. let (_, contract, _) = initialize_chain().await;
  140. // Data Source Upgrades are submitted with an embedded VAA, generate that one here first
  141. // before we embed it.
  142. let request_vaa = create_vaa_from_payload(
  143. &GovernanceInstruction {
  144. target: Chain::from(WormholeChain::Near),
  145. module: GovernanceModule::Target,
  146. action: GovernanceAction::RequestGovernanceDataSourceTransfer {
  147. governance_data_source_index: 1,
  148. },
  149. }
  150. .serialize()
  151. .unwrap(),
  152. SECONDARY_GOVERNANCE_SOURCE.address,
  153. SECONDARY_GOVERNANCE_SOURCE.chain,
  154. 1,
  155. );
  156. // Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
  157. let vaa = create_vaa_from_payload(
  158. &GovernanceInstruction {
  159. target: Chain::from(WormholeChain::Near),
  160. module: GovernanceModule::Target,
  161. action: GovernanceAction::AuthorizeGovernanceDataSourceTransfer {
  162. claim_vaa: serde_wormhole::to_vec(&request_vaa).unwrap(),
  163. },
  164. }
  165. .serialize()
  166. .unwrap(),
  167. DEFAULT_GOVERNANCE_SOURCE.address,
  168. DEFAULT_GOVERNANCE_SOURCE.chain,
  169. 2,
  170. );
  171. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  172. assert!(contract
  173. .call("execute_governance_instruction")
  174. .gas(Gas::from_gas(300_000_000_000_000))
  175. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  176. .args_json(json!({
  177. "vaa": vaa,
  178. }))
  179. .transact()
  180. .await
  181. .expect("Failed to submit VAA")
  182. .unwrap()
  183. .failures()
  184. .is_empty());
  185. // An action from the new source should now be accepted.
  186. let vaa = create_vaa_from_payload(
  187. &GovernanceInstruction {
  188. target: Chain::from(WormholeChain::Near),
  189. module: GovernanceModule::Target,
  190. action: GovernanceAction::SetDataSources {
  191. data_sources: vec![
  192. Source {
  193. emitter: DEFAULT_DATA_SOURCE.address.0,
  194. chain: Chain::from(WormholeChain::from(u16::from(
  195. DEFAULT_DATA_SOURCE.chain,
  196. ))),
  197. },
  198. Source {
  199. emitter: SECONDARY_DATA_SOURCE.address.0,
  200. chain: Chain::from(WormholeChain::from(u16::from(
  201. SECONDARY_DATA_SOURCE.chain,
  202. ))),
  203. },
  204. ],
  205. },
  206. }
  207. .serialize()
  208. .unwrap(),
  209. SECONDARY_GOVERNANCE_SOURCE.address,
  210. SECONDARY_GOVERNANCE_SOURCE.chain,
  211. 2,
  212. );
  213. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  214. assert!(contract
  215. .call("execute_governance_instruction")
  216. .gas(Gas::from_gas(300_000_000_000_000))
  217. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  218. .args_json(json!({
  219. "vaa": vaa,
  220. }))
  221. .transact_async()
  222. .await
  223. .expect("Failed to submit VAA")
  224. .await
  225. .unwrap()
  226. .failures()
  227. .is_empty());
  228. // But not from the old source.
  229. let vaa = create_vaa_from_payload(
  230. &GovernanceInstruction {
  231. target: Chain::from(WormholeChain::Near),
  232. module: GovernanceModule::Target,
  233. action: GovernanceAction::SetDataSources {
  234. data_sources: vec![
  235. Source::default(),
  236. Source {
  237. emitter: DEFAULT_DATA_SOURCE.address.0,
  238. chain: Chain::from(WormholeChain::from(u16::from(
  239. DEFAULT_DATA_SOURCE.chain,
  240. ))),
  241. },
  242. Source {
  243. emitter: SECONDARY_DATA_SOURCE.address.0,
  244. chain: Chain::from(WormholeChain::from(u16::from(
  245. SECONDARY_DATA_SOURCE.chain,
  246. ))),
  247. },
  248. ],
  249. },
  250. }
  251. .serialize()
  252. .unwrap(),
  253. DEFAULT_GOVERNANCE_SOURCE.address,
  254. DEFAULT_GOVERNANCE_SOURCE.chain,
  255. 4,
  256. );
  257. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  258. assert!(contract
  259. .call("execute_governance_instruction")
  260. .gas(Gas::from_gas(300_000_000_000_000))
  261. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  262. .args_json(json!({
  263. "vaa": vaa,
  264. }))
  265. .transact_async()
  266. .await
  267. .expect("Failed to submit VAA")
  268. .await
  269. .unwrap()
  270. .outcome()
  271. .is_success());
  272. }
  273. #[tokio::test]
  274. async fn test_stale_threshold() {
  275. let (_, contract, _) = initialize_chain().await;
  276. // Get current UNIX timestamp and subtract a minute from it to place the price attestation in
  277. // the past. This should be accepted but untrusted.
  278. let now = std::time::SystemTime::now()
  279. .duration_since(std::time::UNIX_EPOCH)
  280. .expect("Failed to get UNIX timestamp")
  281. .as_secs()
  282. - (DEFAULT_VALID_TIME_PERIOD + 1);
  283. // Submit a Price Attestation to the contract.
  284. let vaa = create_vaa_from_payload(
  285. &BatchPriceAttestation {
  286. price_attestations: vec![PriceAttestation {
  287. product_id: Identifier::default(),
  288. price_id: Identifier::default(),
  289. price: 100,
  290. conf: 1,
  291. expo: 8,
  292. ema_price: 100,
  293. ema_conf: 1,
  294. status: PriceStatus::Trading,
  295. num_publishers: 8,
  296. max_num_publishers: 8,
  297. attestation_time: now.try_into().unwrap(),
  298. publish_time: now.try_into().unwrap(),
  299. prev_publish_time: now.try_into().unwrap(),
  300. prev_price: 100,
  301. prev_conf: 1,
  302. last_attested_publish_time: now.try_into().unwrap(),
  303. }],
  304. }
  305. .serialize()
  306. .unwrap(),
  307. DEFAULT_DATA_SOURCE.address,
  308. DEFAULT_DATA_SOURCE.chain,
  309. 1,
  310. );
  311. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  312. // Submit price. As there are no prices this should succeed despite being old.
  313. assert!(contract
  314. .call("update_price_feeds")
  315. .gas(Gas::from_gas(300_000_000_000_000))
  316. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  317. .args_json(json!({
  318. "data": vaa,
  319. }))
  320. .transact_async()
  321. .await
  322. .expect("Failed to submit VAA")
  323. .await
  324. .unwrap()
  325. .failures()
  326. .is_empty());
  327. // Despite succeeding, assert Price cannot be requested, 60 seconds in the past should be
  328. // considered stale. [tag:failed_price_check]
  329. assert_eq!(
  330. None,
  331. serde_json::from_slice::<Option<Price>>(
  332. &contract
  333. .view("get_price")
  334. .args_json(json!({ "price_identifier": PriceIdentifier([0; 32]) }))
  335. .await
  336. .unwrap()
  337. .result
  338. )
  339. .unwrap(),
  340. );
  341. assert_eq!(
  342. &None,
  343. serde_json::from_slice::<HashMap<PriceIdentifier, Option<Price>>>(
  344. &contract
  345. .view("list_prices")
  346. .args_json(json!({ "price_ids": vec![PriceIdentifier([0; 32])] }))
  347. .await
  348. .unwrap()
  349. .result
  350. )
  351. .unwrap()
  352. .get(&PriceIdentifier([0; 32]))
  353. .unwrap(),
  354. );
  355. // Submit another Price Attestation to the contract with an even older timestamp. Which
  356. // should now fail due to the existing newer price.
  357. let vaa = create_vaa_from_payload(
  358. &BatchPriceAttestation {
  359. price_attestations: vec![PriceAttestation {
  360. product_id: Identifier::default(),
  361. price_id: Identifier::default(),
  362. price: 1000,
  363. conf: 1,
  364. expo: 8,
  365. ema_price: 1000,
  366. ema_conf: 1,
  367. status: PriceStatus::Trading,
  368. num_publishers: 8,
  369. max_num_publishers: 8,
  370. attestation_time: (now - 1024).try_into().unwrap(),
  371. publish_time: (now - 1024).try_into().unwrap(),
  372. prev_publish_time: (now - 1024).try_into().unwrap(),
  373. prev_price: 90,
  374. prev_conf: 1,
  375. last_attested_publish_time: (now - 1024).try_into().unwrap(),
  376. }],
  377. }
  378. .serialize()
  379. .unwrap(),
  380. DEFAULT_DATA_SOURCE.address,
  381. DEFAULT_DATA_SOURCE.chain,
  382. 2,
  383. );
  384. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  385. // The update handler should now succeed even if price is old, but simply not update the price.
  386. assert!(contract
  387. .call("update_price_feeds")
  388. .gas(Gas::from_gas(300_000_000_000_000))
  389. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  390. .args_json(json!({
  391. "data": vaa,
  392. }))
  393. .transact_async()
  394. .await
  395. .expect("Failed to submit VAA")
  396. .await
  397. .unwrap()
  398. .failures()
  399. .is_empty());
  400. // The price however should _not_ have updated and if we check the unsafe stored price the
  401. // timestamp and price should be unchanged.
  402. assert_eq!(
  403. Price {
  404. price: 100.into(),
  405. conf: 1.into(),
  406. expo: 8,
  407. publish_time: now as i64,
  408. },
  409. serde_json::from_slice::<Price>(
  410. &contract
  411. .view("get_price_unsafe")
  412. .args_json(json!({ "price_identifier": PriceIdentifier([0; 32]) }))
  413. .await
  414. .unwrap()
  415. .result
  416. )
  417. .unwrap(),
  418. );
  419. // Now we extend the staleness threshold with a Governance VAA.
  420. let vaa = create_vaa_from_payload(
  421. &GovernanceInstruction {
  422. target: Chain::from(WormholeChain::Near),
  423. module: GovernanceModule::Target,
  424. action: GovernanceAction::SetValidPeriod { valid_seconds: 256 },
  425. }
  426. .serialize()
  427. .unwrap(),
  428. DEFAULT_GOVERNANCE_SOURCE.address,
  429. DEFAULT_GOVERNANCE_SOURCE.chain,
  430. 3,
  431. );
  432. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  433. assert!(contract
  434. .call("execute_governance_instruction")
  435. .gas(Gas::from_gas(300_000_000_000_000))
  436. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  437. .args_json(json!({
  438. "vaa": vaa,
  439. }))
  440. .transact_async()
  441. .await
  442. .expect("Failed to submit VAA")
  443. .await
  444. .unwrap()
  445. .failures()
  446. .is_empty());
  447. // It should now be possible to request the price that previously returned None.
  448. // [ref:failed_price_check]
  449. assert_eq!(
  450. Some(Price {
  451. price: 100.into(),
  452. conf: 1.into(),
  453. expo: 8,
  454. publish_time: now as i64,
  455. }),
  456. serde_json::from_slice::<Option<Price>>(
  457. &contract
  458. .view("get_price")
  459. .args_json(json!({ "price_identifier": PriceIdentifier([0; 32]) }))
  460. .await
  461. .unwrap()
  462. .result
  463. )
  464. .unwrap(),
  465. );
  466. assert_eq!(
  467. &Some(Price {
  468. price: 100.into(),
  469. conf: 1.into(),
  470. expo: 8,
  471. publish_time: now as i64,
  472. }),
  473. serde_json::from_slice::<HashMap<PriceIdentifier, Option<Price>>>(
  474. &contract
  475. .view("list_prices")
  476. .args_json(json!({ "price_ids": vec![PriceIdentifier([0; 32])] }))
  477. .await
  478. .unwrap()
  479. .result
  480. )
  481. .unwrap()
  482. .get(&PriceIdentifier([0; 32]))
  483. .unwrap(),
  484. );
  485. }
  486. #[tokio::test]
  487. async fn test_contract_fees() {
  488. let (_, contract, _) = initialize_chain().await;
  489. let now = std::time::SystemTime::now()
  490. .duration_since(std::time::UNIX_EPOCH)
  491. .expect("Failed to get UNIX timestamp")
  492. .as_secs();
  493. // Set a high fee for the contract needed to submit a price.
  494. let vaa = create_vaa_from_payload(
  495. &GovernanceInstruction {
  496. target: Chain::from(WormholeChain::Near),
  497. module: GovernanceModule::Target,
  498. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  499. }
  500. .serialize()
  501. .unwrap(),
  502. DEFAULT_GOVERNANCE_SOURCE.address,
  503. DEFAULT_GOVERNANCE_SOURCE.chain,
  504. 1,
  505. );
  506. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  507. // Fetch Update fee before changing it.
  508. let update_fee = serde_json::from_slice::<U128>(
  509. &contract
  510. .view("get_update_fee_estimate")
  511. .args_json(json!({
  512. "data": vaa,
  513. }))
  514. .await
  515. .unwrap()
  516. .result,
  517. )
  518. .unwrap();
  519. // Now set the update_fee so that it is too high for the deposit to cover.
  520. assert!(contract
  521. .call("execute_governance_instruction")
  522. .gas(Gas::from_gas(300_000_000_000_000))
  523. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  524. .args_json(json!({
  525. "vaa": vaa,
  526. }))
  527. .transact_async()
  528. .await
  529. .expect("Failed to submit VAA")
  530. .await
  531. .unwrap()
  532. .failures()
  533. .is_empty());
  534. // Check the state has actually changed before we try and execute another VAA.
  535. assert_ne!(
  536. u128::from(update_fee),
  537. u128::from(
  538. serde_json::from_slice::<U128>(
  539. &contract
  540. .view("get_update_fee_estimate")
  541. .args_json(json!({
  542. "data": vaa,
  543. }))
  544. .await
  545. .unwrap()
  546. .result,
  547. )
  548. .unwrap()
  549. )
  550. );
  551. // Attempt to update the price feed with a now too low deposit.
  552. let vaa = create_vaa_from_payload(
  553. &BatchPriceAttestation {
  554. price_attestations: vec![PriceAttestation {
  555. product_id: Identifier::default(),
  556. price_id: Identifier::default(),
  557. price: 1000,
  558. conf: 1,
  559. expo: 8,
  560. ema_price: 1000,
  561. ema_conf: 1,
  562. status: PriceStatus::Trading,
  563. num_publishers: 8,
  564. max_num_publishers: 8,
  565. attestation_time: (now - 1024).try_into().unwrap(),
  566. publish_time: (now - 1024).try_into().unwrap(),
  567. prev_publish_time: (now - 1024).try_into().unwrap(),
  568. prev_price: 90,
  569. prev_conf: 1,
  570. last_attested_publish_time: (now - 1024).try_into().unwrap(),
  571. }],
  572. }
  573. .serialize()
  574. .unwrap(),
  575. DEFAULT_DATA_SOURCE.address,
  576. DEFAULT_DATA_SOURCE.chain,
  577. 2,
  578. );
  579. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  580. assert!(contract
  581. .call("update_price_feeds")
  582. .gas(Gas::from_gas(300_000_000_000_000))
  583. .deposit(NearToken::from_yoctonear(update_fee.into()))
  584. .args_json(json!({
  585. "data": vaa,
  586. }))
  587. .transact_async()
  588. .await
  589. .expect("Failed to submit VAA")
  590. .await
  591. .unwrap()
  592. .failures()
  593. .is_empty());
  594. // Submitting a Price should have failed because the fee was not enough.
  595. assert_eq!(
  596. None,
  597. serde_json::from_slice::<Option<Price>>(
  598. &contract
  599. .view("get_price")
  600. .args_json(json!({ "price_identifier": PriceIdentifier([0; 32]) }))
  601. .await
  602. .unwrap()
  603. .result
  604. )
  605. .unwrap(),
  606. );
  607. }
  608. // A test that attempts to SetFee twice with the same governance action, the first should succeed,
  609. // the second should fail.
  610. #[tokio::test]
  611. async fn test_same_governance_sequence_fails() {
  612. let (_, contract, _) = initialize_chain().await;
  613. // Set a high fee for the contract needed to submit a price.
  614. let vaa = create_vaa_from_payload(
  615. &GovernanceInstruction {
  616. target: Chain::from(WormholeChain::Near),
  617. module: GovernanceModule::Target,
  618. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  619. }
  620. .serialize()
  621. .unwrap(),
  622. DEFAULT_GOVERNANCE_SOURCE.address,
  623. DEFAULT_GOVERNANCE_SOURCE.chain,
  624. 1,
  625. );
  626. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  627. // Attempt our first SetFee.
  628. assert!(contract
  629. .call("execute_governance_instruction")
  630. .gas(Gas::from_gas(300_000_000_000_000))
  631. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  632. .args_json(json!({
  633. "vaa": vaa,
  634. }))
  635. .transact_async()
  636. .await
  637. .expect("Failed to submit VAA")
  638. .await
  639. .unwrap()
  640. .failures()
  641. .is_empty());
  642. // Attempt to run the same VAA again.
  643. assert!(!contract
  644. .call("execute_governance_instruction")
  645. .gas(Gas::from_gas(300_000_000_000_000))
  646. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  647. .args_json(json!({
  648. "vaa": vaa,
  649. }))
  650. .transact_async()
  651. .await
  652. .expect("Failed to submit VAA")
  653. .await
  654. .unwrap()
  655. .failures()
  656. .is_empty());
  657. }
  658. // A test that attempts to SetFee twice with the same governance action, the first should succeed,
  659. // the second should fail.
  660. #[tokio::test]
  661. async fn test_out_of_order_sequences_fail() {
  662. let (_, contract, _) = initialize_chain().await;
  663. // Set a high fee for the contract needed to submit a price.
  664. let vaa = create_vaa_from_payload(
  665. &GovernanceInstruction {
  666. target: Chain::from(WormholeChain::Near),
  667. module: GovernanceModule::Target,
  668. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  669. }
  670. .serialize()
  671. .unwrap(),
  672. DEFAULT_GOVERNANCE_SOURCE.address,
  673. DEFAULT_GOVERNANCE_SOURCE.chain,
  674. 1,
  675. );
  676. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  677. // Attempt our first SetFee.
  678. assert!(contract
  679. .call("execute_governance_instruction")
  680. .gas(Gas::from_gas(300_000_000_000_000))
  681. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  682. .args_json(json!({
  683. "vaa": vaa,
  684. }))
  685. .transact_async()
  686. .await
  687. .expect("Failed to submit VAA")
  688. .await
  689. .unwrap()
  690. .failures()
  691. .is_empty());
  692. // Generate another VAA with sequence 3.
  693. let vaa = create_vaa_from_payload(
  694. &GovernanceInstruction {
  695. target: Chain::from(WormholeChain::Near),
  696. module: GovernanceModule::Target,
  697. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  698. }
  699. .serialize()
  700. .unwrap(),
  701. DEFAULT_GOVERNANCE_SOURCE.address,
  702. DEFAULT_GOVERNANCE_SOURCE.chain,
  703. 3,
  704. );
  705. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  706. // This should succeed.
  707. assert!(contract
  708. .call("execute_governance_instruction")
  709. .gas(Gas::from_gas(300_000_000_000_000))
  710. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  711. .args_json(json!({
  712. "vaa": vaa,
  713. }))
  714. .transact_async()
  715. .await
  716. .expect("Failed to submit VAA")
  717. .await
  718. .unwrap()
  719. .failures()
  720. .is_empty());
  721. // Generate another VAA with sequence 2.
  722. let vaa = create_vaa_from_payload(
  723. &GovernanceInstruction {
  724. target: Chain::from(WormholeChain::Near),
  725. module: GovernanceModule::Target,
  726. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  727. }
  728. .serialize()
  729. .unwrap(),
  730. DEFAULT_GOVERNANCE_SOURCE.address,
  731. DEFAULT_GOVERNANCE_SOURCE.chain,
  732. 2,
  733. );
  734. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  735. // This should fail due to being out of order.
  736. assert!(!contract
  737. .call("execute_governance_instruction")
  738. .gas(Gas::from_gas(300_000_000_000_000))
  739. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  740. .args_json(json!({
  741. "vaa": vaa,
  742. }))
  743. .transact_async()
  744. .await
  745. .expect("Failed to submit VAA")
  746. .await
  747. .unwrap()
  748. .failures()
  749. .is_empty());
  750. }
  751. // A test that fails if the governance action payload target is not NEAR.
  752. #[tokio::test]
  753. async fn test_governance_target_fails_if_not_near() {
  754. let (_, contract, _) = initialize_chain().await;
  755. let vaa = create_vaa_from_payload(
  756. &GovernanceInstruction {
  757. target: Chain::from(WormholeChain::Solana),
  758. module: GovernanceModule::Target,
  759. action: GovernanceAction::SetFee { base: 128, expo: 8 },
  760. }
  761. .serialize()
  762. .unwrap(),
  763. DEFAULT_GOVERNANCE_SOURCE.address,
  764. DEFAULT_GOVERNANCE_SOURCE.chain,
  765. 1,
  766. );
  767. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  768. // This should fail as the target is Solana, when Near is expected.
  769. assert!(!contract
  770. .call("execute_governance_instruction")
  771. .gas(Gas::from_gas(300_000_000_000_000))
  772. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  773. .args_json(json!({
  774. "vaa": vaa,
  775. }))
  776. .transact_async()
  777. .await
  778. .expect("Failed to submit VAA")
  779. .await
  780. .unwrap()
  781. .failures()
  782. .is_empty());
  783. }
  784. // A test to check accumulator style updates work as intended.
  785. #[tokio::test]
  786. async fn test_accumulator_updates() {
  787. let (_, contract, _) = initialize_chain().await;
  788. // Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
  789. let vaa = create_vaa_from_payload(
  790. &GovernanceInstruction {
  791. target: Chain::from(WormholeChain::Any),
  792. module: GovernanceModule::Target,
  793. action: GovernanceAction::SetDataSources {
  794. data_sources: vec![Source {
  795. emitter: DEFAULT_DATA_SOURCE.address.0,
  796. chain: Chain::from(WormholeChain::from(u16::from(DEFAULT_DATA_SOURCE.chain))),
  797. }],
  798. },
  799. }
  800. .serialize()
  801. .unwrap(),
  802. DEFAULT_GOVERNANCE_SOURCE.address,
  803. DEFAULT_GOVERNANCE_SOURCE.chain,
  804. 1,
  805. );
  806. let vaa = hex::encode(serde_wormhole::to_vec(&vaa).unwrap());
  807. assert!(contract
  808. .call("execute_governance_instruction")
  809. .gas(Gas::from_gas(300_000_000_000_000))
  810. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  811. .args_json(json!({
  812. "vaa": vaa,
  813. }))
  814. .transact_async()
  815. .await
  816. .expect("Failed to submit VAA")
  817. .await
  818. .unwrap()
  819. .failures()
  820. .is_empty());
  821. // Create a couple of test feeds.
  822. let feed_1 = create_dummy_price_feed_message(100);
  823. let feed_2 = create_dummy_price_feed_message(200);
  824. let message = create_accumulator_message(&[&feed_1, &feed_2], &[&feed_1], false, false, None);
  825. let message = hex::encode(message);
  826. // Call the usual UpdatePriceFeed function.
  827. assert!(contract
  828. .call("update_price_feeds")
  829. .gas(Gas::from_gas(300_000_000_000_000))
  830. .deposit(NearToken::from_yoctonear(300_000_000_000_000_000_000_000))
  831. .args_json(json!({
  832. "data": message,
  833. }))
  834. .transact_async()
  835. .await
  836. .expect("Failed to submit VAA")
  837. .await
  838. .unwrap()
  839. .failures()
  840. .is_empty());
  841. // Check the price feed actually updated. Check both types of serialized PriceIdentifier.
  842. let mut identifier = [0; 32];
  843. identifier[0] = 100;
  844. assert_eq!(
  845. Some(Price {
  846. price: 100.into(),
  847. conf: 100.into(),
  848. expo: 100,
  849. publish_time: 100,
  850. }),
  851. serde_json::from_slice::<Option<Price>>(
  852. &contract
  853. .view("get_price_unsafe")
  854. .args_json(json!({ "price_identifier": PriceIdentifier(identifier) }))
  855. .await
  856. .unwrap()
  857. .result
  858. )
  859. .unwrap(),
  860. );
  861. }
  862. #[tokio::test]
  863. async fn test_sdk_compat() {
  864. let price = pyth_sdk::Price {
  865. price: i64::MAX,
  866. conf: u64::MAX,
  867. expo: 100,
  868. publish_time: 100,
  869. };
  870. let encoded = serde_json::to_string(&price).unwrap();
  871. let decoded_price: Price = serde_json::from_str(&encoded).unwrap();
  872. assert_eq!(
  873. decoded_price,
  874. Price {
  875. price: i64::MAX.into(),
  876. conf: u64::MAX.into(),
  877. expo: 100,
  878. publish_time: 100,
  879. }
  880. );
  881. }
  882. #[tokio::test]
  883. async fn test_borsh_field_cmopat() {
  884. use near_sdk::borsh::{BorshDeserialize, BorshSerialize};
  885. let price = Price {
  886. price: I64(i64::MAX),
  887. conf: U64(u64::MAX),
  888. expo: 100,
  889. publish_time: 100,
  890. };
  891. // Verify that we can still BorshDeserialize a struct with a different field name. Confirms
  892. // we don't have to migrate the state.
  893. #[derive(Eq, PartialEq, Debug, BorshSerialize, BorshDeserialize)]
  894. #[borsh(crate = "near_sdk::borsh")]
  895. struct PriceTester {
  896. price: i64,
  897. conf: u64,
  898. expo: u32,
  899. bad_field_name: u64,
  900. }
  901. let encoded = near_sdk::borsh::to_vec(&price).unwrap();
  902. let decoded_price: PriceTester =
  903. near_sdk::borsh::BorshDeserialize::try_from_slice(&encoded).unwrap();
  904. assert_eq!(
  905. decoded_price,
  906. PriceTester {
  907. price: i64::MAX,
  908. conf: u64::MAX,
  909. expo: 100,
  910. bad_field_name: 100,
  911. }
  912. );
  913. }