[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:
parent
9ac61742c3
commit
9c79ab8862
|
@ -2,7 +2,14 @@ use {
|
|||
crate::{
|
||||
error::PythContractError,
|
||||
governance::{
|
||||
GovernanceAction::SetFee,
|
||||
GovernanceAction::{
|
||||
AuthorizeGovernanceDataSourceTransfer,
|
||||
RequestGovernanceDataSourceTransfer,
|
||||
SetDataSources,
|
||||
SetFee,
|
||||
SetValidPeriod,
|
||||
UpgradeContract,
|
||||
},
|
||||
GovernanceInstruction,
|
||||
},
|
||||
msg::{
|
||||
|
@ -52,6 +59,7 @@ use {
|
|||
std::{
|
||||
collections::HashSet,
|
||||
convert::TryFrom,
|
||||
iter::FromIterator,
|
||||
time::Duration,
|
||||
},
|
||||
wormhole::{
|
||||
|
@ -85,6 +93,7 @@ pub fn instantiate(
|
|||
emitter: msg.governance_emitter,
|
||||
pyth_emitter_chain: msg.governance_emitter_chain,
|
||||
},
|
||||
governance_source_index: msg.governance_source_index,
|
||||
governance_sequence_number: msg.governance_sequence_number,
|
||||
valid_time_period: Duration::from_secs(msg.valid_time_period_secs as u64),
|
||||
fee: msg.fee,
|
||||
|
@ -114,11 +123,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
|
|||
ExecuteMsg::ExecuteGovernanceInstruction { 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 {
|
||||
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 } => {
|
||||
updated_config.fee = Uint128::new(
|
||||
(val as u128)
|
||||
|
@ -191,7 +252,18 @@ fn execute_governance_instruction(
|
|||
.add_attribute("action", "set_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)?;
|
||||
|
@ -199,61 +271,6 @@ fn execute_governance_instruction(
|
|||
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).
|
||||
fn verify_vaa_from_data_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
|
||||
let vaa_data_source = PythDataSource {
|
||||
|
@ -565,6 +582,7 @@ mod test {
|
|||
emitter: Binary(vec![]),
|
||||
pyth_emitter_chain: 0,
|
||||
},
|
||||
governance_source_index: 0,
|
||||
governance_sequence_number: 0,
|
||||
chain_id: 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
|
||||
/// against it. Returns the response of the governance instruction along with the resulting config.
|
||||
fn apply_governance_vaa(
|
||||
|
@ -1188,6 +1045,166 @@ mod test {
|
|||
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]
|
||||
fn test_set_fee() {
|
||||
let mut test_config = governance_test_config();
|
||||
|
@ -1217,4 +1234,38 @@ mod test {
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
use {
|
||||
crate::{
|
||||
governance::GovernanceAction::{
|
||||
RequestGovernanceDataSourceTransfer,
|
||||
SetValidPeriod,
|
||||
},
|
||||
state::PythDataSource,
|
||||
},
|
||||
byteorder::{
|
||||
BigEndian,
|
||||
ReadBytesExt,
|
||||
WriteBytesExt,
|
||||
},
|
||||
cosmwasm_std::Binary,
|
||||
p2w_sdk::ErrBox,
|
||||
schemars::JsonSchema,
|
||||
serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
},
|
||||
std::io::Write,
|
||||
std::{
|
||||
convert::TryFrom,
|
||||
io::Write,
|
||||
},
|
||||
};
|
||||
|
||||
const PYTH_GOVERNANCE_MAGIC: &[u8] = b"PTGM";
|
||||
|
@ -46,14 +57,14 @@ impl GovernanceModule {
|
|||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[repr(u8)]
|
||||
pub enum GovernanceAction {
|
||||
UpgradeContract, // 0
|
||||
AuthorizeGovernanceDataSourceTransfer, // 1
|
||||
SetDataSources, // 2
|
||||
UpgradeContract { address: [u8; 20] }, // 0
|
||||
AuthorizeGovernanceDataSourceTransfer { claim_vaa: Binary }, // 1
|
||||
SetDataSources { data_sources: Vec<PythDataSource> }, // 2
|
||||
// Set the fee to val * (10 ** expo)
|
||||
SetFee { val: u64, expo: u64 }, // 3
|
||||
// Set the default valid period to the provided number of seconds
|
||||
SetValidPeriod { valid_seconds: u64 }, // 4
|
||||
RequestGovernanceDataSourceTransfer, // 5
|
||||
RequestGovernanceDataSourceTransfer { governance_data_source_index: u32 }, // 5
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
|
@ -86,13 +97,50 @@ impl GovernanceInstruction {
|
|||
let target_chain_id: u16 = bytes.read_u16::<BigEndian>()?;
|
||||
|
||||
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 => {
|
||||
let val = bytes.read_u64::<BigEndian>()?;
|
||||
let expo = bytes.read_u64::<BigEndian>()?;
|
||||
Ok(GovernanceAction::SetFee { val, expo })
|
||||
}
|
||||
// TODO: add parsing for additional actions
|
||||
_ => Err(format!("Bad governance action {action_type}",)),
|
||||
4 => {
|
||||
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 {
|
||||
|
@ -109,17 +157,32 @@ impl GovernanceInstruction {
|
|||
buf.write_u8(self.module.to_u8())?;
|
||||
|
||||
match &self.action {
|
||||
GovernanceAction::UpgradeContract => {
|
||||
GovernanceAction::UpgradeContract { address } => {
|
||||
buf.write_u8(0)?;
|
||||
buf.write_u16::<BigEndian>(self.target_chain_id)?;
|
||||
buf.write_all(address)?;
|
||||
}
|
||||
GovernanceAction::AuthorizeGovernanceDataSourceTransfer => {
|
||||
GovernanceAction::AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
|
||||
buf.write_u8(1)?;
|
||||
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_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 } => {
|
||||
buf.write_u8(3)?;
|
||||
|
@ -136,9 +199,12 @@ impl GovernanceInstruction {
|
|||
|
||||
buf.write_u64::<BigEndian>(*new_valid_period)?;
|
||||
}
|
||||
GovernanceAction::RequestGovernanceDataSourceTransfer => {
|
||||
GovernanceAction::RequestGovernanceDataSourceTransfer {
|
||||
governance_data_source_index,
|
||||
} => {
|
||||
buf.write_u8(5)?;
|
||||
buf.write_u16::<BigEndian>(self.target_chain_id)?;
|
||||
buf.write_u32::<BigEndian>(*governance_data_source_index)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use {
|
||||
crate::state::PythDataSource,
|
||||
cosmwasm_std::{
|
||||
Binary,
|
||||
Uint128,
|
||||
|
@ -26,6 +25,7 @@ pub struct InstantiateMsg {
|
|||
pub pyth_emitter_chain: u16,
|
||||
pub governance_emitter: Binary,
|
||||
pub governance_emitter_chain: u16,
|
||||
pub governance_source_index: u32,
|
||||
pub governance_sequence_number: u64,
|
||||
pub chain_id: u16,
|
||||
pub valid_time_period_secs: u16,
|
||||
|
@ -39,8 +39,6 @@ pub struct InstantiateMsg {
|
|||
pub enum ExecuteMsg {
|
||||
// TODO: add UpdatePriceFeeds if necessary
|
||||
UpdatePriceFeeds { data: Binary },
|
||||
AddDataSource { data_source: PythDataSource },
|
||||
RemoveDataSource { data_source: PythDataSource },
|
||||
ExecuteGovernanceInstruction { data: Binary },
|
||||
}
|
||||
|
||||
|
|
|
@ -37,13 +37,22 @@ pub struct PythDataSource {
|
|||
pub pyth_emitter_chain: u16,
|
||||
}
|
||||
|
||||
// Guardian set information
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ConfigInfo {
|
||||
pub owner: Addr,
|
||||
pub wormhole_contract: Addr,
|
||||
pub data_sources: HashSet<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,
|
||||
// FIXME: This id needs to agree with the wormhole chain id.
|
||||
// We should read this directly from wormhole.
|
||||
|
|
|
@ -180,6 +180,7 @@ addresses["pyth_cosmwasm.wasm"] = await instantiate(
|
|||
"hex"
|
||||
).toString("base64"),
|
||||
governance_emitter_chain: pythChain,
|
||||
governance_source_index: 0,
|
||||
governance_sequence_number: 0,
|
||||
chain_id: 3,
|
||||
valid_time_period_secs: 60,
|
||||
|
|
Loading…
Reference in New Issue