Enable multiple datasource in cw contract (#247)
This commit is contained in:
parent
d5186f8d56
commit
2361793147
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue