diff --git a/cosmwasm/contracts/pyth/src/contract.rs b/cosmwasm/contracts/pyth/src/contract.rs index 2a8be888..f09ddca3 100644 --- a/cosmwasm/contracts/pyth/src/contract.rs +++ b/cosmwasm/contracts/pyth/src/contract.rs @@ -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 { - 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 { - 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()); + } } diff --git a/cosmwasm/contracts/pyth/src/governance.rs b/cosmwasm/contracts/pyth/src/governance.rs index 06cf108d..cfbdeb21 100644 --- a/cosmwasm/contracts/pyth/src/governance.rs +++ b/cosmwasm/contracts/pyth/src/governance.rs @@ -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 }, // 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::()?; let action: Result = 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 = 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 = vec![]; + for _ in 0..num_data_sources { + let chain_id = bytes.read_u16::()?; + 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::()?; let expo = bytes.read_u64::()?; 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::()?; + Ok(SetValidPeriod { valid_seconds }) + } + 5 => { + let governance_data_source_index = bytes.read_u32::()?; + 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::(self.target_chain_id)?; + buf.write_all(address)?; } - GovernanceAction::AuthorizeGovernanceDataSourceTransfer => { + GovernanceAction::AuthorizeGovernanceDataSourceTransfer { claim_vaa } => { buf.write_u8(1)?; buf.write_u16::(self.target_chain_id)?; + buf.write_all(claim_vaa.as_slice())?; } - GovernanceAction::SetDataSources => { + GovernanceAction::SetDataSources { data_sources } => { buf.write_u8(2)?; buf.write_u16::(self.target_chain_id)?; + buf.write_u8(u8::try_from(data_sources.len())?)?; + for data_source in data_sources { + buf.write_u16::(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::(*new_valid_period)?; } - GovernanceAction::RequestGovernanceDataSourceTransfer => { + GovernanceAction::RequestGovernanceDataSourceTransfer { + governance_data_source_index, + } => { buf.write_u8(5)?; buf.write_u16::(self.target_chain_id)?; + buf.write_u32::(*governance_data_source_index)?; } } diff --git a/cosmwasm/contracts/pyth/src/msg.rs b/cosmwasm/contracts/pyth/src/msg.rs index f63f008e..d34fe4e3 100644 --- a/cosmwasm/contracts/pyth/src/msg.rs +++ b/cosmwasm/contracts/pyth/src/msg.rs @@ -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 }, } diff --git a/cosmwasm/contracts/pyth/src/state.rs b/cosmwasm/contracts/pyth/src/state.rs index 4f022a31..d12d86eb 100644 --- a/cosmwasm/contracts/pyth/src/state.rs +++ b/cosmwasm/contracts/pyth/src/state.rs @@ -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, 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. diff --git a/cosmwasm/tools/deploy.js b/cosmwasm/tools/deploy.js index c812a5ee..16bf0aa1 100644 --- a/cosmwasm/tools/deploy.js +++ b/cosmwasm/tools/deploy.js @@ -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,