[cosmwasm] update contract (#509)

* contract updated

* add tests for the logic
This commit is contained in:
Dev Kalra 2023-01-19 15:27:59 +05:30 committed by GitHub
parent f3f28b4214
commit 5d0acc1a76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 316 additions and 150 deletions

View File

@ -1136,7 +1136,7 @@ name = "p2w-sdk"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"hex", "hex",
"pyth-sdk", "pyth-sdk 0.5.0",
"serde", "serde",
] ]
@ -1248,7 +1248,7 @@ dependencies = [
"k256 0.9.6", "k256 0.9.6",
"lazy_static", "lazy_static",
"p2w-sdk", "p2w-sdk",
"pyth-sdk-cw", "pyth-sdk 0.7.0",
"schemars", "schemars",
"serde", "serde",
"serde_derive", "serde_derive",
@ -1273,14 +1273,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pyth-sdk-cw" name = "pyth-sdk"
version = "0.2.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1575db8f501031a810aa5fe5da8ac3868bfcee7ce3deb27d6354c81a5833cb" checksum = "00bf2540203ca3c7a5712fdb8b5897534b7f6a0b6e7b0923ff00466c5f9efcb3"
dependencies = [ dependencies = [
"cosmwasm-std", "borsh",
"cosmwasm-storage", "borsh-derive",
"pyth-sdk", "hex",
"schemars", "schemars",
"serde", "serde",
] ]

View File

@ -29,7 +29,7 @@ hex = "0.4.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
bigint = "4" bigint = "4"
p2w-sdk = { path = "../../../../third_party/pyth/p2w-sdk/rust" } p2w-sdk = { path = "../../../../third_party/pyth/p2w-sdk/rust" }
pyth-sdk-cw = "0.2.0" pyth-sdk = "0.7.0"
byteorder = "1.4.3" byteorder = "1.4.3"
[dev-dependencies] [dev-dependencies]

View File

@ -28,6 +28,9 @@ use {
PriceInfo, PriceInfo,
PythDataSource, PythDataSource,
}, },
Price,
PriceFeed,
PriceIdentifier,
}, },
cosmwasm_std::{ cosmwasm_std::{
coin, coin,
@ -51,12 +54,10 @@ use {
WasmMsg, WasmMsg,
WasmQuery, WasmQuery,
}, },
p2w_sdk::BatchPriceAttestation, p2w_sdk::{
pyth_sdk_cw::{ BatchPriceAttestation,
PriceFeed, PriceAttestation,
PriceIdentifier,
PriceStatus, PriceStatus,
ProductIdentifier,
}, },
std::{ std::{
collections::HashSet, collections::HashSet,
@ -348,22 +349,7 @@ fn process_batch_attestation(
// Update prices // Update prices
for price_attestation in batch_attestation.price_attestations.iter() { for price_attestation in batch_attestation.price_attestations.iter() {
let price_feed = PriceFeed::new( let price_feed = create_price_feed_from_price_attestation(price_attestation);
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 attestation_time = Timestamp::from_seconds(price_attestation.attestation_time as u64); 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. /// 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. /// 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)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> { pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg { 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::GetUpdateFee { vaas } => to_binary(&get_update_fee(&deps, &vaas)?),
QueryMsg::GetValidTimePeriod => to_binary(&get_valid_time_period(&deps)?), QueryMsg::GetValidTimePeriod => to_binary(&get_valid_time_period(&deps)?),
} }
} }
pub fn query_price_feed(deps: &Deps, env: Env, address: &[u8]) -> StdResult<PriceFeedResponse> { pub fn query_price_feed(deps: &Deps, address: &[u8]) -> StdResult<PriceFeedResponse> {
let config = config_read(deps.storage).load()?;
match price_info_read(deps.storage).load(address) { match price_info_read(deps.storage).load(address) {
Ok(mut price_info) => { Ok(price_info) => Ok(PriceFeedResponse {
let env_time_sec = env.block.time.seconds(); price_feed: price_info.price_feed,
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,
})
}
Err(_) => Err(PythContractError::PriceFeedNotFound)?, Err(_) => Err(PythContractError::PriceFeedNotFound)?,
} }
} }
@ -489,9 +487,12 @@ pub fn get_valid_time_period(deps: &Deps) -> StdResult<Duration> {
mod test { mod test {
use { use {
super::*, super::*,
crate::governance::GovernanceModule::{ crate::{
Executor, governance::GovernanceModule::{
Target, Executor,
Target,
},
PriceIdentifier,
}, },
cosmwasm_std::{ cosmwasm_std::{
coins, coins,
@ -512,6 +513,7 @@ mod test {
SystemResult, SystemResult,
Uint128, Uint128,
}, },
p2w_sdk::PriceAttestation,
std::time::Duration, std::time::Duration,
}; };
@ -635,9 +637,17 @@ mod test {
} }
fn create_price_feed(expo: i32) -> PriceFeed { fn create_price_feed(expo: i32) -> PriceFeed {
let mut price_feed = PriceFeed::default(); PriceFeed::new(
price_feed.expo = expo; PriceIdentifier::new([0u8; 32]),
price_feed Price {
expo,
..Default::default()
},
Price {
expo,
..Default::default()
},
)
} }
fn create_data_sources( fn create_data_sources(
@ -681,6 +691,210 @@ mod test {
update_price_feeds(deps.as_mut(), env, info, &[msg]) 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] #[test]
fn test_verify_vaa_sender_ok() { fn test_verify_vaa_sender_ok() {
let result = apply_price_update( let result = apply_price_update(
@ -828,102 +1042,47 @@ mod test {
} }
#[test] #[test]
fn test_query_price_info_ok_trading() { fn test_query_price_info_ok() {
let (mut deps, mut env) = setup_test(); let (mut deps, _env) = setup_test();
let address = b"123".as_ref(); let address = b"123".as_ref();
let mut dummy_price_info = PriceInfo::default(); let dummy_price_info = PriceInfo {
dummy_price_info.price_feed.publish_time = 80; price_feed: PriceFeed::new(
dummy_price_info.price_feed.status = PriceStatus::Trading; PriceIdentifier::new([0u8; 32]),
Price {
price: 300,
conf: 301,
expo: 302,
publish_time: 303,
},
Price {
..Default::default()
},
),
..Default::default()
};
price_info(&mut deps.storage) price_info(&mut deps.storage)
.save(address, &dummy_price_info) .save(address, &dummy_price_info)
.unwrap(); .unwrap();
env.block.time = Timestamp::from_seconds(80 + VALID_TIME_PERIOD.as_secs()); let price_feed = query_price_feed(&deps.as_ref(), address)
let price_feed = query_price_feed(&deps.as_ref(), env, address)
.unwrap() .unwrap()
.price_feed; .price_feed;
assert_eq!(price_feed.status, PriceStatus::Trading); 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);
#[test] assert_eq!(price_feed.get_price_unchecked().publish_time, 303);
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);
} }
#[test] #[test]
fn test_query_price_info_err_not_found() { fn test_query_price_info_err_not_found() {
let (deps, env) = setup_test(); let deps = setup_test().0;
assert_eq!( 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()) Err(PythContractError::PriceFeedNotFound.into())
); );
} }

View File

@ -6,3 +6,10 @@ pub mod error;
pub mod governance; pub mod governance;
pub mod msg; pub mod msg;
pub mod state; pub mod state;
pub use pyth_sdk::{
Price,
PriceFeed,
PriceIdentifier,
ProductIdentifier,
};

View File

@ -1,13 +1,13 @@
use { use {
crate::state::PythDataSource, crate::{
state::PythDataSource,
PriceFeed,
PriceIdentifier,
},
cosmwasm_std::{ cosmwasm_std::{
Binary, Binary,
Coin, Coin,
}, },
pyth_sdk_cw::{
PriceFeed,
PriceIdentifier,
},
schemars::JsonSchema, schemars::JsonSchema,
serde::{ serde::{
Deserialize, Deserialize,

View File

@ -1,4 +1,5 @@
use { use {
crate::PriceFeed,
cosmwasm_std::{ cosmwasm_std::{
Addr, Addr,
Binary, Binary,
@ -16,7 +17,6 @@ use {
ReadonlySingleton, ReadonlySingleton,
Singleton, Singleton,
}, },
pyth_sdk_cw::PriceFeed,
schemars::JsonSchema, schemars::JsonSchema,
serde::{ serde::{
Deserialize, Deserialize,