From 9dbfbd4174f7ecf9a6471c214c0277ead7a2508b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Drozd?= Date: Wed, 13 Jul 2022 15:57:16 +0200 Subject: [PATCH] [WIP] p2w-client: Implement a migrate command and instruction generator (#232) * p2w-client: Implement a migrate command and instruction generator * Fix minor bug in gen_migrate_tx * Fix migration test and add a test for success Co-authored-by: Ali Behjati --- solana/pyth2wormhole/client/src/cli.rs | 10 + solana/pyth2wormhole/client/src/lib.rs | 34 ++++ solana/pyth2wormhole/client/src/main.rs | 15 ++ .../client/tests/test_migrate.rs | 187 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 solana/pyth2wormhole/client/tests/test_migrate.rs diff --git a/solana/pyth2wormhole/client/src/cli.rs b/solana/pyth2wormhole/client/src/cli.rs index 093d5fef..7391c613 100644 --- a/solana/pyth2wormhole/client/src/cli.rs +++ b/solana/pyth2wormhole/client/src/cli.rs @@ -106,4 +106,14 @@ pub enum Action { #[clap(long = "is-active")] is_active: Option, }, + #[clap(about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract.")] + Migrate { + /// owner keypair path + #[clap( + long, + default_value = "~/.config/solana/id.json", + help = "Keypair file for the current config owner" + )] + owner: String, + }, } diff --git a/solana/pyth2wormhole/client/src/lib.rs b/solana/pyth2wormhole/client/src/lib.rs index 96b997ad..1d2879b7 100644 --- a/solana/pyth2wormhole/client/src/lib.rs +++ b/solana/pyth2wormhole/client/src/lib.rs @@ -48,6 +48,7 @@ use pyth2wormhole::{ attest::P2W_MAX_BATCH_SIZE, config::P2WConfigAccount, initialize::InitializeAccounts, + migrate::MigrateAccounts, set_config::SetConfigAccounts, AttestData, Pyth2WormholeConfig, @@ -124,6 +125,39 @@ pub fn gen_set_config_tx( Ok(tx_signed) } +pub fn gen_migrate_tx( + payer: Keypair, + p2w_addr: Pubkey, + owner: Keypair, + latest_blockhash: Hash, +) -> Result { + use AccEntry::*; + + let payer_pubkey = payer.pubkey(); + + let accs = MigrateAccounts { + new_config: Derived(p2w_addr), + old_config: DerivedRO(p2w_addr), + current_owner: Signer(owner), + payer: Signer(payer), + }; + + let ix_data = ( + pyth2wormhole::instruction::Instruction::Migrate, + (), + ); + + let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?; + + let tx_signed = Transaction::new_signed_with_payer::>( + &[ix], + Some(&payer_pubkey), + signers.iter().collect::>().as_ref(), + latest_blockhash, + ); + Ok(tx_signed) +} + /// Get the current config account data for given p2w program address pub fn get_config_account( rpc_client: &RpcClient, diff --git a/solana/pyth2wormhole/client/src/main.rs b/solana/pyth2wormhole/client/src/main.rs index 90ea4a71..c7d56f61 100644 --- a/solana/pyth2wormhole/client/src/main.rs +++ b/solana/pyth2wormhole/client/src/main.rs @@ -111,6 +111,21 @@ fn main() -> Result<(), ErrBox> { get_config_account(&rpc_client, &p2w_addr)? ); } + Action::Migrate { + ref owner, + } => { + let tx = gen_migrate_tx( + payer, + p2w_addr, + read_keypair_file(&*shellexpand::tilde(&owner))?, + latest_blockhash, + )?; + rpc_client.send_and_confirm_transaction_with_spinner(&tx)?; + println!( + "Applied conifg:\n{:?}", + get_config_account(&rpc_client, &p2w_addr)? + ); + } Action::Attest { ref attestation_cfg, n_retries, diff --git a/solana/pyth2wormhole/client/tests/test_migrate.rs b/solana/pyth2wormhole/client/tests/test_migrate.rs new file mode 100644 index 00000000..9fe2ca2e --- /dev/null +++ b/solana/pyth2wormhole/client/tests/test_migrate.rs @@ -0,0 +1,187 @@ +//! Checks for migrating the previous config schema into the current one + +pub mod fixtures; + +use solana_program::system_program; +use solana_program_test::*; +use solana_sdk::{ + account::Account, + instruction::{ + AccountMeta, + Instruction, + }, + pubkey::Pubkey, + rent::Rent, + signature::Signer, + signer::keypair::Keypair, + transaction::Transaction, +}; + +use bridge::accounts::{ + Bridge, + BridgeConfig, + BridgeData, +}; + +use log::info; + +use pyth2wormhole::config::{ + OldP2WConfigAccount, + P2WConfigAccount, + OldPyth2WormholeConfig, + Pyth2WormholeConfig, +}; +use pyth2wormhole_client as p2wc; +use solitaire::{ + processors::seeded::Seeded, + AccountState, + BorshSerialize, +}; + +use fixtures::{ + passthrough, + pyth, +}; + +#[tokio::test] +async fn test_migrate_works() -> Result<(), solitaire::ErrBox> { + info!("Starting"); + // Programs + let p2w_program_id = Pubkey::new_unique(); + let wh_fixture_program_id = Pubkey::new_unique(); + + // Authorities + let p2w_owner = Keypair::new(); + let pyth_owner = Pubkey::new_unique(); + + // On-chain state + let old_p2w_config = OldPyth2WormholeConfig {owner: p2w_owner.pubkey(), + wh_prog: wh_fixture_program_id, + max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE, + pyth_owner, + }; + + info!("Before ProgramTest::new()"); + + // Populate test environment + let mut p2w_test = ProgramTest::new( + "pyth2wormhole", + p2w_program_id, + processor!(pyth2wormhole::instruction::solitaire), + ); + + // Plant filled config accounts + let old_p2w_config_bytes = old_p2w_config.try_to_vec()?; + let old_p2w_config_account = Account { + lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()), + data: old_p2w_config_bytes, + owner: p2w_program_id, + executable: false, + rent_epoch: 0, + }; + let old_p2w_config_addr = + OldP2WConfigAccount::key(None, &p2w_program_id); + + info!("Before add_account() calls"); + + p2w_test.add_account(old_p2w_config_addr, old_p2w_config_account); + + // Add system program because the contract creates an account for new configuration account + passthrough::add_passthrough(&mut p2w_test, "system", system_program::id()); + + info!("Before start_with_context"); + let mut ctx = p2w_test.start_with_context().await; + + let migrate_tx = p2wc::gen_migrate_tx( + ctx.payer, + p2w_program_id, + p2w_owner, + ctx.last_blockhash, + )?; + info!("Before process_transaction"); + + // Migration should fail because the new config account is already initialized + ctx.banks_client.process_transaction(migrate_tx).await?; + + Ok(()) +} + + +#[tokio::test] +async fn test_migrate_already_migrated() -> Result<(), solitaire::ErrBox> { + info!("Starting"); + // Programs + let p2w_program_id = Pubkey::new_unique(); + let wh_fixture_program_id = Pubkey::new_unique(); + + // Authorities + let p2w_owner = Keypair::new(); + let pyth_owner = Pubkey::new_unique(); + + // On-chain state + let old_p2w_config = OldPyth2WormholeConfig {owner: p2w_owner.pubkey(), + wh_prog: wh_fixture_program_id, + max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE, + pyth_owner, + }; + + let new_p2w_config = Pyth2WormholeConfig {owner: p2w_owner.pubkey(), + wh_prog: wh_fixture_program_id, + max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE, + pyth_owner, + is_active: true, + }; + + info!("Before ProgramTest::new()"); + + // Populate test environment + let mut p2w_test = ProgramTest::new( + "pyth2wormhole", + p2w_program_id, + processor!(pyth2wormhole::instruction::solitaire), + ); + + // Plant filled config accounts + let old_p2w_config_bytes = old_p2w_config.try_to_vec()?; + let old_p2w_config_account = Account { + lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()), + data: old_p2w_config_bytes, + owner: p2w_program_id, + executable: false, + rent_epoch: 0, + }; + let old_p2w_config_addr = + OldP2WConfigAccount::key(None, &p2w_program_id); + + let new_p2w_config_bytes = new_p2w_config.try_to_vec()?; + let new_p2w_config_account = Account { + lamports: Rent::default().minimum_balance(new_p2w_config_bytes.len()), + data: new_p2w_config_bytes, + owner: p2w_program_id, + executable: false, + rent_epoch: 0, + }; + let new_p2w_config_addr = + P2WConfigAccount::<{AccountState::Initialized}>::key(None, &p2w_program_id); + + info!("Before add_account() calls"); + + p2w_test.add_account(old_p2w_config_addr, old_p2w_config_account); + p2w_test.add_account(new_p2w_config_addr, new_p2w_config_account); + + info!("Before start_with_context"); + let mut ctx = p2w_test.start_with_context().await; + + let migrate_tx = p2wc::gen_migrate_tx( + ctx.payer, + p2w_program_id, + p2w_owner, + ctx.last_blockhash, + )?; + info!("Before process_transaction"); + + // Migration should fail because the new config account is already initialized + assert!(ctx.banks_client.process_transaction(migrate_tx).await.is_err()); + + Ok(()) +}