pyth-crosschain/target_chains/cosmwasm/contracts/pyth/src/governance.rs

264 lines
9.7 KiB
Rust

use {
crate::state::PythDataSource,
byteorder::{
BigEndian,
ReadBytesExt,
WriteBytesExt,
},
cosmwasm_std::Binary,
pyth_wormhole_attester_sdk::ErrBox,
schemars::JsonSchema,
serde::{
Deserialize,
Serialize,
},
std::{
convert::TryFrom,
io::Write,
},
};
const PYTH_GOVERNANCE_MAGIC: &[u8] = b"PTGM";
/// The type of contract that can accept a governance instruction.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[repr(u8)]
pub enum GovernanceModule {
/// The PythNet executor contract. Messages sent to the
Executor = 0,
/// A target chain contract (like this one!)
Target = 1,
}
impl GovernanceModule {
pub fn from_u8(x: u8) -> Result<GovernanceModule, ErrBox> {
match x {
0 => Ok(GovernanceModule::Executor),
1 => Ok(GovernanceModule::Target),
_ => Err(format!("Invalid governance module: {x}",).into()),
}
}
pub fn to_u8(&self) -> u8 {
match &self {
GovernanceModule::Executor => 0,
GovernanceModule::Target => 1,
}
}
}
/// The action to perform to change the state of the target chain contract.
///
/// Note that the order of the enum cannot be changed, as the integer representation of
/// each field must be preserved for backward compatibility.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[repr(u8)]
pub enum GovernanceAction {
/// Upgrade the code for the contract to the code uploaded at code_id
UpgradeContract { code_id: u64 }, // 0
/// This action is the second step of a governance handoff process.
/// The handoff is as follows:
/// 1. The new governance emitter creates a VAA containing a RequestGovernanceDataSourceTransfer action
/// 2. The existing governance emitter creates a AuthorizeGovernanceDataSourceTransfer message where
/// claim_vaa is the VAA from step 1.
/// 3. The VAA from step 2 is submitted to the contract.
///
/// This 2-step process ensures that the new emitter is able to send VAAs before the transfer
/// is completed.
AuthorizeGovernanceDataSourceTransfer { claim_vaa: Binary }, // 1
/// Set the set of authorized emitters for price update messages.
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
/// The first step of the governance handoff process (see documentation
/// on AuthorizeGovernanceDataSourceTransfer). `governance_data_source_index` is an incrementing
/// sequence number that ensures old transfer messages cannot be replayed.
RequestGovernanceDataSourceTransfer { governance_data_source_index: u32 }, // 5
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct GovernanceInstruction {
pub module: GovernanceModule,
pub action: GovernanceAction,
pub target_chain_id: u16,
}
impl GovernanceInstruction {
pub fn deserialize(mut bytes: impl ReadBytesExt) -> Result<Self, ErrBox> {
let mut magic_vec = vec![0u8; PYTH_GOVERNANCE_MAGIC.len()];
bytes.read_exact(magic_vec.as_mut_slice())?;
if magic_vec.as_slice() != PYTH_GOVERNANCE_MAGIC {
return Err(format!(
"Invalid magic {magic_vec:02X?}, expected {PYTH_GOVERNANCE_MAGIC:02X?}",
)
.into());
}
let module_num = bytes.read_u8()?;
let module = GovernanceModule::from_u8(module_num)?;
let action_type: u8 = bytes.read_u8()?;
let target_chain_id: u16 = bytes.read_u16::<BigEndian>()?;
let action: Result<GovernanceAction, String> = match action_type {
0 => {
let code_id = bytes.read_u64::<BigEndian>()?;
Ok(GovernanceAction::UpgradeContract { code_id })
}
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),
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 })
}
4 => {
let valid_seconds = bytes.read_u64::<BigEndian>()?;
Ok(GovernanceAction::SetValidPeriod { valid_seconds })
}
5 => {
let governance_data_source_index = bytes.read_u32::<BigEndian>()?;
Ok(GovernanceAction::RequestGovernanceDataSourceTransfer {
governance_data_source_index,
})
}
_ => Err(format!("Unknown governance action type: {action_type}",)),
};
// Check that we're at the end of the buffer (to ensure that this contract knows how to
// interpret every field in the governance message). The logic is a little janky
// but seems to be the simplest way to check that the reader is at EOF.
let mut next_byte = [0_u8; 1];
let read_result = bytes.read(&mut next_byte);
match read_result {
Ok(0) => (),
_ => Err("Governance action had an unexpectedly long payload.".to_string())?,
}
Ok(GovernanceInstruction {
module,
action: action?,
target_chain_id,
})
}
pub fn serialize(&self) -> Result<Vec<u8>, ErrBox> {
let mut buf = vec![];
buf.write_all(PYTH_GOVERNANCE_MAGIC)?;
buf.write_u8(self.module.to_u8())?;
match &self.action {
GovernanceAction::UpgradeContract { code_id } => {
buf.write_u8(0)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_u64::<BigEndian>(*code_id)?;
}
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 { 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.chain_id)?;
// 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)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_u64::<BigEndian>(*val)?;
buf.write_u64::<BigEndian>(*expo)?;
}
GovernanceAction::SetValidPeriod {
valid_seconds: new_valid_period,
} => {
buf.write_u8(4)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_u64::<BigEndian>(*new_valid_period)?;
}
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)?;
}
}
Ok(buf)
}
}
#[cfg(test)]
mod test {
use crate::governance::{
GovernanceAction,
GovernanceInstruction,
GovernanceModule,
};
#[test]
fn test_payload_wrong_size() {
let instruction = GovernanceInstruction {
module: GovernanceModule::Target,
action: GovernanceAction::SetFee {
val: 100,
expo: 200,
},
target_chain_id: 7,
};
let mut buf: Vec<u8> = instruction.serialize().unwrap();
let result = GovernanceInstruction::deserialize(buf.as_slice());
assert!(result.is_ok());
assert_eq!(result.unwrap(), instruction);
buf.push(0);
let result = GovernanceInstruction::deserialize(buf.as_slice());
assert!(result.is_err());
let result = GovernanceInstruction::deserialize(&buf[0..buf.len() - 2]);
assert!(result.is_err());
}
}