pyth.cairo 46 KB

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