From 5d0acc1a76165c16c9bdd90cd010533b2a86be3c Mon Sep 17 00:00:00 2001 From: Dev Kalra Date: Thu, 19 Jan 2023 15:27:59 +0530 Subject: [PATCH] [cosmwasm] update contract (#509) * contract updated * add tests for the logic --- target-chains/cosmwasm/Cargo.lock | 16 +- .../cosmwasm/contracts/pyth/Cargo.toml | 2 +- .../cosmwasm/contracts/pyth/src/contract.rs | 429 ++++++++++++------ .../cosmwasm/contracts/pyth/src/lib.rs | 7 + .../cosmwasm/contracts/pyth/src/msg.rs | 10 +- .../cosmwasm/contracts/pyth/src/state.rs | 2 +- 6 files changed, 316 insertions(+), 150 deletions(-) diff --git a/target-chains/cosmwasm/Cargo.lock b/target-chains/cosmwasm/Cargo.lock index 3691e07e..cae55bf5 100644 --- a/target-chains/cosmwasm/Cargo.lock +++ b/target-chains/cosmwasm/Cargo.lock @@ -1136,7 +1136,7 @@ name = "p2w-sdk" version = "0.1.1" dependencies = [ "hex", - "pyth-sdk", + "pyth-sdk 0.5.0", "serde", ] @@ -1248,7 +1248,7 @@ dependencies = [ "k256 0.9.6", "lazy_static", "p2w-sdk", - "pyth-sdk-cw", + "pyth-sdk 0.7.0", "schemars", "serde", "serde_derive", @@ -1273,14 +1273,14 @@ dependencies = [ ] [[package]] -name = "pyth-sdk-cw" -version = "0.2.0" +name = "pyth-sdk" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1575db8f501031a810aa5fe5da8ac3868bfcee7ce3deb27d6354c81a5833cb" +checksum = "00bf2540203ca3c7a5712fdb8b5897534b7f6a0b6e7b0923ff00466c5f9efcb3" dependencies = [ - "cosmwasm-std", - "cosmwasm-storage", - "pyth-sdk", + "borsh", + "borsh-derive", + "hex", "schemars", "serde", ] diff --git a/target-chains/cosmwasm/contracts/pyth/Cargo.toml b/target-chains/cosmwasm/contracts/pyth/Cargo.toml index 16df11bf..986c1267 100644 --- a/target-chains/cosmwasm/contracts/pyth/Cargo.toml +++ b/target-chains/cosmwasm/contracts/pyth/Cargo.toml @@ -29,7 +29,7 @@ hex = "0.4.2" lazy_static = "1.4.0" bigint = "4" p2w-sdk = { path = "../../../../third_party/pyth/p2w-sdk/rust" } -pyth-sdk-cw = "0.2.0" +pyth-sdk = "0.7.0" byteorder = "1.4.3" [dev-dependencies] diff --git a/target-chains/cosmwasm/contracts/pyth/src/contract.rs b/target-chains/cosmwasm/contracts/pyth/src/contract.rs index 2c30ed94..1d0ff833 100644 --- a/target-chains/cosmwasm/contracts/pyth/src/contract.rs +++ b/target-chains/cosmwasm/contracts/pyth/src/contract.rs @@ -28,6 +28,9 @@ use { PriceInfo, PythDataSource, }, + Price, + PriceFeed, + PriceIdentifier, }, cosmwasm_std::{ coin, @@ -51,12 +54,10 @@ use { WasmMsg, WasmQuery, }, - p2w_sdk::BatchPriceAttestation, - pyth_sdk_cw::{ - PriceFeed, - PriceIdentifier, + p2w_sdk::{ + BatchPriceAttestation, + PriceAttestation, PriceStatus, - ProductIdentifier, }, std::{ collections::HashSet, @@ -348,22 +349,7 @@ fn process_batch_attestation( // Update prices for price_attestation in batch_attestation.price_attestations.iter() { - let price_feed = PriceFeed::new( - PriceIdentifier::new(price_attestation.price_id.to_bytes()), - price_attestation.status, - price_attestation.publish_time, - price_attestation.expo, - price_attestation.max_num_publishers, - price_attestation.num_publishers, - ProductIdentifier::new(price_attestation.product_id.to_bytes()), - price_attestation.price, - price_attestation.conf, - price_attestation.ema_price, - price_attestation.ema_conf, - price_attestation.prev_price, - price_attestation.prev_conf, - price_attestation.prev_publish_time, - ); + let price_feed = create_price_feed_from_price_attestation(price_attestation); let attestation_time = Timestamp::from_seconds(price_attestation.attestation_time as u64); @@ -378,6 +364,41 @@ fn process_batch_attestation( )) } +fn create_price_feed_from_price_attestation(price_attestation: &PriceAttestation) -> PriceFeed { + match price_attestation.status { + PriceStatus::Trading => PriceFeed::new( + PriceIdentifier::new(price_attestation.price_id.to_bytes()), + Price { + price: price_attestation.price, + conf: price_attestation.conf, + expo: price_attestation.expo, + publish_time: price_attestation.publish_time, + }, + Price { + price: price_attestation.ema_price, + conf: price_attestation.ema_conf, + expo: price_attestation.expo, + publish_time: price_attestation.publish_time, + }, + ), + _ => PriceFeed::new( + PriceIdentifier::new(price_attestation.price_id.to_bytes()), + Price { + price: price_attestation.prev_price, + conf: price_attestation.prev_conf, + expo: price_attestation.expo, + publish_time: price_attestation.prev_publish_time, + }, + Price { + price: price_attestation.ema_price, + conf: price_attestation.ema_conf, + expo: price_attestation.expo, + publish_time: price_attestation.prev_publish_time, + }, + ), + } +} + /// Returns true if the price_feed is newer than the stored one. /// /// This function returns error only if there be issues in ser/de when it reads from the bucket. @@ -423,42 +444,19 @@ fn update_price_feed_if_new( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::PriceFeed { id } => to_binary(&query_price_feed(&deps, env, id.as_ref())?), + QueryMsg::PriceFeed { id } => to_binary(&query_price_feed(&deps, id.as_ref())?), QueryMsg::GetUpdateFee { vaas } => to_binary(&get_update_fee(&deps, &vaas)?), QueryMsg::GetValidTimePeriod => to_binary(&get_valid_time_period(&deps)?), } } -pub fn query_price_feed(deps: &Deps, env: Env, address: &[u8]) -> StdResult { - let config = config_read(deps.storage).load()?; +pub fn query_price_feed(deps: &Deps, address: &[u8]) -> StdResult { match price_info_read(deps.storage).load(address) { - Ok(mut price_info) => { - let env_time_sec = env.block.time.seconds(); - let price_pub_time_sec = price_info.price_feed.publish_time as u64; - - // Cases that it will cover: - // - This will ensure to set status unknown if the price has become very old and hasn't - // updated yet. - // - If a price has arrived very late to this chain it will set the status to unknown. - // - If a price is coming from future it's tolerated up to VALID_TIME_PERIOD seconds - // (using abs diff) but more than that is set to unknown, the reason could be the - // clock time drift between the source and target chains. - let time_abs_diff = if env_time_sec > price_pub_time_sec { - env_time_sec - price_pub_time_sec - } else { - price_pub_time_sec - env_time_sec - }; - - if time_abs_diff > config.valid_time_period.as_secs() { - price_info.price_feed.status = PriceStatus::Unknown; - } - - Ok(PriceFeedResponse { - price_feed: price_info.price_feed, - }) - } + Ok(price_info) => Ok(PriceFeedResponse { + price_feed: price_info.price_feed, + }), Err(_) => Err(PythContractError::PriceFeedNotFound)?, } } @@ -489,9 +487,12 @@ pub fn get_valid_time_period(deps: &Deps) -> StdResult { mod test { use { super::*, - crate::governance::GovernanceModule::{ - Executor, - Target, + crate::{ + governance::GovernanceModule::{ + Executor, + Target, + }, + PriceIdentifier, }, cosmwasm_std::{ coins, @@ -512,6 +513,7 @@ mod test { SystemResult, Uint128, }, + p2w_sdk::PriceAttestation, std::time::Duration, }; @@ -635,9 +637,17 @@ mod test { } fn create_price_feed(expo: i32) -> PriceFeed { - let mut price_feed = PriceFeed::default(); - price_feed.expo = expo; - price_feed + PriceFeed::new( + PriceIdentifier::new([0u8; 32]), + Price { + expo, + ..Default::default() + }, + Price { + expo, + ..Default::default() + }, + ) } fn create_data_sources( @@ -681,6 +691,210 @@ mod test { update_price_feeds(deps.as_mut(), env, info, &[msg]) } + #[test] + fn test_process_batch_attestation_empty_array() { + let (mut deps, env) = setup_test(); + let attestations = BatchPriceAttestation { + price_attestations: vec![], + }; + let (total_attestations, new_attestations) = + process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap(); + + assert_eq!(total_attestations, 0); + assert_eq!(new_attestations, 0); + } + + #[test] + fn test_create_price_feed_from_price_attestation_status_trading() { + let price_attestation = PriceAttestation { + price_id: p2w_sdk::Identifier::new([0u8; 32]), + price: 100, + conf: 100, + expo: 100, + ema_price: 100, + ema_conf: 100, + status: PriceStatus::Trading, + attestation_time: 100, + publish_time: 100, + prev_publish_time: 99, + prev_price: 99, + prev_conf: 99, + ..Default::default() + }; + + let price_feed = create_price_feed_from_price_attestation(&price_attestation); + let price = price_feed.get_price_unchecked(); + let ema_price = price_feed.get_ema_price_unchecked(); + + // for price + assert_eq!(price.price, 100); + assert_eq!(price.conf, 100); + assert_eq!(price.expo, 100); + assert_eq!(price.publish_time, 100); + + // for ema + assert_eq!(ema_price.price, 100); + assert_eq!(ema_price.conf, 100); + assert_eq!(ema_price.expo, 100); + assert_eq!(ema_price.publish_time, 100); + } + + #[test] + fn test_create_price_feed_from_price_attestation_status_unknown() { + test_create_price_feed_from_price_attestation_not_trading(PriceStatus::Unknown) + } + + #[test] + fn test_create_price_feed_from_price_attestation_status_halted() { + test_create_price_feed_from_price_attestation_not_trading(PriceStatus::Halted) + } + + #[test] + fn test_create_price_feed_from_price_attestation_status_auction() { + test_create_price_feed_from_price_attestation_not_trading(PriceStatus::Auction) + } + + fn test_create_price_feed_from_price_attestation_not_trading(status: PriceStatus) { + let price_attestation = PriceAttestation { + price_id: p2w_sdk::Identifier::new([0u8; 32]), + price: 100, + conf: 100, + expo: 100, + ema_price: 100, + ema_conf: 100, + status, + attestation_time: 100, + publish_time: 100, + prev_publish_time: 99, + prev_price: 99, + prev_conf: 99, + ..Default::default() + }; + + let price_feed = create_price_feed_from_price_attestation(&price_attestation); + + let price = price_feed.get_price_unchecked(); + let ema_price = price_feed.get_ema_price_unchecked(); + + // for price + assert_eq!(price.price, 99); + assert_eq!(price.conf, 99); + assert_eq!(price.expo, 100); + assert_eq!(price.publish_time, 99); + + // for ema + assert_eq!(ema_price.price, 100); + assert_eq!(ema_price.conf, 100); + assert_eq!(ema_price.expo, 100); + assert_eq!(ema_price.publish_time, 99); + } + + // this is testing the function process_batch_attestation + // process_batch_attestation is calling update_price_feed_if_new + // changes to update_price_feed_if_new might cause this test + #[test] + fn test_process_batch_attestation_status_not_trading() { + let (mut deps, env) = setup_test(); + + let price_attestation = PriceAttestation { + price_id: p2w_sdk::Identifier::new([0u8; 32]), + price: 100, + conf: 100, + expo: 100, + ema_price: 100, + ema_conf: 100, + status: PriceStatus::Auction, + attestation_time: 100, + publish_time: 100, + prev_publish_time: 99, + prev_price: 99, + prev_conf: 99, + ..Default::default() + }; + + let attestations = BatchPriceAttestation { + price_attestations: vec![price_attestation], + }; + let (total_attestations, new_attestations) = + process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap(); + + + let stored_price_feed = price_info_read(&deps.storage) + .load(&[0u8; 32]) + .unwrap() + .price_feed; + let price = stored_price_feed.get_price_unchecked(); + let ema_price = stored_price_feed.get_ema_price_unchecked(); + + assert_eq!(total_attestations, 1); + assert_eq!(new_attestations, 1); + + // for price + assert_eq!(price.price, 99); + assert_eq!(price.conf, 99); + assert_eq!(price.expo, 100); + assert_eq!(price.publish_time, 99); + + // for ema + assert_eq!(ema_price.price, 100); + assert_eq!(ema_price.conf, 100); + assert_eq!(ema_price.expo, 100); + assert_eq!(ema_price.publish_time, 99); + } + + // this is testing the function process_batch_attestation + // process_batch_attestation is calling update_price_feed_if_new + // changes to update_price_feed_if_new might affect this test + #[test] + fn test_process_batch_attestation_status_trading() { + let (mut deps, env) = setup_test(); + + let price_attestation = PriceAttestation { + price_id: p2w_sdk::Identifier::new([0u8; 32]), + price: 100, + conf: 100, + expo: 100, + ema_price: 100, + ema_conf: 100, + status: PriceStatus::Trading, + attestation_time: 100, + publish_time: 100, + prev_publish_time: 99, + prev_price: 99, + prev_conf: 99, + ..Default::default() + }; + + let attestations = BatchPriceAttestation { + price_attestations: vec![price_attestation], + }; + let (total_attestations, new_attestations) = + process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap(); + + + let stored_price_feed = price_info_read(&deps.storage) + .load(&[0u8; 32]) + .unwrap() + .price_feed; + let price = stored_price_feed.get_price_unchecked(); + let ema_price = stored_price_feed.get_ema_price_unchecked(); + + assert_eq!(total_attestations, 1); + assert_eq!(new_attestations, 1); + + // for price + assert_eq!(price.price, 100); + assert_eq!(price.conf, 100); + assert_eq!(price.expo, 100); + assert_eq!(price.publish_time, 100); + + // for ema + assert_eq!(ema_price.price, 100); + assert_eq!(ema_price.conf, 100); + assert_eq!(ema_price.expo, 100); + assert_eq!(ema_price.publish_time, 100); + } + #[test] fn test_verify_vaa_sender_ok() { let result = apply_price_update( @@ -828,102 +1042,47 @@ mod test { } #[test] - fn test_query_price_info_ok_trading() { - let (mut deps, mut env) = setup_test(); + fn test_query_price_info_ok() { + let (mut deps, _env) = setup_test(); let address = b"123".as_ref(); - let mut dummy_price_info = PriceInfo::default(); - dummy_price_info.price_feed.publish_time = 80; - dummy_price_info.price_feed.status = PriceStatus::Trading; + let dummy_price_info = PriceInfo { + price_feed: PriceFeed::new( + PriceIdentifier::new([0u8; 32]), + Price { + price: 300, + conf: 301, + expo: 302, + publish_time: 303, + }, + Price { + ..Default::default() + }, + ), + ..Default::default() + }; price_info(&mut deps.storage) .save(address, &dummy_price_info) .unwrap(); - env.block.time = Timestamp::from_seconds(80 + VALID_TIME_PERIOD.as_secs()); - - let price_feed = query_price_feed(&deps.as_ref(), env, address) + let price_feed = query_price_feed(&deps.as_ref(), address) .unwrap() .price_feed; - assert_eq!(price_feed.status, PriceStatus::Trading); - } - - #[test] - fn test_query_price_info_ok_stale_past() { - let (mut deps, mut env) = setup_test(); - let address = b"123".as_ref(); - - let mut dummy_price_info = PriceInfo::default(); - dummy_price_info.price_feed.publish_time = 500; - dummy_price_info.price_feed.status = PriceStatus::Trading; - - price_info(&mut deps.storage) - .save(address, &dummy_price_info) - .unwrap(); - - env.block.time = Timestamp::from_seconds(500 + VALID_TIME_PERIOD.as_secs() + 1); - - let price_feed = query_price_feed(&deps.as_ref(), env, address) - .unwrap() - .price_feed; - - assert_eq!(price_feed.status, PriceStatus::Unknown); - } - - #[test] - fn test_query_price_info_ok_trading_future() { - let (mut deps, mut env) = setup_test(); - - let address = b"123".as_ref(); - - let mut dummy_price_info = PriceInfo::default(); - dummy_price_info.price_feed.publish_time = 500; - dummy_price_info.price_feed.status = PriceStatus::Trading; - - price_info(&mut deps.storage) - .save(address, &dummy_price_info) - .unwrap(); - - env.block.time = Timestamp::from_seconds(500 - VALID_TIME_PERIOD.as_secs()); - - let price_feed = query_price_feed(&deps.as_ref(), env, address) - .unwrap() - .price_feed; - - assert_eq!(price_feed.status, PriceStatus::Trading); - } - - #[test] - fn test_query_price_info_ok_stale_future() { - let (mut deps, mut env) = setup_test(); - - let address = b"123".as_ref(); - - let mut dummy_price_info = PriceInfo::default(); - dummy_price_info.price_feed.publish_time = 500; - dummy_price_info.price_feed.status = PriceStatus::Trading; - - price_info(&mut deps.storage) - .save(address, &dummy_price_info) - .unwrap(); - - env.block.time = Timestamp::from_seconds(500 - VALID_TIME_PERIOD.as_secs() - 1); - - let price_feed = query_price_feed(&deps.as_ref(), env, address) - .unwrap() - .price_feed; - - assert_eq!(price_feed.status, PriceStatus::Unknown); + assert_eq!(price_feed.get_price_unchecked().price, 300); + assert_eq!(price_feed.get_price_unchecked().conf, 301); + assert_eq!(price_feed.get_price_unchecked().expo, 302); + assert_eq!(price_feed.get_price_unchecked().publish_time, 303); } #[test] fn test_query_price_info_err_not_found() { - let (deps, env) = setup_test(); + let deps = setup_test().0; assert_eq!( - query_price_feed(&deps.as_ref(), env, b"123".as_ref()), + query_price_feed(&deps.as_ref(), b"123".as_ref()), Err(PythContractError::PriceFeedNotFound.into()) ); } diff --git a/target-chains/cosmwasm/contracts/pyth/src/lib.rs b/target-chains/cosmwasm/contracts/pyth/src/lib.rs index 7a87f22b..b71f55fe 100644 --- a/target-chains/cosmwasm/contracts/pyth/src/lib.rs +++ b/target-chains/cosmwasm/contracts/pyth/src/lib.rs @@ -6,3 +6,10 @@ pub mod error; pub mod governance; pub mod msg; pub mod state; + +pub use pyth_sdk::{ + Price, + PriceFeed, + PriceIdentifier, + ProductIdentifier, +}; diff --git a/target-chains/cosmwasm/contracts/pyth/src/msg.rs b/target-chains/cosmwasm/contracts/pyth/src/msg.rs index 90dfb002..59280c9d 100644 --- a/target-chains/cosmwasm/contracts/pyth/src/msg.rs +++ b/target-chains/cosmwasm/contracts/pyth/src/msg.rs @@ -1,13 +1,13 @@ use { - crate::state::PythDataSource, + crate::{ + state::PythDataSource, + PriceFeed, + PriceIdentifier, + }, cosmwasm_std::{ Binary, Coin, }, - pyth_sdk_cw::{ - PriceFeed, - PriceIdentifier, - }, schemars::JsonSchema, serde::{ Deserialize, diff --git a/target-chains/cosmwasm/contracts/pyth/src/state.rs b/target-chains/cosmwasm/contracts/pyth/src/state.rs index 889f308c..312e0827 100644 --- a/target-chains/cosmwasm/contracts/pyth/src/state.rs +++ b/target-chains/cosmwasm/contracts/pyth/src/state.rs @@ -1,4 +1,5 @@ use { + crate::PriceFeed, cosmwasm_std::{ Addr, Binary, @@ -16,7 +17,6 @@ use { ReadonlySingleton, Singleton, }, - pyth_sdk_cw::PriceFeed, schemars::JsonSchema, serde::{ Deserialize,