[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"
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",
]

View File

@ -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]

View File

@ -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<Binary> {
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
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<PriceFeedResponse> {
let config = config_read(deps.storage).load()?;
pub fn query_price_feed(deps: &Deps, address: &[u8]) -> StdResult<PriceFeedResponse> {
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<Duration> {
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())
);
}

View File

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

View File

@ -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,

View File

@ -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,