[cosmos] Implement the other governance instructions (#442)

* initial commit for governance instructions

* merge

* docs

* update deployment code

Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com>
This commit is contained in:
Jayant Krishnamurthy 2022-12-29 06:02:43 -08:00 committed by GitHub
parent 9ac61742c3
commit 9c79ab8862
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 363 additions and 238 deletions

View File

@ -2,7 +2,14 @@ use {
crate::{ crate::{
error::PythContractError, error::PythContractError,
governance::{ governance::{
GovernanceAction::SetFee, GovernanceAction::{
AuthorizeGovernanceDataSourceTransfer,
RequestGovernanceDataSourceTransfer,
SetDataSources,
SetFee,
SetValidPeriod,
UpgradeContract,
},
GovernanceInstruction, GovernanceInstruction,
}, },
msg::{ msg::{
@ -52,6 +59,7 @@ use {
std::{ std::{
collections::HashSet, collections::HashSet,
convert::TryFrom, convert::TryFrom,
iter::FromIterator,
time::Duration, time::Duration,
}, },
wormhole::{ wormhole::{
@ -85,6 +93,7 @@ pub fn instantiate(
emitter: msg.governance_emitter, emitter: msg.governance_emitter,
pyth_emitter_chain: msg.governance_emitter_chain, pyth_emitter_chain: msg.governance_emitter_chain,
}, },
governance_source_index: msg.governance_source_index,
governance_sequence_number: msg.governance_sequence_number, governance_sequence_number: msg.governance_sequence_number,
valid_time_period: Duration::from_secs(msg.valid_time_period_secs as u64), valid_time_period: Duration::from_secs(msg.valid_time_period_secs as u64),
fee: msg.fee, fee: msg.fee,
@ -114,11 +123,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
ExecuteMsg::ExecuteGovernanceInstruction { data } => { ExecuteMsg::ExecuteGovernanceInstruction { data } => {
execute_governance_instruction(deps, env, info, &data) execute_governance_instruction(deps, env, info, &data)
} }
// TODO: remove these and invoke via governance
ExecuteMsg::AddDataSource { data_source } => add_data_source(deps, env, info, data_source),
ExecuteMsg::RemoveDataSource { data_source } => {
remove_data_source(deps, env, info, data_source)
}
} }
} }
@ -173,6 +177,63 @@ fn execute_governance_instruction(
} }
let response = match instruction.action { let response = match instruction.action {
UpgradeContract { .. } => {
// FIXME: implement this
Err(PythContractError::InvalidGovernancePayload)?
}
AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
let parsed_claim_vaa = parse_vaa(deps.branch(), env.block.time.seconds(), &claim_vaa)?;
let claim_vaa_instruction =
GovernanceInstruction::deserialize(parsed_claim_vaa.payload.as_slice())
.map_err(|_| PythContractError::InvalidGovernancePayload)?;
if claim_vaa_instruction.target_chain_id != state.chain_id
&& claim_vaa_instruction.target_chain_id != 0
{
Err(PythContractError::InvalidGovernancePayload)?
}
match claim_vaa_instruction.action {
RequestGovernanceDataSourceTransfer {
governance_data_source_index,
} => {
if state.governance_source_index >= governance_data_source_index {
Err(PythContractError::OldGovernanceMessage)?
}
updated_config.governance_source_index = governance_data_source_index;
let new_governance_source = PythDataSource {
emitter: Binary::from(parsed_claim_vaa.emitter_address.clone()),
pyth_emitter_chain: parsed_claim_vaa.emitter_chain,
};
updated_config.governance_source = new_governance_source;
updated_config.governance_sequence_number = parsed_claim_vaa.sequence;
Response::new()
.add_attribute("action", "authorize_governance_data_source_transfer")
.add_attribute(
"new_governance_emitter_address",
format!("{:?}", parsed_claim_vaa.emitter_address),
)
.add_attribute(
"new_governance_emitter_chain",
format!("{}", parsed_claim_vaa.emitter_chain),
)
.add_attribute(
"new_governance_sequence_number",
format!("{}", parsed_claim_vaa.sequence),
)
}
_ => Err(PythContractError::InvalidGovernancePayload)?,
}
}
SetDataSources { data_sources } => {
updated_config.data_sources = HashSet::from_iter(data_sources.iter().cloned());
Response::new()
.add_attribute("action", "set_data_sources")
.add_attribute("new_data_sources", format!("{data_sources:?}"))
}
SetFee { val, expo } => { SetFee { val, expo } => {
updated_config.fee = Uint128::new( updated_config.fee = Uint128::new(
(val as u128) (val as u128)
@ -191,7 +252,18 @@ fn execute_governance_instruction(
.add_attribute("action", "set_fee") .add_attribute("action", "set_fee")
.add_attribute("new_fee", format!("{}", updated_config.fee)) .add_attribute("new_fee", format!("{}", updated_config.fee))
} }
_ => Err(PythContractError::InvalidGovernancePayload)?, SetValidPeriod { valid_seconds } => {
updated_config.valid_time_period = Duration::from_secs(valid_seconds);
Response::new()
.add_attribute("action", "set_valid_period")
.add_attribute("new_valid_seconds", format!("{valid_seconds}"))
}
RequestGovernanceDataSourceTransfer { .. } => {
// RequestGovernanceDataSourceTransfer can only be part of the
// AuthorizeGovernanceDataSourceTransfer message.
Err(PythContractError::InvalidGovernancePayload)?
}
}; };
config(deps.storage).save(&updated_config)?; config(deps.storage).save(&updated_config)?;
@ -199,61 +271,6 @@ fn execute_governance_instruction(
Ok(response) Ok(response)
} }
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 Err(PythContractError::PermissionDenied)?;
}
if !state.data_sources.insert(data_source.clone()) {
return Err(PythContractError::DataSourceAlreadyExists)?;
}
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 Err(PythContractError::PermissionDenied)?;
}
if !state.data_sources.remove(&data_source) {
return Err(PythContractError::DataSourceDoesNotExists)?;
}
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),
))
}
/// Check that `vaa` is from a valid data source (and hence is a legitimate price update message). /// Check that `vaa` is from a valid data source (and hence is a legitimate price update message).
fn verify_vaa_from_data_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> { fn verify_vaa_from_data_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
let vaa_data_source = PythDataSource { let vaa_data_source = PythDataSource {
@ -565,6 +582,7 @@ mod test {
emitter: Binary(vec![]), emitter: Binary(vec![]),
pyth_emitter_chain: 0, pyth_emitter_chain: 0,
}, },
governance_source_index: 0,
governance_sequence_number: 0, governance_sequence_number: 0,
chain_id: 0, chain_id: 0,
valid_time_period: Duration::new(0, 0), valid_time_period: Duration::new(0, 0),
@ -927,167 +945,6 @@ mod test {
); );
} }
#[test]
fn test_add_data_source_ok_with_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage)
.save(&ConfigInfo {
owner: Addr::unchecked("123"),
..create_zero_config_info()
})
.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, mock_info("123", &[]), data_source).is_err());
}
#[test]
fn test_add_data_source_err_without_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage)
.save(&ConfigInfo {
owner: Addr::unchecked("123"),
..create_zero_config_info()
})
.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: Addr::unchecked("123"),
data_sources: create_data_sources(vec![1u8], 1),
..create_zero_config_info()
})
.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, mock_info("123", &[]), data_source).is_err()
);
}
#[test]
fn test_remove_data_source_err_without_owner() {
let (mut deps, env) = setup_test();
config(&mut deps.storage)
.save(&ConfigInfo {
owner: Addr::unchecked("123"),
data_sources: create_data_sources(vec![1u8], 1),
..create_zero_config_info()
})
.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: Addr::unchecked("123"),
..create_zero_config_info()
})
.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_from_data_source(&config_read(&deps.storage).load().unwrap(), &vaa),
Err(PythContractError::InvalidUpdateEmitter.into())
);
let data_source = PythDataSource {
emitter: vec![1u8].into(),
pyth_emitter_chain: 3,
};
assert!(add_data_source(deps.as_mut(), env, mock_info("123", &[]), data_source).is_ok());
assert_eq!(
verify_vaa_from_data_source(&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: Addr::unchecked("123"),
data_sources: create_data_sources(vec![1u8], 3),
..create_zero_config_info()
})
.unwrap();
let mut vaa = create_zero_vaa();
vaa.emitter_address = vec![1u8];
vaa.emitter_chain = 3;
assert_eq!(
verify_vaa_from_data_source(&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, mock_info("123", &[]), data_source).is_ok());
// Should result an error because data source should not exist anymore
assert_eq!(
verify_vaa_from_data_source(&config_read(&deps.storage).load().unwrap(), &vaa),
Err(PythContractError::InvalidUpdateEmitter.into())
);
}
/// Initialize the contract with `initial_config` then execute `vaa` as a governance instruction /// Initialize the contract with `initial_config` then execute `vaa` as a governance instruction
/// against it. Returns the response of the governance instruction along with the resulting config. /// against it. Returns the response of the governance instruction along with the resulting config.
fn apply_governance_vaa( fn apply_governance_vaa(
@ -1188,6 +1045,166 @@ mod test {
assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err()); assert!(apply_governance_vaa(&test_config, &vaa_copy).is_err());
} }
#[test]
fn test_authorize_governance_transfer_success() {
let source_2 = PythDataSource {
emitter: Binary::from([2u8; 32]),
pyth_emitter_chain: 4,
};
let test_config = governance_test_config();
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: AuthorizeGovernanceDataSourceTransfer {
claim_vaa: to_binary(&ParsedVAA {
emitter_address: source_2.emitter.to_vec(),
emitter_chain: source_2.pyth_emitter_chain,
sequence: 12,
payload: GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: RequestGovernanceDataSourceTransfer {
governance_data_source_index: 11,
},
}
.serialize()
.unwrap(),
..create_zero_vaa()
})
.unwrap(),
},
};
let test_vaa = governance_vaa(&test_instruction);
let (_response, result_config) = apply_governance_vaa(&test_config, &test_vaa).unwrap();
assert_eq!(result_config.governance_source, source_2);
assert_eq!(result_config.governance_source_index, 11);
assert_eq!(result_config.governance_sequence_number, 12);
}
#[test]
fn test_authorize_governance_transfer_bad_source_index() {
let source_2 = PythDataSource {
emitter: Binary::from([2u8; 32]),
pyth_emitter_chain: 4,
};
let mut test_config = governance_test_config();
test_config.governance_source_index = 10;
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: AuthorizeGovernanceDataSourceTransfer {
claim_vaa: to_binary(&ParsedVAA {
emitter_address: source_2.emitter.to_vec(),
emitter_chain: source_2.pyth_emitter_chain,
sequence: 12,
payload: GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: RequestGovernanceDataSourceTransfer {
governance_data_source_index: 10,
},
}
.serialize()
.unwrap(),
..create_zero_vaa()
})
.unwrap(),
},
};
let test_vaa = governance_vaa(&test_instruction);
assert_eq!(
apply_governance_vaa(&test_config, &test_vaa),
Err(PythContractError::OldGovernanceMessage.into())
);
}
#[test]
fn test_authorize_governance_transfer_bad_target_chain() {
let source_2 = PythDataSource {
emitter: Binary::from([2u8; 32]),
pyth_emitter_chain: 4,
};
let test_config = governance_test_config();
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: AuthorizeGovernanceDataSourceTransfer {
claim_vaa: to_binary(&ParsedVAA {
emitter_address: source_2.emitter.to_vec(),
emitter_chain: source_2.pyth_emitter_chain,
sequence: 12,
payload: GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id + 1,
action: RequestGovernanceDataSourceTransfer {
governance_data_source_index: 11,
},
}
.serialize()
.unwrap(),
..create_zero_vaa()
})
.unwrap(),
},
};
let test_vaa = governance_vaa(&test_instruction);
assert_eq!(
apply_governance_vaa(&test_config, &test_vaa),
Err(PythContractError::InvalidGovernancePayload.into())
);
}
#[test]
fn test_set_data_sources() {
let source_1 = PythDataSource {
emitter: Binary::from([1u8; 32]),
pyth_emitter_chain: 2,
};
let source_2 = PythDataSource {
emitter: Binary::from([2u8; 32]),
pyth_emitter_chain: 4,
};
let source_3 = PythDataSource {
emitter: Binary::from([3u8; 32]),
pyth_emitter_chain: 6,
};
let mut test_config = governance_test_config();
test_config.data_sources = HashSet::from([source_1]);
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: SetDataSources {
data_sources: vec![source_2.clone(), source_3.clone()],
},
};
let test_vaa = governance_vaa(&test_instruction);
assert_eq!(
apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.data_sources),
Ok([source_2, source_3].iter().cloned().collect())
);
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: SetDataSources {
data_sources: vec![],
},
};
let test_vaa = governance_vaa(&test_instruction);
assert_eq!(
apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.data_sources),
Ok(HashSet::new())
);
}
#[test] #[test]
fn test_set_fee() { fn test_set_fee() {
let mut test_config = governance_test_config(); let mut test_config = governance_test_config();
@ -1217,4 +1234,38 @@ mod test {
Ok(Uint128::new(6)) Ok(Uint128::new(6))
); );
} }
#[test]
fn test_set_valid_period() {
let mut test_config = governance_test_config();
test_config.valid_time_period = Duration::from_secs(10);
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: 5,
action: SetValidPeriod { valid_seconds: 20 },
};
let test_vaa = governance_vaa(&test_instruction);
assert_eq!(
apply_governance_vaa(&test_config, &test_vaa).map(|(_r, c)| c.valid_time_period),
Ok(Duration::from_secs(20))
);
}
#[test]
fn test_request_governance_transfer() {
let test_config = governance_test_config();
let test_instruction = GovernanceInstruction {
module: Target,
target_chain_id: test_config.chain_id,
action: RequestGovernanceDataSourceTransfer {
governance_data_source_index: 7,
},
};
let test_vaa = governance_vaa(&test_instruction);
assert!(apply_governance_vaa(&test_config, &test_vaa).is_err());
}
} }

View File

@ -1,16 +1,27 @@
use { use {
crate::{
governance::GovernanceAction::{
RequestGovernanceDataSourceTransfer,
SetValidPeriod,
},
state::PythDataSource,
},
byteorder::{ byteorder::{
BigEndian, BigEndian,
ReadBytesExt, ReadBytesExt,
WriteBytesExt, WriteBytesExt,
}, },
cosmwasm_std::Binary,
p2w_sdk::ErrBox, p2w_sdk::ErrBox,
schemars::JsonSchema, schemars::JsonSchema,
serde::{ serde::{
Deserialize, Deserialize,
Serialize, Serialize,
}, },
std::io::Write, std::{
convert::TryFrom,
io::Write,
},
}; };
const PYTH_GOVERNANCE_MAGIC: &[u8] = b"PTGM"; const PYTH_GOVERNANCE_MAGIC: &[u8] = b"PTGM";
@ -46,14 +57,14 @@ impl GovernanceModule {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[repr(u8)] #[repr(u8)]
pub enum GovernanceAction { pub enum GovernanceAction {
UpgradeContract, // 0 UpgradeContract { address: [u8; 20] }, // 0
AuthorizeGovernanceDataSourceTransfer, // 1 AuthorizeGovernanceDataSourceTransfer { claim_vaa: Binary }, // 1
SetDataSources, // 2 SetDataSources { data_sources: Vec<PythDataSource> }, // 2
// Set the fee to val * (10 ** expo) // Set the fee to val * (10 ** expo)
SetFee { val: u64, expo: u64 }, // 3 SetFee { val: u64, expo: u64 }, // 3
// Set the default valid period to the provided number of seconds // Set the default valid period to the provided number of seconds
SetValidPeriod { valid_seconds: u64 }, // 4 SetValidPeriod { valid_seconds: u64 }, // 4
RequestGovernanceDataSourceTransfer, // 5 RequestGovernanceDataSourceTransfer { governance_data_source_index: u32 }, // 5
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
@ -86,13 +97,50 @@ impl GovernanceInstruction {
let target_chain_id: u16 = bytes.read_u16::<BigEndian>()?; let target_chain_id: u16 = bytes.read_u16::<BigEndian>()?;
let action: Result<GovernanceAction, String> = match action_type { let action: Result<GovernanceAction, String> = match action_type {
0 => {
let mut address: [u8; 20] = [0; 20];
bytes.read_exact(&mut address)?;
Ok(GovernanceAction::UpgradeContract { address })
}
1 => {
let mut payload: Vec<u8> = vec![];
bytes.read_to_end(&mut payload)?;
Ok(GovernanceAction::AuthorizeGovernanceDataSourceTransfer {
claim_vaa: Binary::from(payload),
})
}
2 => {
let num_data_sources = bytes.read_u8()?;
let mut data_sources: Vec<PythDataSource> = vec![];
for _ in 0..num_data_sources {
let chain_id = bytes.read_u16::<BigEndian>()?;
let mut emitter_address: [u8; 32] = [0; 32];
bytes.read_exact(&mut emitter_address)?;
data_sources.push(PythDataSource {
emitter: Binary::from(&emitter_address),
pyth_emitter_chain: chain_id,
});
}
Ok(GovernanceAction::SetDataSources { data_sources })
}
3 => { 3 => {
let val = bytes.read_u64::<BigEndian>()?; let val = bytes.read_u64::<BigEndian>()?;
let expo = bytes.read_u64::<BigEndian>()?; let expo = bytes.read_u64::<BigEndian>()?;
Ok(GovernanceAction::SetFee { val, expo }) Ok(GovernanceAction::SetFee { val, expo })
} }
// TODO: add parsing for additional actions 4 => {
_ => Err(format!("Bad governance action {action_type}",)), let valid_seconds = bytes.read_u64::<BigEndian>()?;
Ok(SetValidPeriod { valid_seconds })
}
5 => {
let governance_data_source_index = bytes.read_u32::<BigEndian>()?;
Ok(RequestGovernanceDataSourceTransfer {
governance_data_source_index,
})
}
_ => Err(format!("Unknown governance action type: {action_type}",)),
}; };
Ok(GovernanceInstruction { Ok(GovernanceInstruction {
@ -109,17 +157,32 @@ impl GovernanceInstruction {
buf.write_u8(self.module.to_u8())?; buf.write_u8(self.module.to_u8())?;
match &self.action { match &self.action {
GovernanceAction::UpgradeContract => { GovernanceAction::UpgradeContract { address } => {
buf.write_u8(0)?; buf.write_u8(0)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?; buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_all(address)?;
} }
GovernanceAction::AuthorizeGovernanceDataSourceTransfer => { GovernanceAction::AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
buf.write_u8(1)?; buf.write_u8(1)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?; buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_all(claim_vaa.as_slice())?;
} }
GovernanceAction::SetDataSources => { GovernanceAction::SetDataSources { data_sources } => {
buf.write_u8(2)?; buf.write_u8(2)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?; buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_u8(u8::try_from(data_sources.len())?)?;
for data_source in data_sources {
buf.write_u16::<BigEndian>(data_source.pyth_emitter_chain)?;
// The message format expects emitter addresses to be 32 bytes.
// However, we don't maintain this invariant in the rust code (and we violate it in the tests).
// This check gives you a reasonable error message if you happen to violate it in the tests.
if data_source.emitter.len() != 32 {
Err("Emitter addresses must be 32 bytes")?
}
buf.write_all(data_source.emitter.as_slice())?;
}
} }
GovernanceAction::SetFee { val, expo } => { GovernanceAction::SetFee { val, expo } => {
buf.write_u8(3)?; buf.write_u8(3)?;
@ -136,9 +199,12 @@ impl GovernanceInstruction {
buf.write_u64::<BigEndian>(*new_valid_period)?; buf.write_u64::<BigEndian>(*new_valid_period)?;
} }
GovernanceAction::RequestGovernanceDataSourceTransfer => { GovernanceAction::RequestGovernanceDataSourceTransfer {
governance_data_source_index,
} => {
buf.write_u8(5)?; buf.write_u8(5)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?; buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_u32::<BigEndian>(*governance_data_source_index)?;
} }
} }

View File

@ -1,5 +1,4 @@
use { use {
crate::state::PythDataSource,
cosmwasm_std::{ cosmwasm_std::{
Binary, Binary,
Uint128, Uint128,
@ -26,6 +25,7 @@ pub struct InstantiateMsg {
pub pyth_emitter_chain: u16, pub pyth_emitter_chain: u16,
pub governance_emitter: Binary, pub governance_emitter: Binary,
pub governance_emitter_chain: u16, pub governance_emitter_chain: u16,
pub governance_source_index: u32,
pub governance_sequence_number: u64, pub governance_sequence_number: u64,
pub chain_id: u16, pub chain_id: u16,
pub valid_time_period_secs: u16, pub valid_time_period_secs: u16,
@ -39,8 +39,6 @@ pub struct InstantiateMsg {
pub enum ExecuteMsg { pub enum ExecuteMsg {
// TODO: add UpdatePriceFeeds if necessary // TODO: add UpdatePriceFeeds if necessary
UpdatePriceFeeds { data: Binary }, UpdatePriceFeeds { data: Binary },
AddDataSource { data_source: PythDataSource },
RemoveDataSource { data_source: PythDataSource },
ExecuteGovernanceInstruction { data: Binary }, ExecuteGovernanceInstruction { data: Binary },
} }

View File

@ -37,13 +37,22 @@ pub struct PythDataSource {
pub pyth_emitter_chain: u16, pub pyth_emitter_chain: u16,
} }
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ConfigInfo { pub struct ConfigInfo {
pub owner: Addr, 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,
// Index for preventing replay attacks on governance data source transfers.
// This index increases every time the governance data source is changed, which prevents old
// transfer request VAAs from being replayed.
pub governance_source_index: u32,
// The wormhole sequence number for governance messages. This value is increased every time the
// a governance instruction is executed.
//
// This field differs from the one above in that it is generated by wormhole and applicable to all
// governance messages, whereas the one above is generated by Pyth and only applicable to governance
// source transfers.
pub governance_sequence_number: u64, pub governance_sequence_number: u64,
// FIXME: This id needs to agree with the wormhole chain id. // FIXME: This id needs to agree with the wormhole chain id.
// We should read this directly from wormhole. // We should read this directly from wormhole.

View File

@ -180,6 +180,7 @@ addresses["pyth_cosmwasm.wasm"] = await instantiate(
"hex" "hex"
).toString("base64"), ).toString("base64"),
governance_emitter_chain: pythChain, governance_emitter_chain: pythChain,
governance_source_index: 0,
governance_sequence_number: 0, governance_sequence_number: 0,
chain_id: 3, chain_id: 3,
valid_time_period_secs: 60, valid_time_period_secs: 60,