Skip to content

Commit 2361793

Browse files
authored
Enable multiple datasource in cw contract (#247)
1 parent d5186f8 commit 2361793

File tree

3 files changed

+211
-13
lines changed

3 files changed

+211
-13
lines changed

cosmwasm/contracts/pyth-bridge/src/contract.rs

Lines changed: 194 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashSet;
2+
13
use cosmwasm_std::{
24
entry_point,
35
to_binary,
@@ -11,6 +13,7 @@ use cosmwasm_std::{
1113
StdResult,
1214
Timestamp,
1315
WasmQuery,
16+
StdError,
1417
};
1518

1619
use pyth_sdk::{
@@ -35,6 +38,7 @@ use crate::state::{
3538
ConfigInfo,
3639
PriceInfo,
3740
VALID_TIME_PERIOD,
41+
PythDataSource,
3842
};
3943

4044
use p2w_sdk::BatchPriceAttestation;
@@ -52,14 +56,17 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Respons
5256
pub fn instantiate(
5357
deps: DepsMut,
5458
_env: Env,
55-
_info: MessageInfo,
59+
info: MessageInfo,
5660
msg: InstantiateMsg,
5761
) -> StdResult<Response> {
5862
// Save general wormhole and pyth info
5963
let state = ConfigInfo {
64+
owner: info.sender.to_string(),
6065
wormhole_contract: msg.wormhole_contract,
61-
pyth_emitter: msg.pyth_emitter.as_slice().to_vec(),
62-
pyth_emitter_chain: msg.pyth_emitter_chain,
66+
data_sources: HashSet::from([PythDataSource {
67+
emitter: msg.pyth_emitter,
68+
pyth_emitter_chain: msg.pyth_emitter_chain,
69+
}]),
6370
};
6471
config(deps.storage).save(&state)?;
6572

@@ -82,6 +89,8 @@ pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<Par
8289
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
8390
match msg {
8491
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
92+
ExecuteMsg::AddDataSource { data_source } => add_data_source(deps, env, info, data_source ),
93+
ExecuteMsg::RemoveDataSource { data_source } => remove_data_source(deps, env, info, data_source ),
8594
}
8695
}
8796

@@ -104,10 +113,75 @@ fn submit_vaa(
104113
process_batch_attestation(deps, env, &batch_attestation)
105114
}
106115

116+
fn add_data_source(
117+
deps: DepsMut,
118+
_env: Env,
119+
info: MessageInfo,
120+
data_source: PythDataSource,
121+
) -> StdResult<Response> {
122+
let mut state = config_read(deps.storage).load()?;
123+
124+
if state.owner != info.sender {
125+
return ContractError::PermissionDenied.std_err();
126+
}
127+
128+
if state.data_sources.insert(data_source.clone()) == false {
129+
return Err(StdError::GenericErr { msg: format!("Data source already exists") });
130+
}
131+
132+
config(deps.storage).save(&state)?;
133+
134+
Ok(Response::new()
135+
.add_attribute("action", "add_data_source")
136+
.add_attribute(
137+
"data_source_emitter",
138+
format!("{}", data_source.emitter),
139+
)
140+
.add_attribute(
141+
"data_source_emitter_chain",
142+
format!("{}", data_source.pyth_emitter_chain)
143+
))
144+
}
145+
146+
fn remove_data_source(
147+
deps: DepsMut,
148+
_env: Env,
149+
info: MessageInfo,
150+
data_source: PythDataSource,
151+
) -> StdResult<Response> {
152+
let mut state = config_read(deps.storage).load()?;
153+
154+
if state.owner != info.sender {
155+
return ContractError::PermissionDenied.std_err();
156+
}
157+
158+
if state.data_sources.remove(&data_source) == false {
159+
return Err(StdError::GenericErr { msg: format!("Data source does not exist") });
160+
}
161+
162+
config(deps.storage).save(&state)?;
163+
164+
Ok(Response::new()
165+
.add_attribute("action", "remove_data_source")
166+
.add_attribute(
167+
"data_source_emitter",
168+
format!("{}", data_source.emitter),
169+
)
170+
.add_attribute(
171+
"data_source_emitter_chain",
172+
format!("{}", data_source.pyth_emitter_chain)
173+
))
174+
}
175+
176+
107177
// This checks the emitter to be the pyth emitter in wormhole and it comes from emitter chain
108178
// (Solana)
109179
fn verify_vaa_sender(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
110-
if vaa.emitter_address != state.pyth_emitter || vaa.emitter_chain != state.pyth_emitter_chain {
180+
let vaa_data_source = PythDataSource {
181+
emitter: vaa.emitter_address.clone().into(),
182+
pyth_emitter_chain: vaa.emitter_chain
183+
};
184+
if !state.data_sources.contains(&vaa_data_source) {
111185
return ContractError::InvalidVAA.std_err();
112186
}
113187
Ok(())
@@ -246,6 +320,7 @@ mod test {
246320
MockApi,
247321
MockQuerier,
248322
MockStorage,
323+
mock_info,
249324
};
250325
use cosmwasm_std::OwnedDeps;
251326

@@ -277,6 +352,15 @@ mod test {
277352
price_feed
278353
}
279354

355+
fn create_data_sources(pyth_emitter: Vec<u8>, pyth_emitter_chain: u16) -> HashSet<PythDataSource> {
356+
HashSet::from([
357+
PythDataSource {
358+
emitter: pyth_emitter.into(),
359+
pyth_emitter_chain
360+
}
361+
])
362+
}
363+
280364
/// Updates the price feed with the given attestation time stamp and
281365
/// returns the update status (true means updated, false means ignored)
282366
fn do_update_price_feed(
@@ -297,8 +381,7 @@ mod test {
297381
#[test]
298382
fn test_verify_vaa_sender_ok() {
299383
let config_info = ConfigInfo {
300-
pyth_emitter: vec![1u8],
301-
pyth_emitter_chain: 3,
384+
data_sources: create_data_sources(vec![1u8], 3),
302385
..Default::default()
303386
};
304387

@@ -312,8 +395,7 @@ mod test {
312395
#[test]
313396
fn test_verify_vaa_sender_fail_wrong_emitter_address() {
314397
let config_info = ConfigInfo {
315-
pyth_emitter: vec![1u8],
316-
pyth_emitter_chain: 3,
398+
data_sources: create_data_sources(vec![1u8], 3),
317399
..Default::default()
318400
};
319401

@@ -329,8 +411,7 @@ mod test {
329411
#[test]
330412
fn test_verify_vaa_sender_fail_wrong_emitter_chain() {
331413
let config_info = ConfigInfo {
332-
pyth_emitter: vec![1u8],
333-
pyth_emitter_chain: 3,
414+
data_sources: create_data_sources(vec![1u8], 3),
334415
..Default::default()
335416
};
336417

@@ -517,4 +598,107 @@ mod test {
517598
ContractError::AssetNotFound.std_err()
518599
);
519600
}
601+
602+
#[test]
603+
fn test_add_data_source_ok_with_owner() {
604+
let (mut deps, env) = setup_test();
605+
config(&mut deps.storage).save(&ConfigInfo {
606+
owner: String::from("123"),
607+
..Default::default()
608+
}).unwrap();
609+
610+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
611+
612+
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
613+
614+
// Adding an existing data source should result an error
615+
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_err());
616+
}
617+
618+
#[test]
619+
fn test_add_data_source_err_without_owner() {
620+
let (mut deps, env) = setup_test();
621+
config(&mut deps.storage).save(&ConfigInfo {
622+
owner: String::from("123"),
623+
..Default::default()
624+
}).unwrap();
625+
626+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
627+
628+
assert!(add_data_source(deps.as_mut(), env, mock_info("321", &[]), data_source).is_err());
629+
}
630+
631+
#[test]
632+
fn test_remove_data_source_ok_with_owner() {
633+
let (mut deps, env) = setup_test();
634+
config(&mut deps.storage).save(&ConfigInfo {
635+
owner: String::from("123"),
636+
data_sources: create_data_sources(vec![1u8], 1),
637+
..Default::default()
638+
}).unwrap();
639+
640+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
641+
642+
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
643+
644+
// Removing a non existent data source should result an error
645+
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_err());
646+
}
647+
648+
#[test]
649+
fn test_remove_data_source_err_without_owner() {
650+
let (mut deps, env) = setup_test();
651+
config(&mut deps.storage).save(&ConfigInfo {
652+
owner: String::from("123"),
653+
data_sources: create_data_sources(vec![1u8], 1),
654+
..Default::default()
655+
}).unwrap();
656+
657+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 1 };
658+
659+
assert!(remove_data_source(deps.as_mut(), env, mock_info("321", &[]), data_source).is_err());
660+
}
661+
662+
#[test]
663+
fn test_verify_vaa_works_after_adding_data_source() {
664+
let (mut deps, env) = setup_test();
665+
config(&mut deps.storage).save(&ConfigInfo {
666+
owner: String::from("123"),
667+
..Default::default()
668+
}).unwrap();
669+
670+
let mut vaa = create_zero_vaa();
671+
vaa.emitter_address = vec![1u8];
672+
vaa.emitter_chain = 3;
673+
674+
// Should result an error because there is no data source
675+
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), ContractError::InvalidVAA.std_err());
676+
677+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 3 };
678+
assert!(add_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
679+
680+
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), Ok(()));
681+
}
682+
683+
#[test]
684+
fn test_verify_vaa_err_after_removing_data_source() {
685+
let (mut deps, env) = setup_test();
686+
config(&mut deps.storage).save(&ConfigInfo {
687+
owner: String::from("123"),
688+
data_sources: create_data_sources(vec![1u8], 3),
689+
..Default::default()
690+
}).unwrap();
691+
692+
let mut vaa = create_zero_vaa();
693+
vaa.emitter_address = vec![1u8];
694+
vaa.emitter_chain = 3;
695+
696+
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), Ok(()));
697+
698+
let data_source = PythDataSource { emitter: vec![1u8].into(), pyth_emitter_chain: 3 };
699+
assert!(remove_data_source(deps.as_mut(), env.clone(), mock_info("123", &[]), data_source.clone()).is_ok());
700+
701+
// Should result an error because data source should not exist anymore
702+
assert_eq!(verify_vaa_sender(&config_read(&deps.storage).load().unwrap(), &vaa), ContractError::InvalidVAA.std_err());
703+
}
520704
}

cosmwasm/contracts/pyth-bridge/src/msg.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use serde::{
99
Serialize,
1010
};
1111

12+
use crate::state::PythDataSource;
13+
1214
type HumanAddr = String;
1315

1416
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -22,6 +24,8 @@ pub struct InstantiateMsg {
2224
#[serde(rename_all = "snake_case")]
2325
pub enum ExecuteMsg {
2426
SubmitVaa { data: Binary },
27+
AddDataSource { data_source: PythDataSource },
28+
RemoveDataSource { data_source: PythDataSource },
2529
}
2630

2731
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]

cosmwasm/contracts/pyth-bridge/src/state.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::time::Duration;
1+
use std::{
2+
time::Duration,
3+
collections::HashSet
4+
};
25

36
use pyth_sdk::PriceFeed;
47
use schemars::JsonSchema;
@@ -10,6 +13,7 @@ use serde::{
1013
use cosmwasm_std::{
1114
Storage,
1215
Timestamp,
16+
Binary,
1317
};
1418

1519
use cosmwasm_storage::{
@@ -33,12 +37,18 @@ pub static PRICE_INFO_KEY: &[u8] = b"price_info_v3";
3337
/// This value considers attestation delay which currently might up to a minute.
3438
pub const VALID_TIME_PERIOD: Duration = Duration::from_secs(3 * 60);
3539

40+
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, JsonSchema)]
41+
pub struct PythDataSource {
42+
pub emitter: Binary,
43+
pub pyth_emitter_chain: u16,
44+
}
45+
3646
// Guardian set information
3747
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)]
3848
pub struct ConfigInfo {
49+
pub owner: HumanAddr,
3950
pub wormhole_contract: HumanAddr,
40-
pub pyth_emitter: Vec<u8>,
41-
pub pyth_emitter_chain: u16,
51+
pub data_sources: HashSet<PythDataSource>,
4252
}
4353

4454

0 commit comments

Comments
 (0)