pyth.cairo 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. use snforge_std::{
  2. declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, spy_events, SpyOn, EventSpy,
  3. EventFetcher, event_name_hash, Event, start_warp, stop_warp, ContractClass,
  4. };
  5. use pyth::pyth::{
  6. IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdated,
  7. WormholeAddressSet, GovernanceDataSourceSet, ContractUpgraded, DataSourcesSet, FeeSet,
  8. PriceFeedPublishTime, GetPriceNoOlderThanError, Price, PriceFeed, GetPriceUnsafeError,
  9. };
  10. use pyth::byte_buffer::{ByteBuffer, ByteBufferImpl};
  11. use pyth::util::{array_try_into, UnwrapWithFelt252};
  12. use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait};
  13. use core::starknet::ContractAddress;
  14. use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
  15. use super::wormhole::corrupted_vm;
  16. use super::data;
  17. #[generate_trait]
  18. impl DecodeEventHelpers of DecodeEventHelpersTrait {
  19. fn pop<T, +TryInto<felt252, T>>(ref self: Array<felt252>) -> T {
  20. self.pop_front().unwrap().try_into().unwrap()
  21. }
  22. fn pop_u256(ref self: Array<felt252>) -> u256 {
  23. u256 { low: self.pop(), high: self.pop(), }
  24. }
  25. fn pop_data_source(ref self: Array<felt252>) -> DataSource {
  26. DataSource { emitter_chain_id: self.pop(), emitter_address: self.pop_u256(), }
  27. }
  28. fn pop_data_sources(ref self: Array<felt252>) -> Array<DataSource> {
  29. let count: usize = self.pop();
  30. let mut i = 0;
  31. let mut output = array![];
  32. while i < count {
  33. output.append(self.pop_data_source());
  34. i += 1;
  35. };
  36. output
  37. }
  38. }
  39. fn decode_event(mut event: Event) -> PythEvent {
  40. let key0: felt252 = event.keys.pop();
  41. let output = if key0 == event_name_hash('PriceFeedUpdated') {
  42. let event = PriceFeedUpdated {
  43. price_id: event.keys.pop_u256(),
  44. price: event.data.pop(),
  45. conf: event.data.pop(),
  46. publish_time: event.data.pop(),
  47. };
  48. PythEvent::PriceFeedUpdated(event)
  49. } else if key0 == event_name_hash('FeeSet') {
  50. let event = FeeSet {
  51. old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), token: event.data.pop(),
  52. };
  53. PythEvent::FeeSet(event)
  54. } else if key0 == event_name_hash('DataSourcesSet') {
  55. let event = DataSourcesSet {
  56. old_data_sources: event.data.pop_data_sources(),
  57. new_data_sources: event.data.pop_data_sources(),
  58. };
  59. PythEvent::DataSourcesSet(event)
  60. } else if key0 == event_name_hash('WormholeAddressSet') {
  61. let event = WormholeAddressSet {
  62. old_address: event.data.pop(), new_address: event.data.pop(),
  63. };
  64. PythEvent::WormholeAddressSet(event)
  65. } else if key0 == event_name_hash('GovernanceDataSourceSet') {
  66. let event = GovernanceDataSourceSet {
  67. old_data_source: event.data.pop_data_source(),
  68. new_data_source: event.data.pop_data_source(),
  69. last_executed_governance_sequence: event.data.pop(),
  70. };
  71. PythEvent::GovernanceDataSourceSet(event)
  72. } else if key0 == event_name_hash('ContractUpgraded') {
  73. let event = ContractUpgraded { new_class_hash: event.data.pop() };
  74. PythEvent::ContractUpgraded(event)
  75. } else {
  76. panic!("unrecognized event")
  77. };
  78. assert!(event.keys.len() == 0);
  79. assert!(event.data.len() == 0);
  80. output
  81. }
  82. #[test]
  83. fn test_getters_work() {
  84. let ctx = deploy_mainnet();
  85. let pyth = ctx.pyth;
  86. assert!(pyth.wormhole_address() == ctx.wormhole.contract_address);
  87. assert!(
  88. pyth
  89. .fee_token_addresses() == array![
  90. ctx.fee_contract.contract_address, ctx.fee_contract2.contract_address,
  91. ]
  92. );
  93. assert!(pyth.get_single_update_fee(ctx.fee_contract.contract_address) == 1000);
  94. assert!(pyth.get_single_update_fee(ctx.fee_contract2.contract_address) == 2000);
  95. assert!(
  96. pyth
  97. .valid_data_sources() == array![
  98. DataSource {
  99. emitter_chain_id: 26,
  100. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  101. }
  102. ]
  103. );
  104. assert!(
  105. pyth
  106. .is_valid_data_source(
  107. DataSource {
  108. emitter_chain_id: 26,
  109. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  110. }
  111. )
  112. );
  113. assert!(
  114. !pyth.is_valid_data_source(DataSource { emitter_chain_id: 26, emitter_address: 0xbad, })
  115. );
  116. assert!(
  117. !pyth
  118. .is_valid_data_source(
  119. DataSource {
  120. emitter_chain_id: 27,
  121. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  122. }
  123. )
  124. );
  125. assert!(
  126. pyth.governance_data_source() == DataSource { emitter_chain_id: 1, emitter_address: 41, }
  127. );
  128. assert!(
  129. pyth
  130. .is_valid_governance_data_source(
  131. DataSource { emitter_chain_id: 1, emitter_address: 41, }
  132. )
  133. );
  134. assert!(
  135. !pyth
  136. .is_valid_governance_data_source(
  137. DataSource { emitter_chain_id: 1, emitter_address: 42, }
  138. )
  139. );
  140. assert!(pyth.last_executed_governance_sequence() == 0);
  141. assert!(pyth.governance_data_source_index() == 0);
  142. assert!(pyth.chain_id() == 60051);
  143. }
  144. #[test]
  145. fn update_price_feeds_works() {
  146. let ctx = deploy_mainnet();
  147. let pyth = ctx.pyth;
  148. assert!(
  149. !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  150. );
  151. assert!(
  152. pyth
  153. .latest_price_info_publish_time(
  154. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  155. ) == 0
  156. );
  157. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  158. assert!(fee == 1000);
  159. ctx.approve_fee(fee);
  160. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  161. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  162. pyth.update_price_feeds(data::good_update1());
  163. stop_prank(CheatTarget::One(pyth.contract_address));
  164. spy.fetch_events();
  165. assert!(spy.events.len() == 1);
  166. let (from, event) = spy.events.pop_front().unwrap();
  167. assert!(from == pyth.contract_address);
  168. let event = decode_event(event);
  169. let expected = PriceFeedUpdated {
  170. price_id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  171. publish_time: 1712589206,
  172. price: 7192002930010,
  173. conf: 3596501465,
  174. };
  175. assert!(event == PythEvent::PriceFeedUpdated(expected));
  176. let last_price = pyth
  177. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  178. .unwrap_with_felt252();
  179. assert!(last_price.price == 7192002930010);
  180. assert!(last_price.conf == 3596501465);
  181. assert!(last_price.expo == -8);
  182. assert!(last_price.publish_time == 1712589206);
  183. let last_ema_price = pyth
  184. .get_ema_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  185. .unwrap_with_felt252();
  186. assert!(last_ema_price.price == 7181868900000);
  187. assert!(last_ema_price.conf == 4096812700);
  188. assert!(last_ema_price.expo == -8);
  189. assert!(last_ema_price.publish_time == 1712589206);
  190. let feed = pyth
  191. .query_price_feed_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  192. .unwrap_with_felt252();
  193. assert!(feed.id == 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43);
  194. assert!(feed.price.price == 7192002930010);
  195. assert!(feed.price.conf == 3596501465);
  196. assert!(feed.price.expo == -8);
  197. assert!(feed.price.publish_time == 1712589206);
  198. assert!(feed.ema_price.price == 7181868900000);
  199. assert!(feed.ema_price.conf == 4096812700);
  200. assert!(feed.ema_price.expo == -8);
  201. assert!(feed.ema_price.publish_time == 1712589206);
  202. assert!(
  203. pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  204. );
  205. assert!(
  206. pyth
  207. .latest_price_info_publish_time(
  208. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  209. ) == 1712589206
  210. );
  211. }
  212. #[test]
  213. fn test_accepts_secondary_fee() {
  214. let ctx = deploy_mainnet();
  215. let pyth = ctx.pyth;
  216. assert!(
  217. !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  218. );
  219. assert!(
  220. pyth
  221. .latest_price_info_publish_time(
  222. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  223. ) == 0
  224. );
  225. let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
  226. assert!(fee2 == 2000);
  227. ctx.approve_fee2(fee2);
  228. let balance1 = ctx.fee_contract.balanceOf(ctx.user);
  229. let balance2 = ctx.fee_contract2.balanceOf(ctx.user);
  230. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  231. pyth.update_price_feeds(data::good_update1());
  232. stop_prank(CheatTarget::One(pyth.contract_address));
  233. assert!(balance1 - ctx.fee_contract.balanceOf(ctx.user) == 0);
  234. assert!(balance2 - ctx.fee_contract2.balanceOf(ctx.user) == 2000);
  235. }
  236. #[test]
  237. fn test_accepts_secondary_fee_if_first_allowance_insufficient() {
  238. let ctx = deploy_mainnet();
  239. let pyth = ctx.pyth;
  240. assert!(
  241. !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  242. );
  243. assert!(
  244. pyth
  245. .latest_price_info_publish_time(
  246. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  247. ) == 0
  248. );
  249. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  250. assert!(fee == 1000);
  251. let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
  252. assert!(fee2 == 2000);
  253. ctx.approve_fee(500);
  254. ctx.approve_fee2(fee2);
  255. let balance1 = ctx.fee_contract.balanceOf(ctx.user);
  256. let balance2 = ctx.fee_contract2.balanceOf(ctx.user);
  257. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  258. pyth.update_price_feeds(data::good_update1());
  259. stop_prank(CheatTarget::One(pyth.contract_address));
  260. assert!(balance1 - ctx.fee_contract.balanceOf(ctx.user) == 0);
  261. assert!(balance2 - ctx.fee_contract2.balanceOf(ctx.user) == 2000);
  262. }
  263. #[test]
  264. fn test_accepts_secondary_fee_if_first_balance_insufficient() {
  265. let ctx = deploy_mainnet();
  266. let pyth = ctx.pyth;
  267. let user2 = 'user2'.try_into().unwrap();
  268. start_prank(CheatTarget::One(ctx.fee_contract.contract_address), ctx.user);
  269. ctx.fee_contract.transfer(user2, 500);
  270. stop_prank(CheatTarget::One(ctx.fee_contract.contract_address));
  271. start_prank(CheatTarget::One(ctx.fee_contract2.contract_address), ctx.user);
  272. ctx.fee_contract2.transfer(user2, 2000);
  273. stop_prank(CheatTarget::One(ctx.fee_contract2.contract_address));
  274. assert!(
  275. !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  276. );
  277. assert!(
  278. pyth
  279. .latest_price_info_publish_time(
  280. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  281. ) == 0
  282. );
  283. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  284. assert!(fee == 1000);
  285. let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
  286. assert!(fee2 == 2000);
  287. start_prank(CheatTarget::One(ctx.fee_contract.contract_address), user2);
  288. ctx.fee_contract.approve(ctx.pyth.contract_address, fee);
  289. stop_prank(CheatTarget::One(ctx.fee_contract.contract_address));
  290. start_prank(CheatTarget::One(ctx.fee_contract2.contract_address), user2);
  291. ctx.fee_contract2.approve(ctx.pyth.contract_address, fee2);
  292. stop_prank(CheatTarget::One(ctx.fee_contract2.contract_address));
  293. let balance1 = ctx.fee_contract.balanceOf(user2);
  294. let balance2 = ctx.fee_contract2.balanceOf(user2);
  295. start_prank(CheatTarget::One(pyth.contract_address), user2.try_into().unwrap());
  296. pyth.update_price_feeds(data::good_update1());
  297. stop_prank(CheatTarget::One(pyth.contract_address));
  298. assert!(balance1 - ctx.fee_contract.balanceOf(user2) == 0);
  299. assert!(balance2 - ctx.fee_contract2.balanceOf(user2) == 2000);
  300. }
  301. #[test]
  302. #[should_panic(expected: ('insufficient fee allowance',))]
  303. fn test_rejects_if_both_fees_insufficient() {
  304. let ctx = deploy_mainnet();
  305. let pyth = ctx.pyth;
  306. assert!(
  307. !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  308. );
  309. assert!(
  310. pyth
  311. .latest_price_info_publish_time(
  312. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
  313. ) == 0
  314. );
  315. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  316. assert!(fee == 1000);
  317. let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
  318. assert!(fee2 == 2000);
  319. ctx.approve_fee(500);
  320. ctx.approve_fee2(1500);
  321. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  322. pyth.update_price_feeds(data::good_update1());
  323. stop_prank(CheatTarget::One(pyth.contract_address));
  324. }
  325. #[test]
  326. #[should_panic(expected: ('unsupported token',))]
  327. fn test_get_update_fee_rejects_unsupported_token() {
  328. let ctx = deploy_mainnet();
  329. let pyth = ctx.pyth;
  330. pyth.get_update_fee(data::good_update1(), ctx.pyth.contract_address);
  331. }
  332. #[test]
  333. #[should_panic(expected: ('unsupported token',))]
  334. fn test_get_single_update_fee_rejects_unsupported_token() {
  335. let ctx = deploy_mainnet();
  336. let pyth = ctx.pyth;
  337. pyth.get_single_update_fee(ctx.pyth.contract_address);
  338. }
  339. #[test]
  340. fn test_update_if_necessary_works() {
  341. let ctx = deploy_test();
  342. let pyth = ctx.pyth;
  343. ctx.approve_fee(10000);
  344. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  345. let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
  346. assert!(pyth.get_price_unsafe(price_id).is_err());
  347. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  348. let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
  349. pyth.update_price_feeds_if_necessary(data::test_price_update1(), times);
  350. let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
  351. assert!(last_price.price == 6281060000000);
  352. assert!(last_price.publish_time == 1715769470);
  353. spy.fetch_events();
  354. assert!(spy.events.len() == 1);
  355. let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769475 }];
  356. pyth.update_price_feeds_if_necessary(data::test_price_update2(), times);
  357. let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
  358. assert!(last_price.price == 6281522520745);
  359. assert!(last_price.publish_time == 1715769475);
  360. spy.fetch_events();
  361. assert!(spy.events.len() == 2);
  362. stop_prank(CheatTarget::One(pyth.contract_address));
  363. }
  364. #[test]
  365. fn test_parse_price_feed_updates_works() {
  366. let ctx = deploy_mainnet();
  367. let pyth = ctx.pyth;
  368. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  369. assert!(fee == 1000);
  370. ctx.approve_fee(1000);
  371. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  372. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  373. let output = pyth
  374. .parse_price_feed_updates(
  375. data::good_update1(),
  376. array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
  377. 0,
  378. 1712589208
  379. );
  380. stop_prank(CheatTarget::One(pyth.contract_address));
  381. assert!(output.len() == 1);
  382. let output = output.at(0).clone();
  383. let expected = PriceFeed {
  384. id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  385. price: Price {
  386. price: 7192002930010, conf: 3596501465, expo: -8, publish_time: 1712589206,
  387. },
  388. ema_price: Price {
  389. price: 7181868900000, conf: 4096812700, expo: -8, publish_time: 1712589206,
  390. },
  391. };
  392. assert!(output == expected);
  393. spy.fetch_events();
  394. assert!(spy.events.len() == 1);
  395. let (from, event) = spy.events.pop_front().unwrap();
  396. assert!(from == pyth.contract_address);
  397. let event = decode_event(event);
  398. let expected = PriceFeedUpdated {
  399. price_id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  400. publish_time: 1712589206,
  401. price: 7192002930010,
  402. conf: 3596501465,
  403. };
  404. assert!(event == PythEvent::PriceFeedUpdated(expected));
  405. let last_price = pyth
  406. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  407. .unwrap_with_felt252();
  408. assert!(last_price.price == 7192002930010);
  409. assert!(last_price.conf == 3596501465);
  410. assert!(last_price.expo == -8);
  411. assert!(last_price.publish_time == 1712589206);
  412. let last_ema_price = pyth
  413. .get_ema_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  414. .unwrap_with_felt252();
  415. assert!(last_ema_price.price == 7181868900000);
  416. assert!(last_ema_price.conf == 4096812700);
  417. assert!(last_ema_price.expo == -8);
  418. assert!(last_ema_price.publish_time == 1712589206);
  419. }
  420. #[test]
  421. #[should_panic(expected: ('price feed not found',))]
  422. fn test_parse_price_feed_updates_rejects_bad_price_id() {
  423. let ctx = deploy_mainnet();
  424. let pyth = ctx.pyth;
  425. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  426. assert!(fee == 1000);
  427. ctx.approve_fee(fee);
  428. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  429. pyth.parse_price_feed_updates(data::good_update1(), array![0x14], 0, 1712589208);
  430. }
  431. #[test]
  432. #[should_panic(expected: ('price feed not found',))]
  433. fn test_parse_price_feed_updates_rejects_out_of_range() {
  434. let ctx = deploy_mainnet();
  435. let pyth = ctx.pyth;
  436. let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
  437. assert!(fee == 1000);
  438. ctx.approve_fee(fee);
  439. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  440. pyth
  441. .parse_price_feed_updates(
  442. data::good_update1(),
  443. array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
  444. 0,
  445. 1712589000
  446. );
  447. }
  448. #[test]
  449. fn test_parse_price_feed_updates_unique_works() {
  450. let ctx = deploy_mainnet();
  451. let pyth = ctx.pyth;
  452. let fee1 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
  453. assert!(fee1 == 1000);
  454. ctx.approve_fee(10000);
  455. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  456. let output = pyth
  457. .parse_unique_price_feed_updates(
  458. data::unique_update1(),
  459. array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
  460. 1716904943,
  461. 2,
  462. );
  463. stop_prank(CheatTarget::One(pyth.contract_address));
  464. assert!(output.len() == 1);
  465. let output = output.at(0).clone();
  466. let expected = PriceFeed {
  467. id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  468. price: Price {
  469. price: 6751021151231, conf: 7471389383, expo: -8, publish_time: 1716904943,
  470. },
  471. ema_price: Price {
  472. price: 6815630100000, conf: 6236878200, expo: -8, publish_time: 1716904943,
  473. },
  474. };
  475. assert!(output == expected);
  476. }
  477. #[test]
  478. #[should_panic(expected: ('price feed not found',))]
  479. fn test_parse_price_feed_updates_unique_rejects_non_unique() {
  480. let ctx = deploy_mainnet();
  481. let pyth = ctx.pyth;
  482. let fee1 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
  483. assert!(fee1 == 1000);
  484. ctx.approve_fee(10000);
  485. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  486. pyth
  487. .parse_unique_price_feed_updates(
  488. data::good_update1(),
  489. array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
  490. 1712589206,
  491. 2,
  492. );
  493. }
  494. #[test]
  495. #[should_panic(expected: ('no fresh update',))]
  496. fn test_update_if_necessary_rejects_empty() {
  497. let ctx = deploy_test();
  498. let pyth = ctx.pyth;
  499. ctx.approve_fee(10000);
  500. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  501. pyth.update_price_feeds_if_necessary(data::test_price_update1(), array![]);
  502. stop_prank(CheatTarget::One(pyth.contract_address));
  503. }
  504. #[test]
  505. #[should_panic(expected: ('no fresh update',))]
  506. fn test_update_if_necessary_rejects_no_fresh() {
  507. let ctx = deploy_test();
  508. let pyth = ctx.pyth;
  509. ctx.approve_fee(10000);
  510. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  511. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  512. pyth.update_price_feeds_if_necessary(data::test_price_update1(), array![]);
  513. let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
  514. assert!(pyth.get_price_unsafe(price_id).is_err());
  515. spy.fetch_events();
  516. assert!(spy.events.len() == 0);
  517. let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
  518. pyth.update_price_feeds_if_necessary(data::test_price_update1(), times);
  519. let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
  520. assert!(last_price.price == 6281060000000);
  521. assert!(last_price.publish_time == 1715769470);
  522. spy.fetch_events();
  523. assert!(spy.events.len() == 1);
  524. let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
  525. pyth.update_price_feeds_if_necessary(data::test_price_update2(), times);
  526. }
  527. #[test]
  528. fn test_get_no_older_works() {
  529. let ctx = deploy_mainnet();
  530. let pyth = ctx.pyth;
  531. let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
  532. let err = pyth.get_price_unsafe(price_id).unwrap_err();
  533. assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
  534. let err = pyth.get_ema_price_unsafe(price_id).unwrap_err();
  535. assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
  536. let err = pyth.query_price_feed_unsafe(price_id).unwrap_err();
  537. assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
  538. let err = pyth.get_price_no_older_than(price_id, 100).unwrap_err();
  539. assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);
  540. let err = pyth.get_ema_price_no_older_than(price_id, 100).unwrap_err();
  541. assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);
  542. let err = pyth.query_price_feed_no_older_than(price_id, 100).unwrap_err();
  543. assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);
  544. ctx.approve_fee(10000);
  545. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  546. pyth.update_price_feeds(data::good_update1());
  547. stop_prank(CheatTarget::One(pyth.contract_address));
  548. start_warp(CheatTarget::One(pyth.contract_address), 1712589210);
  549. let err = pyth.get_price_no_older_than(price_id, 3).unwrap_err();
  550. assert!(err == GetPriceNoOlderThanError::StalePrice);
  551. let err = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_err();
  552. assert!(err == GetPriceNoOlderThanError::StalePrice);
  553. let err = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_err();
  554. assert!(err == GetPriceNoOlderThanError::StalePrice);
  555. start_warp(CheatTarget::One(pyth.contract_address), 1712589208);
  556. let val = pyth.get_price_no_older_than(price_id, 3).unwrap_with_felt252();
  557. assert!(val.publish_time == 1712589206);
  558. assert!(val.price == 7192002930010);
  559. let val = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_with_felt252();
  560. assert!(val.publish_time == 1712589206);
  561. assert!(val.price == 7181868900000);
  562. let val = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_with_felt252();
  563. assert!(val.price.publish_time == 1712589206);
  564. assert!(val.price.price == 7192002930010);
  565. start_warp(CheatTarget::One(pyth.contract_address), 1712589204);
  566. let val = pyth.get_price_no_older_than(price_id, 3).unwrap_with_felt252();
  567. assert!(val.publish_time == 1712589206);
  568. assert!(val.price == 7192002930010);
  569. let val = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_with_felt252();
  570. assert!(val.publish_time == 1712589206);
  571. assert!(val.price == 7181868900000);
  572. let val = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_with_felt252();
  573. assert!(val.price.publish_time == 1712589206);
  574. assert!(val.price.price == 7192002930010);
  575. stop_warp(CheatTarget::One(pyth.contract_address));
  576. }
  577. #[test]
  578. fn test_governance_set_fee_works() {
  579. let ctx = deploy_test();
  580. let pyth = ctx.pyth;
  581. let fee_contract = ctx.fee_contract;
  582. let user = ctx.user;
  583. let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
  584. assert!(fee1 == 1000);
  585. ctx.approve_fee(10000);
  586. let mut balance = fee_contract.balanceOf(user);
  587. start_prank(CheatTarget::One(pyth.contract_address), user);
  588. pyth.update_price_feeds(data::test_price_update1());
  589. stop_prank(CheatTarget::One(pyth.contract_address));
  590. let new_balance = fee_contract.balanceOf(user);
  591. assert!(balance - new_balance == 1000);
  592. balance = new_balance;
  593. let last_price = pyth
  594. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  595. .unwrap_with_felt252();
  596. assert!(last_price.price == 6281060000000);
  597. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  598. pyth.execute_governance_instruction(data::pyth_set_fee());
  599. spy.fetch_events();
  600. assert!(spy.events.len() == 1);
  601. let (from, event) = spy.events.pop_front().unwrap();
  602. assert!(from == pyth.contract_address);
  603. let event = decode_event(event);
  604. let expected = FeeSet {
  605. old_fee: 1000, new_fee: 4200, token: ctx.fee_contract.contract_address
  606. };
  607. assert!(event == PythEvent::FeeSet(expected));
  608. let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
  609. assert!(fee2 == 4200);
  610. let fee2_alt = pyth
  611. .get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
  612. assert!(fee2_alt == 2000);
  613. start_prank(CheatTarget::One(pyth.contract_address), user);
  614. pyth.update_price_feeds(data::test_price_update2());
  615. stop_prank(CheatTarget::One(pyth.contract_address));
  616. let new_balance = fee_contract.balanceOf(user);
  617. assert!(balance - new_balance == 4200);
  618. let last_price = pyth
  619. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  620. .unwrap_with_felt252();
  621. assert!(last_price.price == 6281522520745);
  622. }
  623. #[test]
  624. fn test_governance_set_fee_in_token_works() {
  625. let ctx = deploy_test();
  626. let pyth = ctx.pyth;
  627. let fee_contract = ctx.fee_contract;
  628. let user = ctx.user;
  629. let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
  630. assert!(fee1 == 1000);
  631. ctx.approve_fee(1000);
  632. let mut balance = fee_contract.balanceOf(user);
  633. start_prank(CheatTarget::One(pyth.contract_address), user);
  634. pyth.update_price_feeds(data::test_price_update1());
  635. stop_prank(CheatTarget::One(pyth.contract_address));
  636. let new_balance = fee_contract.balanceOf(user);
  637. assert!(balance - new_balance == 1000);
  638. balance = new_balance;
  639. let last_price = pyth
  640. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  641. .unwrap_with_felt252();
  642. assert!(last_price.price == 6281060000000);
  643. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  644. pyth.execute_governance_instruction(data::pyth_set_fee_in_token());
  645. spy.fetch_events();
  646. assert!(spy.events.len() == 1);
  647. let (from, event) = spy.events.pop_front().unwrap();
  648. assert!(from == pyth.contract_address);
  649. let event = decode_event(event);
  650. let expected = FeeSet {
  651. old_fee: 2000, new_fee: 4200, token: ctx.fee_contract2.contract_address
  652. };
  653. assert!(event == PythEvent::FeeSet(expected));
  654. let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
  655. assert!(fee2 == 1000);
  656. let fee2_alt = pyth
  657. .get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
  658. assert!(fee2_alt == 4200);
  659. ctx.approve_fee2(4200);
  660. let balance2 = ctx.fee_contract2.balanceOf(user);
  661. start_prank(CheatTarget::One(pyth.contract_address), user);
  662. pyth.update_price_feeds(data::test_price_update2());
  663. stop_prank(CheatTarget::One(pyth.contract_address));
  664. let new_balance2 = ctx.fee_contract2.balanceOf(user);
  665. assert!(balance2 - new_balance2 == 4200);
  666. let last_price = pyth
  667. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  668. .unwrap_with_felt252();
  669. assert!(last_price.price == 6281522520745);
  670. }
  671. #[test]
  672. #[fuzzer(runs: 100, seed: 0)]
  673. #[should_panic]
  674. fn test_rejects_corrupted_governance_instruction(pos: usize, random1: usize, random2: usize) {
  675. let ctx = deploy_test();
  676. let pyth = ctx.pyth;
  677. let input = corrupted_vm(data::pyth_set_fee(), pos, random1, random2);
  678. pyth.execute_governance_instruction(input);
  679. }
  680. #[test]
  681. fn test_governance_set_data_sources_works() {
  682. let ctx = deploy_test();
  683. let pyth = ctx.pyth;
  684. ctx.approve_fee(10000);
  685. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  686. pyth.update_price_feeds(data::test_price_update1());
  687. stop_prank(CheatTarget::One(pyth.contract_address));
  688. let last_price = pyth
  689. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  690. .unwrap_with_felt252();
  691. assert!(last_price.price == 6281060000000);
  692. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  693. pyth.execute_governance_instruction(data::pyth_set_data_sources());
  694. spy.fetch_events();
  695. assert!(spy.events.len() == 1);
  696. let (from, event) = spy.events.pop_front().unwrap();
  697. assert!(from == pyth.contract_address);
  698. let event = decode_event(event);
  699. let expected = DataSourcesSet {
  700. old_data_sources: array![
  701. DataSource {
  702. emitter_chain_id: 26,
  703. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  704. }
  705. ],
  706. new_data_sources: array![
  707. DataSource {
  708. emitter_chain_id: 1,
  709. emitter_address: 0x6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25,
  710. },
  711. DataSource { emitter_chain_id: 3, emitter_address: 0x12d, },
  712. ],
  713. };
  714. assert!(event == PythEvent::DataSourcesSet(expected));
  715. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  716. pyth.update_price_feeds(data::test_update2_alt_emitter());
  717. stop_prank(CheatTarget::One(pyth.contract_address));
  718. let last_price = pyth
  719. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  720. .unwrap_with_felt252();
  721. assert!(last_price.price == 6281522520745);
  722. }
  723. #[test]
  724. #[should_panic(expected: ('invalid update data source',))]
  725. fn test_rejects_update_after_data_source_changed() {
  726. let ctx = deploy_test();
  727. let pyth = ctx.pyth;
  728. ctx.approve_fee(10000);
  729. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  730. pyth.update_price_feeds(data::test_price_update1());
  731. stop_prank(CheatTarget::One(pyth.contract_address));
  732. let last_price = pyth
  733. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  734. .unwrap_with_felt252();
  735. assert!(last_price.price == 6281060000000);
  736. pyth.execute_governance_instruction(data::pyth_set_data_sources());
  737. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  738. pyth.update_price_feeds(data::test_price_update2());
  739. stop_prank(CheatTarget::One(pyth.contract_address));
  740. let last_price = pyth
  741. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  742. .unwrap_with_felt252();
  743. assert!(last_price.price == 6281522520745);
  744. }
  745. #[test]
  746. fn test_governance_set_wormhole_works() {
  747. let wormhole_class = declare("wormhole");
  748. // Arbitrary
  749. let wormhole_address = 0x42.try_into().unwrap();
  750. let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
  751. @wormhole_class, wormhole_address
  752. );
  753. let user = 'user'.try_into().unwrap();
  754. let fee_class = declare("ERC20");
  755. let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
  756. let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
  757. let pyth = deploy_pyth_default(
  758. wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
  759. );
  760. start_prank(CheatTarget::One(fee_contract.contract_address), user);
  761. fee_contract.approve(pyth.contract_address, 10000);
  762. stop_prank(CheatTarget::One(fee_contract.contract_address));
  763. start_prank(CheatTarget::One(pyth.contract_address), user);
  764. pyth.update_price_feeds(data::test_price_update1());
  765. stop_prank(CheatTarget::One(pyth.contract_address));
  766. let last_price = pyth
  767. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  768. .unwrap_with_felt252();
  769. assert!(last_price.price == 6281060000000);
  770. // Address used in the governance instruction
  771. let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
  772. .try_into()
  773. .unwrap();
  774. let wormhole2 = super::wormhole::deploy_declared_with_test_guardian_at(
  775. @wormhole_class, wormhole2_address
  776. );
  777. wormhole2.submit_new_guardian_set(data::upgrade_to_test2());
  778. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  779. pyth.execute_governance_instruction(data::pyth_set_wormhole());
  780. spy.fetch_events();
  781. assert!(spy.events.len() == 1);
  782. let (from, event) = spy.events.pop_front().unwrap();
  783. assert!(from == pyth.contract_address);
  784. let event = decode_event(event);
  785. let expected = WormholeAddressSet {
  786. old_address: wormhole_address, new_address: wormhole2_address,
  787. };
  788. assert!(event == PythEvent::WormholeAddressSet(expected));
  789. start_prank(CheatTarget::One(pyth.contract_address), user);
  790. pyth.update_price_feeds(data::test_update2_set2());
  791. stop_prank(CheatTarget::One(pyth.contract_address));
  792. let last_price = pyth
  793. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  794. .unwrap_with_felt252();
  795. assert!(last_price.price == 6281522520745);
  796. }
  797. #[test]
  798. #[should_panic(expected: ('invalid guardian set index',))]
  799. fn test_rejects_price_update_without_setting_wormhole() {
  800. let ctx = deploy_test();
  801. let pyth = ctx.pyth;
  802. ctx.approve_fee(10000);
  803. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  804. pyth.update_price_feeds(data::test_price_update1());
  805. stop_prank(CheatTarget::One(pyth.contract_address));
  806. let last_price = pyth
  807. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  808. .unwrap_with_felt252();
  809. assert!(last_price.price == 6281060000000);
  810. start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
  811. pyth.update_price_feeds(data::test_update2_set2());
  812. }
  813. // This test doesn't pass because of an snforge bug.
  814. // See https://github.com/foundry-rs/starknet-foundry/issues/2096
  815. // TODO: update snforge and unignore when the next release is available
  816. #[test]
  817. #[should_panic]
  818. #[ignore]
  819. fn test_rejects_set_wormhole_without_deploying() {
  820. let wormhole_class = declare("wormhole");
  821. // Arbitrary
  822. let wormhole_address = 0x42.try_into().unwrap();
  823. let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
  824. @wormhole_class, wormhole_address
  825. );
  826. let user = 'user'.try_into().unwrap();
  827. let fee_class = declare("ERC20");
  828. let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
  829. let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
  830. let pyth = deploy_pyth_default(
  831. wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
  832. );
  833. pyth.execute_governance_instruction(data::pyth_set_wormhole());
  834. }
  835. #[test]
  836. #[should_panic(expected: ('Invalid signature',))]
  837. fn test_rejects_set_wormhole_with_incompatible_guardians() {
  838. let wormhole_class = declare("wormhole");
  839. // Arbitrary
  840. let wormhole_address = 0x42.try_into().unwrap();
  841. let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
  842. @wormhole_class, wormhole_address
  843. );
  844. let user = 'user'.try_into().unwrap();
  845. let fee_class = declare("ERC20");
  846. let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
  847. let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
  848. let pyth = deploy_pyth_default(
  849. wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
  850. );
  851. // Address used in the governance instruction
  852. let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
  853. .try_into()
  854. .unwrap();
  855. super::wormhole::deploy_declared_at(
  856. @wormhole_class,
  857. 0,
  858. array_try_into(array![0x301]),
  859. super::wormhole::CHAIN_ID,
  860. super::wormhole::GOVERNANCE_CHAIN_ID,
  861. super::wormhole::GOVERNANCE_CONTRACT,
  862. Option::Some(wormhole2_address),
  863. );
  864. pyth.execute_governance_instruction(data::pyth_set_wormhole());
  865. }
  866. #[test]
  867. fn test_governance_transfer_works() {
  868. let ctx = deploy_test();
  869. let pyth = ctx.pyth;
  870. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  871. pyth.execute_governance_instruction(data::pyth_auth_transfer());
  872. spy.fetch_events();
  873. assert!(spy.events.len() == 1);
  874. let (from, event) = spy.events.pop_front().unwrap();
  875. assert!(from == pyth.contract_address);
  876. let event = decode_event(event);
  877. let expected = GovernanceDataSourceSet {
  878. old_data_source: DataSource { emitter_chain_id: 1, emitter_address: 41, },
  879. new_data_source: DataSource { emitter_chain_id: 2, emitter_address: 43, },
  880. last_executed_governance_sequence: 1,
  881. };
  882. assert!(event == PythEvent::GovernanceDataSourceSet(expected));
  883. pyth.execute_governance_instruction(data::pyth_set_fee_alt_emitter());
  884. }
  885. #[test]
  886. #[should_panic(expected: ('invalid governance data source',))]
  887. fn test_set_fee_rejects_wrong_emitter() {
  888. let ctx = deploy_test();
  889. let pyth = ctx.pyth;
  890. pyth.execute_governance_instruction(data::pyth_set_fee_alt_emitter());
  891. }
  892. #[test]
  893. #[should_panic(expected: ('invalid governance data source',))]
  894. fn test_rejects_old_emitter_after_transfer() {
  895. let ctx = deploy_test();
  896. let pyth = ctx.pyth;
  897. pyth.execute_governance_instruction(data::pyth_auth_transfer());
  898. pyth.execute_governance_instruction(data::pyth_set_fee());
  899. }
  900. #[test]
  901. fn test_upgrade_works() {
  902. let ctx = deploy_test();
  903. let pyth = ctx.pyth;
  904. let class = declare("pyth_fake_upgrade1");
  905. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  906. pyth.execute_governance_instruction(data::pyth_upgrade_fake1());
  907. spy.fetch_events();
  908. assert!(spy.events.len() == 1);
  909. let (from, event) = spy.events.pop_front().unwrap();
  910. assert!(from == pyth.contract_address);
  911. let event = decode_event(event);
  912. let expected = ContractUpgraded { new_class_hash: class.class_hash };
  913. assert!(event == PythEvent::ContractUpgraded(expected));
  914. let last_price = pyth.get_price_unsafe(1234).unwrap_with_felt252();
  915. assert!(last_price.price == 42);
  916. }
  917. #[test]
  918. #[should_panic]
  919. #[ignore] // TODO: unignore when snforge is updated
  920. fn test_upgrade_rejects_invalid_hash() {
  921. let ctx = deploy_test();
  922. let pyth = ctx.pyth;
  923. pyth.execute_governance_instruction(data::pyth_upgrade_invalid_hash());
  924. }
  925. #[test]
  926. #[should_panic]
  927. #[ignore] // TODO: unignore when snforge is updated
  928. fn test_upgrade_rejects_not_pyth() {
  929. let ctx = deploy_test();
  930. let pyth = ctx.pyth;
  931. declare("pyth_fake_upgrade_not_pyth");
  932. pyth.execute_governance_instruction(data::pyth_upgrade_not_pyth());
  933. }
  934. #[test]
  935. #[should_panic(expected: ('invalid governance message',))]
  936. fn test_upgrade_rejects_wrong_magic() {
  937. let ctx = deploy_test();
  938. let pyth = ctx.pyth;
  939. declare("pyth_fake_upgrade_wrong_magic");
  940. pyth.execute_governance_instruction(data::pyth_upgrade_wrong_magic());
  941. }
  942. #[test]
  943. #[should_panic(expected: ('invalid guardian set index',))]
  944. fn update_price_feeds_with_set3_rejects_on_guardian_set4() {
  945. let wormhole = super::wormhole::deploy_with_mainnet_guardian_set4();
  946. let ctx = deploy_with_wormhole(wormhole);
  947. let pyth = ctx.pyth;
  948. ctx.approve_fee(1000);
  949. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  950. pyth.update_price_feeds(data::good_update1());
  951. stop_prank(CheatTarget::One(pyth.contract_address));
  952. }
  953. #[test]
  954. fn update_price_feeds_works_with_guardian_set4() {
  955. let wormhole = super::wormhole::deploy_with_mainnet_guardian_set4();
  956. let ctx = deploy_with_wormhole(wormhole);
  957. let pyth = ctx.pyth;
  958. ctx.approve_fee(1000);
  959. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  960. pyth.update_price_feeds(data::unique_update1());
  961. stop_prank(CheatTarget::One(pyth.contract_address));
  962. }
  963. #[test]
  964. fn update_price_feeds_works_with_guardian_sets_3_4() {
  965. let wormhole = super::wormhole::deploy_with_mainnet_guardian_sets_3_4();
  966. let ctx = deploy_with_wormhole(wormhole);
  967. let pyth = ctx.pyth;
  968. ctx.approve_fee(2000);
  969. start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
  970. pyth.update_price_feeds(data::good_update1());
  971. pyth.update_price_feeds(data::unique_update1());
  972. stop_prank(CheatTarget::One(pyth.contract_address));
  973. }
  974. #[derive(Drop, Copy)]
  975. struct Context {
  976. user: ContractAddress,
  977. wormhole: IWormholeDispatcher,
  978. fee_contract: IERC20CamelDispatcher,
  979. fee_contract2: IERC20CamelDispatcher,
  980. pyth: IPythDispatcher,
  981. }
  982. #[generate_trait]
  983. impl ContextImpl of ContextTrait {
  984. fn approve_fee(self: Context, amount: u256) {
  985. start_prank(CheatTarget::One(self.fee_contract.contract_address), self.user);
  986. self.fee_contract.approve(self.pyth.contract_address, amount);
  987. stop_prank(CheatTarget::One(self.fee_contract.contract_address));
  988. }
  989. fn approve_fee2(self: Context, amount: u256) {
  990. start_prank(CheatTarget::One(self.fee_contract2.contract_address), self.user);
  991. self.fee_contract2.approve(self.pyth.contract_address, amount);
  992. stop_prank(CheatTarget::One(self.fee_contract2.contract_address));
  993. }
  994. }
  995. fn deploy_test() -> Context {
  996. deploy_with_wormhole(super::wormhole::deploy_with_test_guardian())
  997. }
  998. fn deploy_mainnet() -> Context {
  999. deploy_with_wormhole(super::wormhole::deploy_with_mainnet_guardians())
  1000. }
  1001. fn deploy_with_wormhole(wormhole: IWormholeDispatcher) -> Context {
  1002. let user = 'user'.try_into().unwrap();
  1003. let fee_class = declare("ERC20");
  1004. let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
  1005. let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
  1006. let pyth = deploy_pyth_default(
  1007. wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
  1008. );
  1009. Context { user, wormhole, fee_contract, fee_contract2, pyth }
  1010. }
  1011. fn deploy_pyth_default(
  1012. wormhole_address: ContractAddress,
  1013. fee_token_address1: ContractAddress,
  1014. fee_token_address2: ContractAddress
  1015. ) -> IPythDispatcher {
  1016. deploy_pyth(
  1017. wormhole_address,
  1018. fee_token_address1,
  1019. 1000,
  1020. fee_token_address2,
  1021. 2000,
  1022. array![
  1023. DataSource {
  1024. emitter_chain_id: 26,
  1025. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  1026. }
  1027. ],
  1028. 1,
  1029. 41,
  1030. 0,
  1031. )
  1032. }
  1033. fn deploy_pyth(
  1034. wormhole_address: ContractAddress,
  1035. fee_token_address1: ContractAddress,
  1036. single_update_fee1: u256,
  1037. fee_token_address2: ContractAddress,
  1038. single_update_fee2: u256,
  1039. data_sources: Array<DataSource>,
  1040. governance_emitter_chain_id: u16,
  1041. governance_emitter_address: u256,
  1042. governance_initial_sequence: u64,
  1043. ) -> IPythDispatcher {
  1044. let mut args = array![];
  1045. (wormhole_address, fee_token_address1, single_update_fee1).serialize(ref args);
  1046. (fee_token_address2, single_update_fee2).serialize(ref args);
  1047. (data_sources, governance_emitter_chain_id).serialize(ref args);
  1048. (governance_emitter_address, governance_initial_sequence).serialize(ref args);
  1049. let contract = declare("pyth");
  1050. let contract_address = match contract.deploy(@args) {
  1051. Result::Ok(v) => { v },
  1052. Result::Err(err) => { panic(err.panic_data) },
  1053. };
  1054. IPythDispatcher { contract_address }
  1055. }
  1056. fn fee_address1() -> ContractAddress {
  1057. 0x1010.try_into().unwrap()
  1058. }
  1059. fn fee_address2() -> ContractAddress {
  1060. 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7.try_into().unwrap()
  1061. }
  1062. fn deploy_fee_contract(
  1063. class: ContractClass, at: ContractAddress, recipient: ContractAddress
  1064. ) -> IERC20CamelDispatcher {
  1065. let mut args = array![];
  1066. let name: ByteArray = "eth";
  1067. let symbol: ByteArray = "eth";
  1068. (name, symbol, 100000_u256, recipient).serialize(ref args);
  1069. let contract_address = match class.deploy_at(@args, at) {
  1070. Result::Ok(v) => { v },
  1071. Result::Err(err) => { panic(err.panic_data) },
  1072. };
  1073. IERC20CamelDispatcher { contract_address }
  1074. }