contract.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. use {
  2. crate::{
  3. msg::{
  4. ExecuteMsg,
  5. GetPriceResponse,
  6. InstantiateMsg,
  7. MigrateMsg,
  8. QueryMsg,
  9. },
  10. state::{
  11. config,
  12. config_read,
  13. ConfigInfo,
  14. },
  15. },
  16. cosmwasm_std::{
  17. entry_point,
  18. to_binary,
  19. Binary,
  20. Deps,
  21. DepsMut,
  22. Env,
  23. MessageInfo,
  24. Response,
  25. StdError::{self,},
  26. StdResult,
  27. WasmQuery,
  28. },
  29. pyth_sdk_cw::{
  30. query_price_feed,
  31. Price,
  32. PriceFeed,
  33. PriceFeedResponse,
  34. PriceIdentifier,
  35. },
  36. };
  37. #[cfg_attr(not(feature = "library"), entry_point)]
  38. pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
  39. Ok(Response::new())
  40. }
  41. #[cfg_attr(not(feature = "library"), entry_point)]
  42. pub fn instantiate(
  43. deps: DepsMut,
  44. _env: Env,
  45. _info: MessageInfo,
  46. msg: InstantiateMsg,
  47. ) -> StdResult<Response> {
  48. // Save general wormhole and pyth info
  49. let state = ConfigInfo {
  50. pyth_contract: msg.pyth_contract,
  51. price_feed_id: msg.price_feed_id,
  52. price_in_usd: msg.price_in_usd,
  53. target_exponent: msg.target_exponent,
  54. };
  55. config(deps.storage).save(&state)?;
  56. Ok(Response::default())
  57. }
  58. #[cfg_attr(not(feature = "library"), entry_point)]
  59. pub fn execute(
  60. _deps: DepsMut,
  61. _env: Env,
  62. _info: MessageInfo,
  63. _msg: ExecuteMsg,
  64. ) -> StdResult<Response> {
  65. // TODO
  66. Ok(Response::default())
  67. }
  68. #[cfg_attr(not(feature = "library"), entry_point)]
  69. pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
  70. match msg {
  71. // user wants to purchase `quantity` of the token. They need to pay
  72. QueryMsg::GetPrice { quantity } => {
  73. let cfg = config_read(deps.storage).load()?;
  74. let price_in_usd: Price = Price {
  75. price: i64::from(quantity) * i64::from(cfg.price_in_usd),
  76. conf: 0,
  77. expo: 0,
  78. };
  79. let feed = read_pyth_price(&deps, &env)?;
  80. // Value of token in USD, e.g, 1BTC = $10k
  81. let current_token_price = feed
  82. .get_current_price()
  83. .ok_or(StdError::generic_err("feed is not current"))?;
  84. // Price struct supports nice arithmetic
  85. let price_in_payment_token = price_in_usd
  86. .div(&current_token_price)
  87. .and_then(|p| p.scale_to_exponent(cfg.target_exponent))
  88. .ok_or(StdError::generic_err("division error"))?;
  89. to_binary(&GetPriceResponse {
  90. price: price_in_payment_token.price,
  91. exponent: price_in_payment_token.expo,
  92. })
  93. }
  94. }
  95. }
  96. pub fn read_pyth_price(deps: &Deps, _env: &Env) -> StdResult<PriceFeed> {
  97. let cfg = config_read(deps.storage).load()?;
  98. query_price_feed(&deps.querier, cfg.pyth_contract, cfg.price_feed_id).map(|r| r.price_feed)
  99. }
  100. #[cfg(test)]
  101. mod test {
  102. use {
  103. super::*,
  104. cosmwasm_std::{
  105. from_binary,
  106. testing::{
  107. mock_dependencies,
  108. mock_env,
  109. MockApi,
  110. MockQuerier,
  111. MockStorage,
  112. },
  113. Addr,
  114. ContractResult,
  115. OwnedDeps,
  116. QuerierResult,
  117. SystemError,
  118. SystemResult,
  119. },
  120. p2w_sdk::PriceStatus,
  121. };
  122. // TODO: point to documentation
  123. const PYTH_CONTRACT_ADDR: &str = "pyth_contract_addr";
  124. // See list of price feed ids here https://pyth.network/developers/price-feed-ids
  125. const PRICE_ID: &str = "63f341689d98a12ef60a5cff1d7f85c70a9e17bf1575f0e7c0b2512d48b1c8b3";
  126. fn default_emitter_addr() -> Vec<u8> {
  127. vec![0, 1, 80]
  128. }
  129. fn default_config_info() -> ConfigInfo {
  130. ConfigInfo {
  131. pyth_contract: Addr::unchecked(PYTH_CONTRACT_ADDR),
  132. price_feed_id: PriceIdentifier::from_hex(PRICE_ID).unwrap(),
  133. price_in_usd: 10,
  134. target_exponent: -2,
  135. }
  136. }
  137. fn setup_test(config_info: &ConfigInfo) -> (OwnedDeps<MockStorage, MockApi, MockQuerier>, Env) {
  138. let mut dependencies = mock_dependencies();
  139. dependencies.querier.update_wasm(handle_wasm_query);
  140. let mut config = config(dependencies.as_mut().storage);
  141. config.save(config_info).unwrap();
  142. (dependencies, mock_env())
  143. }
  144. fn handle_wasm_query(wasm_query: &WasmQuery) -> QuerierResult {
  145. match wasm_query {
  146. WasmQuery::Smart { contract_addr, msg } if *contract_addr == PYTH_CONTRACT_ADDR => {
  147. let query_msg = from_binary::<pyth_sdk_cw::QueryMsg>(msg);
  148. match query_msg {
  149. Ok(pyth_sdk_cw::QueryMsg::PriceFeed { id }) => {
  150. if id.to_hex() == PRICE_ID {
  151. let price_feed = PriceFeed::new(
  152. id,
  153. PriceStatus::Trading,
  154. 100,
  155. -2,
  156. 32,
  157. 3,
  158. id,
  159. 100 * 100,
  160. 100,
  161. 75 * 100,
  162. 100,
  163. 99 * 100,
  164. 100,
  165. 99,
  166. );
  167. SystemResult::Ok(ContractResult::Ok(
  168. to_binary(&PriceFeedResponse { price_feed }).unwrap(),
  169. ))
  170. } else {
  171. SystemResult::Ok(ContractResult::Err("unknown price feed".into()))
  172. }
  173. }
  174. Err(_e) => SystemResult::Err(SystemError::InvalidRequest {
  175. error: "Invalid message".into(),
  176. request: msg.clone(),
  177. }),
  178. // TODO: this error isn't right
  179. _ => SystemResult::Err(SystemError::NoSuchContract {
  180. addr: contract_addr.clone(),
  181. }),
  182. }
  183. }
  184. WasmQuery::Smart { contract_addr, .. } => {
  185. SystemResult::Err(SystemError::NoSuchContract {
  186. addr: contract_addr.clone(),
  187. })
  188. }
  189. WasmQuery::Raw { contract_addr, .. } => {
  190. SystemResult::Err(SystemError::NoSuchContract {
  191. addr: contract_addr.clone(),
  192. })
  193. }
  194. WasmQuery::ContractInfo { contract_addr, .. } => {
  195. SystemResult::Err(SystemError::NoSuchContract {
  196. addr: contract_addr.clone(),
  197. })
  198. }
  199. _ => unreachable!(),
  200. }
  201. }
  202. fn query_get_price(config_info: &ConfigInfo, quantity: u32) -> StdResult<GetPriceResponse> {
  203. let (mut deps, env) = setup_test(config_info);
  204. config(&mut deps.storage).save(config_info).unwrap();
  205. let msg = QueryMsg::GetPrice { quantity };
  206. query(deps.as_ref(), env, msg).and_then(|binary| from_binary::<GetPriceResponse>(&binary))
  207. }
  208. #[test]
  209. fn test_get_price() {
  210. let result = query_get_price(&default_config_info(), 100);
  211. assert_eq!(result.map(|r| r.price), Ok(1000));
  212. }
  213. }