pyth.cairo 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. use snforge_std::{
  2. declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, spy_events, SpyOn, EventSpy,
  3. EventFetcher, event_name_hash, Event
  4. };
  5. use pyth::pyth::{
  6. IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdateEvent
  7. };
  8. use pyth::byte_array::{ByteArray, ByteArrayImpl};
  9. use pyth::util::{array_try_into, UnwrapWithFelt252};
  10. use core::starknet::ContractAddress;
  11. use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
  12. use super::wormhole::corrupted_vm;
  13. fn decode_event(event: @Event) -> PythEvent {
  14. if *event.keys.at(0) == event_name_hash('PriceFeedUpdate') {
  15. assert!(event.keys.len() == 3);
  16. assert!(event.data.len() == 3);
  17. let event = PriceFeedUpdateEvent {
  18. price_id: u256 {
  19. low: (*event.keys.at(1)).try_into().unwrap(),
  20. high: (*event.keys.at(2)).try_into().unwrap(),
  21. },
  22. publish_time: (*event.data.at(0)).try_into().unwrap(),
  23. price: (*event.data.at(1)).try_into().unwrap(),
  24. conf: (*event.data.at(2)).try_into().unwrap(),
  25. };
  26. PythEvent::PriceFeedUpdate(event)
  27. } else {
  28. panic!("unrecognized event")
  29. }
  30. }
  31. #[test]
  32. fn update_price_feeds_works() {
  33. let owner = 'owner'.try_into().unwrap();
  34. let user = 'user'.try_into().unwrap();
  35. let wormhole = super::wormhole::deploy_with_mainnet_guardians();
  36. let fee_contract = deploy_fee_contract(user);
  37. let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
  38. start_prank(CheatTarget::One(fee_contract.contract_address), user.try_into().unwrap());
  39. fee_contract.approve(pyth.contract_address, 10000);
  40. stop_prank(CheatTarget::One(fee_contract.contract_address));
  41. let mut spy = spy_events(SpyOn::One(pyth.contract_address));
  42. start_prank(CheatTarget::One(pyth.contract_address), user.try_into().unwrap());
  43. pyth.update_price_feeds(good_update1());
  44. stop_prank(CheatTarget::One(pyth.contract_address));
  45. spy.fetch_events();
  46. assert!(spy.events.len() == 1);
  47. let (from, event) = spy.events.at(0);
  48. assert!(from == @pyth.contract_address);
  49. let event = decode_event(event);
  50. let expected = PriceFeedUpdateEvent {
  51. price_id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  52. publish_time: 1712589206,
  53. price: 7192002930010,
  54. conf: 3596501465,
  55. };
  56. assert!(event == PythEvent::PriceFeedUpdate(expected));
  57. let last_price = pyth
  58. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  59. .unwrap_with_felt252();
  60. assert!(last_price.price == 7192002930010);
  61. assert!(last_price.conf == 3596501465);
  62. assert!(last_price.expo == -8);
  63. assert!(last_price.publish_time == 1712589206);
  64. let last_ema_price = pyth
  65. .get_ema_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  66. .unwrap_with_felt252();
  67. assert!(last_ema_price.price == 7181868900000);
  68. assert!(last_ema_price.conf == 4096812700);
  69. assert!(last_ema_price.expo == -8);
  70. assert!(last_ema_price.publish_time == 1712589206);
  71. }
  72. #[test]
  73. fn test_governance_set_fee_works() {
  74. let owner = 'owner'.try_into().unwrap();
  75. let user = 'user'.try_into().unwrap();
  76. let wormhole = super::wormhole::deploy_with_test_guardian();
  77. let fee_contract = deploy_fee_contract(user);
  78. let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
  79. start_prank(CheatTarget::One(fee_contract.contract_address), user);
  80. fee_contract.approve(pyth.contract_address, 10000);
  81. stop_prank(CheatTarget::One(fee_contract.contract_address));
  82. let mut balance = fee_contract.balanceOf(user);
  83. start_prank(CheatTarget::One(pyth.contract_address), user);
  84. pyth
  85. .update_price_feeds(
  86. ByteArrayImpl::new(
  87. array_try_into(
  88. array![
  89. 141887862745809943100421399774809552050876420277163116849842965275903806689,
  90. 210740906737592158039211995620336526131859667363627655742687286503264782608,
  91. 437230063624699337579360546580839669896712252828825008570863758867641146081,
  92. 3498691308882995183871222184377409432186747119716981166996399082193594993,
  93. 1390200166945919815453709407753165121175395927094647129599868236,
  94. 222819573728193325268644030206737371345667885599602384508424089704440116301,
  95. 341318259000017461738706238280879290398059773267212529438772847337449455616,
  96. 1275126645346645395843037504005879519843596923369759718556759844520336145,
  97. 363528783578153760894082184744116718493621815898909809604883433584616420886,
  98. 301537311768214106147206781423041990995720118715322906821301413003463484347,
  99. 83150006264761451992768264969047148434524798781124754530141755679159432208,
  100. 96387772316726941183358990094337324283641753573556594738287498821253761827,
  101. 395908154570808692326126405856049827157095768069251211022053821585519235652,
  102. 87135893730137265929093180553063146337041045646221968026289709394440932141,
  103. 245333243912241114598596888050489286502591033459250287888834,
  104. ]
  105. ),
  106. 25
  107. )
  108. );
  109. stop_prank(CheatTarget::One(pyth.contract_address));
  110. let new_balance = fee_contract.balanceOf(user);
  111. assert!(balance - new_balance == 1000);
  112. balance = new_balance;
  113. let last_price = pyth
  114. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  115. .unwrap_with_felt252();
  116. assert!(last_price.price == 6281060000000);
  117. pyth.execute_governance_instruction(governance_set_fee());
  118. start_prank(CheatTarget::One(pyth.contract_address), user);
  119. pyth
  120. .update_price_feeds(
  121. ByteArrayImpl::new(
  122. array_try_into(
  123. array![
  124. 141887862745809943100421399774809552050874823427618844548942380383465221086,
  125. 106893583704677921907497845070624642590618427233243792006390965895909696183,
  126. 126617671723931969110123875642449115250793288301361049879364132884271078113,
  127. 3498691308882995183871222184377409432186747119716981166996399082193594993,
  128. 1390200461185063661704370212555794334034815850290352693418762308,
  129. 419598057710749587537080281518289024699150505326900462079484531390510117965,
  130. 341318259000017461738706238280879290398059773267212529438780607147892801536,
  131. 1437437604754599821041091415535991441313586347841485651963630208563420739,
  132. 305222830440467078008666830004555943609735125691441831219591213494068931362,
  133. 358396406696718360717615797531477055540194104082154743994717297650279402646,
  134. 429270385827211102844129651648706540139690432947840438198166022904666187018,
  135. 343946166212648899477337159288779715507980257611242783073384876024451565860,
  136. 67853010773876862913176476530730880916439012004585961528150130218675908823,
  137. 370855179649505412564259994413632062925303311800103998016489412083011059699,
  138. 1182295126766215829784496273374889928477877265080355104888778,
  139. ]
  140. ),
  141. 25
  142. )
  143. );
  144. stop_prank(CheatTarget::One(pyth.contract_address));
  145. let new_balance = fee_contract.balanceOf(user);
  146. assert!(balance - new_balance == 4200);
  147. let last_price = pyth
  148. .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
  149. .unwrap_with_felt252();
  150. assert!(last_price.price == 6281522520745);
  151. }
  152. #[test]
  153. #[fuzzer(runs: 100, seed: 0)]
  154. #[should_panic]
  155. fn test_rejects_corrupted_governance_instruction(pos: usize, random1: usize, random2: usize) {
  156. let owner = 'owner'.try_into().unwrap();
  157. let user = 'user'.try_into().unwrap();
  158. let wormhole = super::wormhole::deploy_with_test_guardian();
  159. let fee_contract = deploy_fee_contract(user);
  160. let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
  161. let input = corrupted_vm(governance_set_fee(), pos, random1, random2);
  162. pyth.execute_governance_instruction(input);
  163. }
  164. fn deploy_default(
  165. owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress
  166. ) -> IPythDispatcher {
  167. deploy(
  168. owner,
  169. wormhole_address,
  170. fee_contract_address,
  171. 1000,
  172. array![
  173. DataSource {
  174. emitter_chain_id: 26,
  175. emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
  176. }
  177. ],
  178. 1,
  179. 41,
  180. 0,
  181. )
  182. }
  183. fn deploy(
  184. owner: ContractAddress,
  185. wormhole_address: ContractAddress,
  186. fee_contract_address: ContractAddress,
  187. single_update_fee: u256,
  188. data_sources: Array<DataSource>,
  189. governance_emitter_chain_id: u16,
  190. governance_emitter_address: u256,
  191. governance_initial_sequence: u64,
  192. ) -> IPythDispatcher {
  193. let mut args = array![];
  194. (owner, wormhole_address, fee_contract_address, single_update_fee).serialize(ref args);
  195. (data_sources, governance_emitter_chain_id).serialize(ref args);
  196. (governance_emitter_address, governance_initial_sequence).serialize(ref args);
  197. let contract = declare("pyth");
  198. let contract_address = match contract.deploy(@args) {
  199. Result::Ok(v) => { v },
  200. Result::Err(err) => {
  201. panic(err.panic_data);
  202. 0.try_into().unwrap()
  203. },
  204. };
  205. IPythDispatcher { contract_address }
  206. }
  207. fn deploy_fee_contract(recipient: ContractAddress) -> IERC20CamelDispatcher {
  208. let mut args = array![];
  209. let name: core::byte_array::ByteArray = "eth";
  210. let symbol: core::byte_array::ByteArray = "eth";
  211. (name, symbol, 100000_u256, recipient).serialize(ref args);
  212. let contract = declare("ERC20");
  213. let contract_address = match contract.deploy(@args) {
  214. Result::Ok(v) => { v },
  215. Result::Err(err) => {
  216. panic(err.panic_data);
  217. 0.try_into().unwrap()
  218. },
  219. };
  220. IERC20CamelDispatcher { contract_address }
  221. }
  222. // A random update pulled from Hermes.
  223. fn good_update1() -> ByteArray {
  224. let bytes = array![
  225. 141887862745809943100717722154781668316147089807066324001213790862261653767,
  226. 451230040559159019530944948086670994623010697390864133264612902902585665886,
  227. 355897384610106978643111834734000274494997301794613218547634257521495150151,
  228. 140511063638834349363702006999356227863549404051701803148734324248522745879,
  229. 435849190784772134907557391544163070978531038970298390345939133663347953446,
  230. 416390591179833928094641114955594939466104495718036761707729297119441316151,
  231. 360454929416220920336539568461651500076647166763464050800345920693176904002,
  232. 316054999864337699543932294956493808847640383114707243342262764542081441331,
  233. 325277902980160684959962429721294603784343718796390808940252812862355246813,
  234. 43683235854839458868457367619068018785880460427473556950900276498953667,
  235. 448289429405712011882317781416869052550573589492688760675666957663813001522,
  236. 118081463902430977133121147164253483958565039026724621562859841189218059803,
  237. 194064310618695309465615383754562031677972810736048112738513050109934134235,
  238. 133901765334590923121691219814784557892214901646312752962904032795881821509,
  239. 404227501001709279944936006741063968912686453006275462577777397594240621266,
  240. 81649001731335394114026683805238949464016657447685509824621946636993704965,
  241. 32402065226491532148674904435794801976788068837745943243341272676331333141,
  242. 431262841416902409381606630149292665102873776020834630861578112749151562174,
  243. 6164523115980545628843981978797257048781800754033825701059814297149591186,
  244. 408761574582108996678203805090470134287794603493622537384530614829262728153,
  245. 185368533577943244707350150853170361880334596276529206938783888784867529821,
  246. 173578821500714074579643724957224629379984215847383417303110192934676518530,
  247. 90209855380378362490166376523380463998928070428866100240907090599465187835,
  248. 97758466908511588082569287391708453107999243934457382895073183209581711489,
  249. 132725011490528489913736834798247512772139171145730373610858422315799224432,
  250. 117123868005849140967825260063167768530251411611975150066586827543934313288,
  251. 408149062252618928234854115279677715692278734600386004492580987016428761675,
  252. 164529520317122600276020522906605877985809506451193373524142111430138855019,
  253. 444793051809958482843529748761971363435331354795896511243191618771787268378,
  254. 247660009137502548346315865368477795392972486141407800140910365553760622080,
  255. 3281582060272565111592312037403686940429019548922889497694300188,
  256. 93649805131515836129946966966350066506512123780266587069413066350925286142,
  257. 394112423559676785086098106350541172262729583743734966358666094809121292390,
  258. 35403101004688876764673991514113473446030702766599795822870037077688984558,
  259. 99366103604611980443183454746643823071419076016677225828619807954313149423,
  260. 10381657217606191031071521950784155484751645280452344547752823767622424055,
  261. 391045354044274401116419632681482293741435113770205621235865697077178955228,
  262. 311250087759201408758984550959714865999349469611700431708031036894849650573,
  263. 59953730895385399344628932835545900304309851622811198425230584225200786697,
  264. 226866843267230707879834616967256711063296411939069440476882347301771901839,
  265. 95752383404870925303422787,
  266. ];
  267. ByteArrayImpl::new(array_try_into(bytes), 11)
  268. }
  269. fn governance_set_fee() -> ByteArray {
  270. ByteArrayImpl::new(
  271. array_try_into(
  272. array![
  273. 1766847064779993955862540543984267022910717161432209540262366788014689913,
  274. 322968519187498395396360816568387642032723484530650782503164941848016432477,
  275. 407975527128964115747680681091773270935844489133353741223501742992990928896,
  276. 49565958604199796163020368,
  277. 8072278384728444780182694421117884443886221966887092226,
  278. ]
  279. ),
  280. 23
  281. )
  282. }