[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 <bahjatia@gmail.com>
This commit is contained in:
Stanisław Drozd 2022-07-13 15:57:16 +02:00 committed by GitHub
parent 3d62f2b4f5
commit 9dbfbd4174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 246 additions and 0 deletions

View File

@ -106,4 +106,14 @@ pub enum Action {
#[clap(long = "is-active")]
is_active: Option<bool>,
},
#[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,
},
}

View File

@ -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<Transaction, ErrBox> {
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::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().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,

View File

@ -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,

View File

@ -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(())
}