pyth.cairo 45 KB

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