Enable multiple datasource in cw contract (#247)

This commit is contained in:
Ali Behjati 2022-08-02 14:00:32 +04:30 committed by GitHub
parent d5186f8d56
commit 2361793147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 211 additions and 13 deletions

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use cosmwasm_std::{
entry_point,
to_binary,
@ -11,6 +13,7 @@ use cosmwasm_std::{
StdResult,
Timestamp,
WasmQuery,
StdError,
};
use pyth_sdk::{
@ -35,6 +38,7 @@ use crate::state::{
ConfigInfo,
PriceInfo,
VALID_TIME_PERIOD,
PythDataSource,
};
use p2w_sdk::BatchPriceAttestation;
@ -52,14 +56,17 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Respons
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole and pyth info
let state = ConfigInfo {
owner: info.sender.to_string(),
wormhole_contract: msg.wormhole_contract,
pyth_emitter: msg.pyth_emitter.as_slice().to_vec(),
data_sources: HashSet::from([PythDataSource {
emitter: msg.pyth_emitter,
pyth_emitter_chain: msg.pyth_emitter_chain,
}]),
};
config(deps.storage).save(&state)?;
@ -82,6 +89,8 @@ pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<Par
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
ExecuteMsg::AddDataSource { data_source } => add_data_source(deps, env, info, data_source ),
ExecuteMsg::RemoveDataSource { data_source } => remove_data_source(deps, env, info, data_source ),
}
}
@ -104,10 +113,75 @@ fn submit_vaa(
process_batch_attestation(deps, env, &batch_attestation)
}
fn add_data_source(
deps: DepsMut,
_env: Env,
info: MessageInfo,
data_source: PythDataSource,
) -> StdResult<Response> {
let mut state = config_read(deps.storage).load()?;
if state.owner != info.sender {
return ContractError::PermissionDenied.std_err();
}
if state.data_sources.insert(data_source.clone()) == false {
return Err(StdError::GenericErr { msg: format!("Data source already exists") });
}
config(deps.storage).save(&state)?;
Ok(Response::new()
.add_attribute("action", "add_data_source")
.add_attribute(
"data_source_emitter",
format!("{}", data_source.emitter),
)
.add_attribute(
"data_source_emitter_chain",
format!("{}", data_source.pyth_emitter_chain)
))
}
fn remove_data_source(
deps: DepsMut,
_env: Env,
info: MessageInfo,
data_source: PythDataSource,
) -> StdResult<Response> {
let mut state = config_read(deps.storage).load()?;
if state.owner != info.sender {
return ContractError::PermissionDenied.std_err();
}
if state.data_sources.remove(&data_source) == false {
return Err(StdError::GenericErr { msg: format!("Data source does not exist") });
}
config(deps.storage).save(&state)?;
Ok(Response::new()
.add_attribute("action", "remove_data_source")
.add_attribute(
"data_source_emitter",
format!("{}", data_source.emitter),
)
.add_attribute(
"data_source_emitter_chain",
format!("{}", data_source.pyth_emitter_chain)
))
}
// This checks the emitter to be the pyth emitter in wormhole and it comes from emitter chain
// (Solana)
fn verify_vaa_sender(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
if vaa.emitter_address != state.pyth_emitter || vaa.emitter_chain != state.pyth_emitter_chain {
let vaa_data_source = PythDataSource {
emitter: vaa.emitter_address.clone().into(),
pyth_emitter_chain: vaa.emitter_chain
};
if !state.data_sources.contains(&vaa_data_source) {
return ContractError::InvalidVAA.std_err();
}
Ok(())
@ -246,6 +320,7 @@ mod test {
MockApi,
MockQuerier,
MockStorage,
mock_info,
};
use cosmwasm_std::OwnedDeps;
@ -277,6 +352,15 @@ mod test {
price_feed
}
fn create_data_sources(pyth_emitter: Vec<u8>, pyth_emitter_chain: u16) -> HashSet<PythDataSource> {
HashSet::from([
PythDataSource {
emitter: pyth_emitter.into(),
pyth_emitter_chain
}
])
}
/// Updates the price feed with the given attestation time stamp and
/// returns the update status (true means updated, false means ignored)
fn do_update_price_feed(
@ -297,8 +381,7 @@ mod test {
#[test]
fn test_verify_vaa_sender_ok() {
let config_info = ConfigInfo {
pyth_emitter: vec![1u8],
pyth_emitter_chain: 3,
data_sources: create_data_sources(vec![1u8], 3),
..Default::default()
};
@ -312,8 +395,7 @@ mod test {
#[test]
fn test_verify_vaa_sender_fail_wrong_emitter_address() {
let config_info = ConfigInfo {
pyth_emitter: vec![1u8],
pyth_emitter_chain: 3,
data_sources: create_data_sources(vec![1u8], 3),
..Default::default()
};
@ -329,8 +411,7 @@ mod test {
#[test]
fn test_verify_vaa_sender_fail_wrong_emitter_chain() {
let config_info = ConfigInfo {
pyth_emitter: vec![1u8],
pyth_emitter_chain: 3,
data_sources: create_data_sources(vec![1u8], 3),
..Default::default()
};
@ -517,4 +598,107 @@ mod test {
ContractError::AssetNotFound.std_err()
);
}
#[test]
fn test_add_data_source_ok_with_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
..Default::default()
}).unwrap();
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
// Adding an existing data source should result an error
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_err());
}
#[test]
fn test_add_data_source_err_without_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
..Default::default()
}).unwrap();
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
assert!(add_data_source(deps.as_mut(), env, mock_info("321", &[]), data_source).is_err());
}
#[test]
fn test_remove_data_source_ok_with_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
data_sources: create_data_sources(vec![1u8], 1),
..Default::default()
}).unwrap();
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
// Removing a non existent data source should result an error
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_err());
}
#[test]
fn test_remove_data_source_err_without_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
data_sources: create_data_sources(vec![1u8], 1),
..Default::default()
}).unwrap();
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
assert!(remove_data_source(deps.as_mut(), env, mock_info("321", &[]), data_source).is_err());
}
#[test]
fn test_verify_vaa_works_after_adding_data_source() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
..Default::default()
}).unwrap();
let mut vaa = create_zero_vaa();
vaa.emitter_address = vec![1u8];
vaa.emitter_chain = 3;
// Should result an error because there is no data source
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), ContractError::InvalidVAA.std_err());
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 3 };
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), Ok(()));
}
#[test]
fn test_verify_vaa_err_after_removing_data_source() {
let (mut deps, env) = setup_test();
config(&mut deps.storage).save(&ConfigInfo {
owner: String::from("123"),
data_sources: create_data_sources(vec![1u8], 3),
..Default::default()
}).unwrap();
let mut vaa = create_zero_vaa();
vaa.emitter_address = vec![1u8];
vaa.emitter_chain = 3;
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), Ok(()));
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 3 };
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
// Should result an error because data source should not exist anymore
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), ContractError::InvalidVAA.std_err());
}
}

View File

@ -9,6 +9,8 @@ use serde::{
Serialize,
};
use crate::state::PythDataSource;
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@ -22,6 +24,8 @@ pub struct InstantiateMsg {
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SubmitVaa { data: Binary },
AddDataSource { data_source: PythDataSource },
RemoveDataSource { data_source: PythDataSource },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]

View File

@ -1,4 +1,7 @@
use std::time::Duration;
use std::{
time::Duration,
collections::HashSet
};
use pyth_sdk::PriceFeed;
use schemars::JsonSchema;
@ -10,6 +13,7 @@ use serde::{
use cosmwasm_std::{
Storage,
Timestamp,
Binary,
};
use cosmwasm_storage::{
@ -33,12 +37,18 @@ pub static PRICE_INFO_KEY: &[u8] = b"price_info_v3";
/// This value considers attestation delay which currently might up to a minute.
pub const VALID_TIME_PERIOD: Duration = Duration::from_secs(3 * 60);
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, JsonSchema)]
pub struct PythDataSource {
pub emitter: Binary,
pub pyth_emitter_chain: u16,
}
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)]
pub struct ConfigInfo {
pub owner: HumanAddr,
pub wormhole_contract: HumanAddr,
pub pyth_emitter: Vec<u8>,
pub pyth_emitter_chain: u16,
pub data_sources: HashSet<PythDataSource>,
}