[pyth-cosmwasm] sec review update (#649)
* remove owner * address sec review feedback * update storage for pricefeeds * deprecate owner * remove owner field
This commit is contained in:
parent
bbc140f2e4
commit
16ac58539e
|
@ -19,10 +19,9 @@ use {
|
||||||
state::{
|
state::{
|
||||||
config,
|
config,
|
||||||
config_read,
|
config_read,
|
||||||
price_info,
|
price_feed_bucket,
|
||||||
price_info_read,
|
price_feed_read_bucket,
|
||||||
ConfigInfo,
|
ConfigInfo,
|
||||||
PriceInfo,
|
|
||||||
PythDataSource,
|
PythDataSource,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -44,7 +43,6 @@ use {
|
||||||
QueryRequest,
|
QueryRequest,
|
||||||
Response,
|
Response,
|
||||||
StdResult,
|
StdResult,
|
||||||
Timestamp,
|
|
||||||
WasmMsg,
|
WasmMsg,
|
||||||
WasmQuery,
|
WasmQuery,
|
||||||
},
|
},
|
||||||
|
@ -93,12 +91,11 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Respons
|
||||||
pub fn instantiate(
|
pub fn instantiate(
|
||||||
deps: DepsMut,
|
deps: DepsMut,
|
||||||
_env: Env,
|
_env: Env,
|
||||||
info: MessageInfo,
|
_info: MessageInfo,
|
||||||
msg: InstantiateMsg,
|
msg: InstantiateMsg,
|
||||||
) -> StdResult<Response> {
|
) -> StdResult<Response> {
|
||||||
// Save general wormhole and pyth info
|
// Save general wormhole and pyth info
|
||||||
let state = ConfigInfo {
|
let state = ConfigInfo {
|
||||||
owner: info.sender,
|
|
||||||
wormhole_contract: deps.api.addr_validate(msg.wormhole_contract.as_ref())?,
|
wormhole_contract: deps.api.addr_validate(msg.wormhole_contract.as_ref())?,
|
||||||
data_sources: msg.data_sources.iter().cloned().collect(),
|
data_sources: msg.data_sources.iter().cloned().collect(),
|
||||||
chain_id: msg.chain_id,
|
chain_id: msg.chain_id,
|
||||||
|
@ -385,9 +382,7 @@ fn process_batch_attestation(
|
||||||
for price_attestation in batch_attestation.price_attestations.iter() {
|
for price_attestation in batch_attestation.price_attestations.iter() {
|
||||||
let price_feed = create_price_feed_from_price_attestation(price_attestation);
|
let price_feed = create_price_feed_from_price_attestation(price_attestation);
|
||||||
|
|
||||||
let attestation_time = Timestamp::from_seconds(price_attestation.attestation_time as u64);
|
if update_price_feed_if_new(deps, env, price_feed)? {
|
||||||
|
|
||||||
if update_price_feed_if_new(deps, env, price_feed, attestation_time)? {
|
|
||||||
new_attestations_cnt += 1;
|
new_attestations_cnt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,37 +435,28 @@ fn create_price_feed_from_price_attestation(price_attestation: &PriceAttestation
|
||||||
/// in the bucket won't be parsed.
|
/// in the bucket won't be parsed.
|
||||||
fn update_price_feed_if_new(
|
fn update_price_feed_if_new(
|
||||||
deps: &mut DepsMut,
|
deps: &mut DepsMut,
|
||||||
env: &Env,
|
_env: &Env,
|
||||||
price_feed: PriceFeed,
|
new_price_feed: PriceFeed,
|
||||||
attestation_time: Timestamp,
|
|
||||||
) -> StdResult<bool> {
|
) -> StdResult<bool> {
|
||||||
let mut is_new_price = true;
|
let mut is_new_price = true;
|
||||||
price_info(deps.storage).update(
|
price_feed_bucket(deps.storage).update(
|
||||||
price_feed.id.as_ref(),
|
new_price_feed.id.as_ref(),
|
||||||
|maybe_price_info| -> StdResult<PriceInfo> {
|
|maybe_price_feed| -> StdResult<PriceFeed> {
|
||||||
match maybe_price_info {
|
match maybe_price_feed {
|
||||||
Some(price_info) => {
|
Some(price_feed) => {
|
||||||
// This check ensures that a price won't be updated with the same or older
|
// This check ensures that a price won't be updated with the same or older
|
||||||
// message. Attestation_time is guaranteed increasing in
|
// message. Publish_TIme is guaranteed increasing in
|
||||||
// solana
|
// solana
|
||||||
if price_info.attestation_time < attestation_time {
|
if price_feed.get_price_unchecked().publish_time
|
||||||
Ok(PriceInfo {
|
< new_price_feed.get_price_unchecked().publish_time
|
||||||
arrival_time: env.block.time,
|
{
|
||||||
arrival_block: env.block.height,
|
Ok(new_price_feed)
|
||||||
price_feed,
|
|
||||||
attestation_time,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
is_new_price = false;
|
is_new_price = false;
|
||||||
Ok(price_info)
|
Ok(price_feed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Ok(PriceInfo {
|
None => Ok(new_price_feed),
|
||||||
arrival_time: env.block.time,
|
|
||||||
arrival_block: env.block.height,
|
|
||||||
price_feed,
|
|
||||||
attestation_time,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -488,10 +474,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||||
|
|
||||||
/// Get the most recent value of the price feed indicated by `feed_id`.
|
/// Get the most recent value of the price feed indicated by `feed_id`.
|
||||||
pub fn query_price_feed(deps: &Deps, feed_id: &[u8]) -> StdResult<PriceFeedResponse> {
|
pub fn query_price_feed(deps: &Deps, feed_id: &[u8]) -> StdResult<PriceFeedResponse> {
|
||||||
match price_info_read(deps.storage).load(feed_id) {
|
match price_feed_read_bucket(deps.storage).load(feed_id) {
|
||||||
Ok(price_info) => Ok(PriceFeedResponse {
|
Ok(price_feed) => Ok(PriceFeedResponse { price_feed }),
|
||||||
price_feed: price_info.price_feed,
|
|
||||||
}),
|
|
||||||
Err(_) => Err(PythContractError::PriceFeedNotFound)?,
|
Err(_) => Err(PythContractError::PriceFeedNotFound)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,6 +531,7 @@ mod test {
|
||||||
SystemResult,
|
SystemResult,
|
||||||
Uint128,
|
Uint128,
|
||||||
},
|
},
|
||||||
|
pyth_sdk::UnixTimestamp,
|
||||||
pyth_sdk_cw::PriceIdentifier,
|
pyth_sdk_cw::PriceIdentifier,
|
||||||
pyth_wormhole_attester_sdk::PriceAttestation,
|
pyth_wormhole_attester_sdk::PriceAttestation,
|
||||||
std::time::Duration,
|
std::time::Duration,
|
||||||
|
@ -656,7 +641,6 @@ mod test {
|
||||||
|
|
||||||
fn create_zero_config_info() -> ConfigInfo {
|
fn create_zero_config_info() -> ConfigInfo {
|
||||||
ConfigInfo {
|
ConfigInfo {
|
||||||
owner: Addr::unchecked(String::default()),
|
|
||||||
wormhole_contract: Addr::unchecked(String::default()),
|
wormhole_contract: Addr::unchecked(String::default()),
|
||||||
data_sources: HashSet::default(),
|
data_sources: HashSet::default(),
|
||||||
governance_source: PythDataSource {
|
governance_source: PythDataSource {
|
||||||
|
@ -671,11 +655,12 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_price_feed(expo: i32) -> PriceFeed {
|
fn create_price_feed(expo: i32, publish_time: UnixTimestamp) -> PriceFeed {
|
||||||
PriceFeed::new(
|
PriceFeed::new(
|
||||||
PriceIdentifier::new([0u8; 32]),
|
PriceIdentifier::new([0u8; 32]),
|
||||||
Price {
|
Price {
|
||||||
expo,
|
expo,
|
||||||
|
publish_time,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Price {
|
Price {
|
||||||
|
@ -695,21 +680,10 @@ mod test {
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the price feed with the given attestation time stamp and
|
/// Updates the price feed with the given publish time stamp and
|
||||||
/// returns the update status (true means updated, false means ignored)
|
/// returns the update status (true means updated, false means ignored)
|
||||||
fn do_update_price_feed(
|
fn do_update_price_feed(deps: &mut DepsMut, env: &Env, price_feed: PriceFeed) -> bool {
|
||||||
deps: &mut DepsMut,
|
update_price_feed_if_new(deps, env, price_feed).unwrap()
|
||||||
env: &Env,
|
|
||||||
price_feed: PriceFeed,
|
|
||||||
attestation_time_seconds: u64,
|
|
||||||
) -> bool {
|
|
||||||
update_price_feed_if_new(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
price_feed,
|
|
||||||
Timestamp::from_seconds(attestation_time_seconds),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_price_update(
|
fn apply_price_update(
|
||||||
|
@ -854,10 +828,9 @@ mod test {
|
||||||
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let stored_price_feed = price_info_read(&deps.storage)
|
let stored_price_feed = price_feed_read_bucket(&deps.storage)
|
||||||
.load(&[0u8; 32])
|
.load(&[0u8; 32])
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
let price = stored_price_feed.get_price_unchecked();
|
let price = stored_price_feed.get_price_unchecked();
|
||||||
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
||||||
|
|
||||||
|
@ -907,10 +880,9 @@ mod test {
|
||||||
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let stored_price_feed = price_info_read(&deps.storage)
|
let stored_price_feed = price_feed_read_bucket(&deps.storage)
|
||||||
.load(&[0u8; 32])
|
.load(&[0u8; 32])
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
let price = stored_price_feed.get_price_unchecked();
|
let price = stored_price_feed.get_price_unchecked();
|
||||||
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
||||||
|
|
||||||
|
@ -1005,15 +977,14 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_price_feed_if_new_first_price_ok() {
|
fn test_update_price_feed_if_new_first_price_ok() {
|
||||||
let (mut deps, env) = setup_test();
|
let (mut deps, env) = setup_test();
|
||||||
let price_feed = create_price_feed(3);
|
let price_feed = create_price_feed(3, 100);
|
||||||
|
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, price_feed, 100);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, price_feed);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
||||||
let stored_price_feed = price_info(&mut deps.storage)
|
let stored_price_feed = price_feed_bucket(&mut deps.storage)
|
||||||
.load(price_feed.id.as_ref())
|
.load(price_feed.id.as_ref())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
|
|
||||||
assert_eq!(stored_price_feed, price_feed);
|
assert_eq!(stored_price_feed, price_feed);
|
||||||
}
|
}
|
||||||
|
@ -1021,20 +992,18 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_price_feed_if_new_ignore_duplicate_time() {
|
fn test_update_price_feed_if_new_ignore_duplicate_time() {
|
||||||
let (mut deps, env) = setup_test();
|
let (mut deps, env) = setup_test();
|
||||||
let time = 100;
|
|
||||||
|
|
||||||
let first_price_feed = create_price_feed(3);
|
let first_price_feed = create_price_feed(3, 100);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, time);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
||||||
let second_price_feed = create_price_feed(4);
|
let second_price_feed = create_price_feed(4, 100);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, time);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed);
|
||||||
assert!(!changed);
|
assert!(!changed);
|
||||||
|
|
||||||
let stored_price_feed = price_info(&mut deps.storage)
|
let stored_price_feed = price_feed_bucket(&mut deps.storage)
|
||||||
.load(first_price_feed.id.as_ref())
|
.load(first_price_feed.id.as_ref())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
assert_eq!(stored_price_feed, first_price_feed);
|
assert_eq!(stored_price_feed, first_price_feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1042,18 +1011,17 @@ mod test {
|
||||||
fn test_update_price_feed_if_new_ignore_older() {
|
fn test_update_price_feed_if_new_ignore_older() {
|
||||||
let (mut deps, env) = setup_test();
|
let (mut deps, env) = setup_test();
|
||||||
|
|
||||||
let first_price_feed = create_price_feed(3);
|
let first_price_feed = create_price_feed(3, 100);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, 100);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
||||||
let second_price_feed = create_price_feed(4);
|
let second_price_feed = create_price_feed(4, 90);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, 90);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed);
|
||||||
assert!(!changed);
|
assert!(!changed);
|
||||||
|
|
||||||
let stored_price_feed = price_info(&mut deps.storage)
|
let stored_price_feed = price_feed_bucket(&mut deps.storage)
|
||||||
.load(first_price_feed.id.as_ref())
|
.load(first_price_feed.id.as_ref())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
assert_eq!(stored_price_feed, first_price_feed);
|
assert_eq!(stored_price_feed, first_price_feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1061,18 +1029,17 @@ mod test {
|
||||||
fn test_update_price_feed_if_new_accept_newer() {
|
fn test_update_price_feed_if_new_accept_newer() {
|
||||||
let (mut deps, env) = setup_test();
|
let (mut deps, env) = setup_test();
|
||||||
|
|
||||||
let first_price_feed = create_price_feed(3);
|
let first_price_feed = create_price_feed(3, 100);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed, 100);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, first_price_feed);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
||||||
let second_price_feed = create_price_feed(4);
|
let second_price_feed = create_price_feed(4, 110);
|
||||||
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed, 110);
|
let changed = do_update_price_feed(&mut deps.as_mut(), &env, second_price_feed);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
||||||
let stored_price_feed = price_info(&mut deps.storage)
|
let stored_price_feed = price_feed_bucket(&mut deps.storage)
|
||||||
.load(first_price_feed.id.as_ref())
|
.load(first_price_feed.id.as_ref())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.price_feed;
|
|
||||||
assert_eq!(stored_price_feed, second_price_feed);
|
assert_eq!(stored_price_feed, second_price_feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,8 +1049,7 @@ mod test {
|
||||||
|
|
||||||
let address = b"123".as_ref();
|
let address = b"123".as_ref();
|
||||||
|
|
||||||
let dummy_price_info = PriceInfo {
|
let dummy_price_feed = PriceFeed::new(
|
||||||
price_feed: PriceFeed::new(
|
|
||||||
PriceIdentifier::new([0u8; 32]),
|
PriceIdentifier::new([0u8; 32]),
|
||||||
Price {
|
Price {
|
||||||
price: 300,
|
price: 300,
|
||||||
|
@ -1094,12 +1060,9 @@ mod test {
|
||||||
Price {
|
Price {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
..Default::default()
|
price_feed_bucket(&mut deps.storage)
|
||||||
};
|
.save(address, &dummy_price_feed)
|
||||||
|
|
||||||
price_info(&mut deps.storage)
|
|
||||||
.save(address, &dummy_price_info)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let price_feed = query_price_feed(&deps.as_ref(), address)
|
let price_feed = query_price_feed(&deps.as_ref(), address)
|
||||||
|
|
|
@ -4,7 +4,6 @@ use {
|
||||||
Binary,
|
Binary,
|
||||||
Coin,
|
Coin,
|
||||||
Storage,
|
Storage,
|
||||||
Timestamp,
|
|
||||||
},
|
},
|
||||||
cosmwasm_storage::{
|
cosmwasm_storage::{
|
||||||
bucket,
|
bucket,
|
||||||
|
@ -29,7 +28,7 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static CONFIG_KEY: &[u8] = b"config";
|
pub static CONFIG_KEY: &[u8] = b"config";
|
||||||
pub static PRICE_INFO_KEY: &[u8] = b"price_info_v4";
|
pub static PRICE_FEED_KEY: &[u8] = b"price_feed";
|
||||||
|
|
||||||
/// A `PythDataSource` identifies a specific contract (given by its Wormhole `emitter`) on
|
/// A `PythDataSource` identifies a specific contract (given by its Wormhole `emitter`) on
|
||||||
/// a specific blockchain (given by `chain_id`).
|
/// a specific blockchain (given by `chain_id`).
|
||||||
|
@ -41,7 +40,6 @@ pub struct PythDataSource {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
pub struct ConfigInfo {
|
pub struct ConfigInfo {
|
||||||
pub owner: Addr,
|
|
||||||
pub wormhole_contract: Addr,
|
pub wormhole_contract: Addr,
|
||||||
pub data_sources: HashSet<PythDataSource>,
|
pub data_sources: HashSet<PythDataSource>,
|
||||||
pub governance_source: PythDataSource,
|
pub governance_source: PythDataSource,
|
||||||
|
@ -65,15 +63,6 @@ pub struct ConfigInfo {
|
||||||
pub fee: Coin,
|
pub fee: Coin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub struct PriceInfo {
|
|
||||||
pub arrival_time: Timestamp,
|
|
||||||
pub arrival_block: u64,
|
|
||||||
pub attestation_time: Timestamp,
|
|
||||||
pub price_feed: PriceFeed,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
|
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
|
||||||
singleton(storage, CONFIG_KEY)
|
singleton(storage, CONFIG_KEY)
|
||||||
}
|
}
|
||||||
|
@ -82,10 +71,10 @@ pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
|
||||||
singleton_read(storage, CONFIG_KEY)
|
singleton_read(storage, CONFIG_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn price_info(storage: &mut dyn Storage) -> Bucket<PriceInfo> {
|
pub fn price_feed_bucket(storage: &mut dyn Storage) -> Bucket<PriceFeed> {
|
||||||
bucket(storage, PRICE_INFO_KEY)
|
bucket(storage, PRICE_FEED_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn price_info_read(storage: &dyn Storage) -> ReadonlyBucket<PriceInfo> {
|
pub fn price_feed_read_bucket(storage: &dyn Storage) -> ReadonlyBucket<PriceFeed> {
|
||||||
bucket_read(storage, PRICE_INFO_KEY)
|
bucket_read(storage, PRICE_FEED_KEY)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue