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::{ use cosmwasm_std::{
entry_point, entry_point,
to_binary, to_binary,
@ -11,6 +13,7 @@ use cosmwasm_std::{
StdResult, StdResult,
Timestamp, Timestamp,
WasmQuery, WasmQuery,
StdError,
}; };
use pyth_sdk::{ use pyth_sdk::{
@ -35,6 +38,7 @@ use crate::state::{
ConfigInfo, ConfigInfo,
PriceInfo, PriceInfo,
VALID_TIME_PERIOD, VALID_TIME_PERIOD,
PythDataSource,
}; };
use p2w_sdk::BatchPriceAttestation; use p2w_sdk::BatchPriceAttestation;
@ -52,14 +56,17 @@ 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.to_string(),
wormhole_contract: msg.wormhole_contract, 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, pyth_emitter_chain: msg.pyth_emitter_chain,
}]),
}; };
config(deps.storage).save(&state)?; 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> { pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg { match msg {
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data), 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) 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 // This checks the emitter to be the pyth emitter in wormhole and it comes from emitter chain
// (Solana) // (Solana)
fn verify_vaa_sender(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> { 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(); return ContractError::InvalidVAA.std_err();
} }
Ok(()) Ok(())
@ -246,6 +320,7 @@ mod test {
MockApi, MockApi,
MockQuerier, MockQuerier,
MockStorage, MockStorage,
mock_info,
}; };
use cosmwasm_std::OwnedDeps; use cosmwasm_std::OwnedDeps;
@ -277,6 +352,15 @@ mod test {
price_feed 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 /// Updates the price feed with the given attestation 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(
@ -297,8 +381,7 @@ mod test {
#[test] #[test]
fn test_verify_vaa_sender_ok() { fn test_verify_vaa_sender_ok() {
let config_info = ConfigInfo { let config_info = ConfigInfo {
pyth_emitter: vec![1u8], data_sources: create_data_sources(vec![1u8], 3),
pyth_emitter_chain: 3,
..Default::default() ..Default::default()
}; };
@ -312,8 +395,7 @@ mod test {
#[test] #[test]
fn test_verify_vaa_sender_fail_wrong_emitter_address() { fn test_verify_vaa_sender_fail_wrong_emitter_address() {
let config_info = ConfigInfo { let config_info = ConfigInfo {
pyth_emitter: vec![1u8], data_sources: create_data_sources(vec![1u8], 3),
pyth_emitter_chain: 3,
..Default::default() ..Default::default()
}; };
@ -329,8 +411,7 @@ mod test {
#[test] #[test]
fn test_verify_vaa_sender_fail_wrong_emitter_chain() { fn test_verify_vaa_sender_fail_wrong_emitter_chain() {
let config_info = ConfigInfo { let config_info = ConfigInfo {
pyth_emitter: vec![1u8], data_sources: create_data_sources(vec![1u8], 3),
pyth_emitter_chain: 3,
..Default::default() ..Default::default()
}; };
@ -517,4 +598,107 @@ mod test {
ContractError::AssetNotFound.std_err() 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, Serialize,
}; };
use crate::state::PythDataSource;
type HumanAddr = String; type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@ -22,6 +24,8 @@ pub struct InstantiateMsg {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ExecuteMsg { pub enum ExecuteMsg {
SubmitVaa { data: Binary }, SubmitVaa { data: Binary },
AddDataSource { data_source: PythDataSource },
RemoveDataSource { data_source: PythDataSource },
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[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 pyth_sdk::PriceFeed;
use schemars::JsonSchema; use schemars::JsonSchema;
@ -10,6 +13,7 @@ use serde::{
use cosmwasm_std::{ use cosmwasm_std::{
Storage, Storage,
Timestamp, Timestamp,
Binary,
}; };
use cosmwasm_storage::{ 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. /// This value considers attestation delay which currently might up to a minute.
pub const VALID_TIME_PERIOD: Duration = Duration::from_secs(3 * 60); 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 // Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)]
pub struct ConfigInfo { pub struct ConfigInfo {
pub owner: HumanAddr,
pub wormhole_contract: HumanAddr, pub wormhole_contract: HumanAddr,
pub pyth_emitter: Vec<u8>, pub data_sources: HashSet<PythDataSource>,
pub pyth_emitter_chain: u16,
} }