diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 178d135d47..5d711ba518 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -68,8 +68,8 @@ pub mod pyth_solana_receiver { Ok(()) } - pub fn authorize_governance_authority_transfer( - ctx: Context, + pub fn accept_governance_authority_transfer( + ctx: Context, ) -> Result<()> { let config = &mut ctx.accounts.config; config.governance_authority = config.target_governance_authority.ok_or(error!( @@ -244,13 +244,13 @@ pub struct Governance<'info> { } #[derive(Accounts)] -pub struct AuthorizeGovernanceAuthorityTransfer<'info> { +pub struct AcceptGovernanceAuthorityTransfer<'info> { #[account(constraint = payer.key() == config.target_governance_authority.ok_or(error!(ReceiverError::NonexistentGovernanceAuthorityTransferRequest))? @ ReceiverError::TargetGovernanceAuthorityMismatch )] pub payer: Signer<'info>, - #[account(seeds = [CONFIG_SEED.as_ref()], bump)] + #[account(mut, seeds = [CONFIG_SEED.as_ref()], bump)] pub config: Account<'info, Config>, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs index b5e1abeedd..58228d7b40 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -81,6 +81,13 @@ impl accounts::Governance { } } +impl accounts::AcceptGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey) -> Self { + let config = get_config_address(); + accounts::AcceptGovernanceAuthorityTransfer { payer, config } + } +} + impl instruction::Initialize { pub fn populate(payer: &Pubkey, initial_config: Config) -> Instruction { Instruction { @@ -173,6 +180,56 @@ impl instruction::SetFee { } +impl instruction::SetWormholeAddress { + pub fn populate(payer: Pubkey, wormhole: Pubkey) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetWormholeAddress { wormhole }.data(), + } + } +} + + +impl instruction::SetMinimumSignatures { + pub fn populate(payer: Pubkey, minimum_signatures: u8) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetMinimumSignatures { minimum_signatures }.data(), + } + } +} + +impl instruction::RequestGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey, target_governance_authority: Pubkey) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::RequestGovernanceAuthorityTransfer { + target_governance_authority, + } + .data(), + } + } +} + +impl instruction::AcceptGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey) -> Instruction { + let governance_accounts = + accounts::AcceptGovernanceAuthorityTransfer::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::AcceptGovernanceAuthorityTransfer {}.data(), + } + } +} + + pub fn get_treasury_address() -> Pubkey { Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0 } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs new file mode 100644 index 0000000000..df4998844c --- /dev/null +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs @@ -0,0 +1,406 @@ +use { + crate::common::WrongSetupOption, + common::{ + setup_pyth_receiver, + ProgramTestFixtures, + }, + program_simulator::into_transation_error, + pyth_solana_receiver::{ + error::ReceiverError, + instruction::{ + AcceptGovernanceAuthorityTransfer, + RequestGovernanceAuthorityTransfer, + SetDataSources, + SetFee, + SetMinimumSignatures, + SetWormholeAddress, + }, + sdk::get_config_address, + state::config::{ + Config, + DataSource, + }, + }, + pythnet_sdk::test_utils::SECONDARY_DATA_SOURCE, + solana_program::{ + native_token::LAMPORTS_PER_SOL, + pubkey::Pubkey, + }, + solana_sdk::signer::Signer, +}; + +mod common; + + +#[tokio::test] +async fn test_governance() { + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + governance_authority, + } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; + + let new_governance_authority = program_simulator.get_funded_keypair().await.unwrap(); + + let initial_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + + let new_config = Config { + governance_authority: new_governance_authority.pubkey(), + target_governance_authority: None, + wormhole: Pubkey::new_unique(), + valid_data_sources: vec![DataSource { + chain: SECONDARY_DATA_SOURCE.chain.into(), + emitter: Pubkey::from(SECONDARY_DATA_SOURCE.address.0), + }], + single_update_fee_in_lamports: LAMPORTS_PER_SOL, + minimum_signatures: 20, + }; + + + // this authority is not allowed to do anything + assert_eq!( + program_simulator + .process_ix( + SetDataSources::populate( + new_governance_authority.pubkey(), + new_config.valid_data_sources.clone() + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetFee::populate( + new_governance_authority.pubkey(), + new_config.single_update_fee_in_lamports + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetWormholeAddress::populate( + new_governance_authority.pubkey(), + new_config.wormhole + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetMinimumSignatures::populate( + new_governance_authority.pubkey(), + new_config.minimum_signatures, + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + RequestGovernanceAuthorityTransfer::populate( + new_governance_authority.pubkey(), + new_config.governance_authority, + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(), + initial_config + ); + + + // Now start changing for real + program_simulator + .process_ix( + SetDataSources::populate( + governance_authority.pubkey(), + new_config.valid_data_sources.clone(), + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + let mut current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, initial_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + initial_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetFee::populate( + governance_authority.pubkey(), + new_config.single_update_fee_in_lamports, + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, initial_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetWormholeAddress::populate(governance_authority.pubkey(), new_config.wormhole), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetMinimumSignatures::populate( + governance_authority.pubkey(), + new_config.minimum_signatures, + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); + + // Target is not defined yet + assert_eq!( + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(new_governance_authority.pubkey()), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::NonexistentGovernanceAuthorityTransferRequest) + ); + + // Request transfer + program_simulator + .process_ix( + RequestGovernanceAuthorityTransfer::populate( + governance_authority.pubkey(), + new_governance_authority.pubkey(), + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!( + current_config.target_governance_authority, + Some(new_governance_authority.pubkey()) + ); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + // Random guy can't accept + + assert_eq!( + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(poster.pubkey()), + &vec![&poster], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::TargetGovernanceAuthorityMismatch) + ); + + // New authority can accept + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(new_governance_authority.pubkey()), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!(current_config, new_config); + + // Now the new authority can do stuff + program_simulator + .process_ix( + SetFee::populate(new_governance_authority.pubkey(), 9), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + new_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!(current_config.single_update_fee_in_lamports, 9); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); +}