Skip to content

Commit 9dbfbd4

Browse files
Stanisław Drozdali-behjati
andauthored
[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 <[email protected]>
1 parent 3d62f2b commit 9dbfbd4

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed

solana/pyth2wormhole/client/src/cli.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,14 @@ pub enum Action {
106106
#[clap(long = "is-active")]
107107
is_active: Option<bool>,
108108
},
109+
#[clap(about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract.")]
110+
Migrate {
111+
/// owner keypair path
112+
#[clap(
113+
long,
114+
default_value = "~/.config/solana/id.json",
115+
help = "Keypair file for the current config owner"
116+
)]
117+
owner: String,
118+
},
109119
}

solana/pyth2wormhole/client/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use pyth2wormhole::{
4848
attest::P2W_MAX_BATCH_SIZE,
4949
config::P2WConfigAccount,
5050
initialize::InitializeAccounts,
51+
migrate::MigrateAccounts,
5152
set_config::SetConfigAccounts,
5253
AttestData,
5354
Pyth2WormholeConfig,
@@ -124,6 +125,39 @@ pub fn gen_set_config_tx(
124125
Ok(tx_signed)
125126
}
126127

128+
pub fn gen_migrate_tx(
129+
payer: Keypair,
130+
p2w_addr: Pubkey,
131+
owner: Keypair,
132+
latest_blockhash: Hash,
133+
) -> Result<Transaction, ErrBox> {
134+
use AccEntry::*;
135+
136+
let payer_pubkey = payer.pubkey();
137+
138+
let accs = MigrateAccounts {
139+
new_config: Derived(p2w_addr),
140+
old_config: DerivedRO(p2w_addr),
141+
current_owner: Signer(owner),
142+
payer: Signer(payer),
143+
};
144+
145+
let ix_data = (
146+
pyth2wormhole::instruction::Instruction::Migrate,
147+
(),
148+
);
149+
150+
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
151+
152+
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
153+
&[ix],
154+
Some(&payer_pubkey),
155+
signers.iter().collect::<Vec<_>>().as_ref(),
156+
latest_blockhash,
157+
);
158+
Ok(tx_signed)
159+
}
160+
127161
/// Get the current config account data for given p2w program address
128162
pub fn get_config_account(
129163
rpc_client: &RpcClient,

solana/pyth2wormhole/client/src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,21 @@ fn main() -> Result<(), ErrBox> {
111111
get_config_account(&rpc_client, &p2w_addr)?
112112
);
113113
}
114+
Action::Migrate {
115+
ref owner,
116+
} => {
117+
let tx = gen_migrate_tx(
118+
payer,
119+
p2w_addr,
120+
read_keypair_file(&*shellexpand::tilde(&owner))?,
121+
latest_blockhash,
122+
)?;
123+
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
124+
println!(
125+
"Applied conifg:\n{:?}",
126+
get_config_account(&rpc_client, &p2w_addr)?
127+
);
128+
}
114129
Action::Attest {
115130
ref attestation_cfg,
116131
n_retries,
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//! Checks for migrating the previous config schema into the current one
2+
3+
pub mod fixtures;
4+
5+
use solana_program::system_program;
6+
use solana_program_test::*;
7+
use solana_sdk::{
8+
account::Account,
9+
instruction::{
10+
AccountMeta,
11+
Instruction,
12+
},
13+
pubkey::Pubkey,
14+
rent::Rent,
15+
signature::Signer,
16+
signer::keypair::Keypair,
17+
transaction::Transaction,
18+
};
19+
20+
use bridge::accounts::{
21+
Bridge,
22+
BridgeConfig,
23+
BridgeData,
24+
};
25+
26+
use log::info;
27+
28+
use pyth2wormhole::config::{
29+
OldP2WConfigAccount,
30+
P2WConfigAccount,
31+
OldPyth2WormholeConfig,
32+
Pyth2WormholeConfig,
33+
};
34+
use pyth2wormhole_client as p2wc;
35+
use solitaire::{
36+
processors::seeded::Seeded,
37+
AccountState,
38+
BorshSerialize,
39+
};
40+
41+
use fixtures::{
42+
passthrough,
43+
pyth,
44+
};
45+
46+
#[tokio::test]
47+
async fn test_migrate_works() -> Result<(), solitaire::ErrBox> {
48+
info!("Starting");
49+
// Programs
50+
let p2w_program_id = Pubkey::new_unique();
51+
let wh_fixture_program_id = Pubkey::new_unique();
52+
53+
// Authorities
54+
let p2w_owner = Keypair::new();
55+
let pyth_owner = Pubkey::new_unique();
56+
57+
// On-chain state
58+
let old_p2w_config = OldPyth2WormholeConfig {owner: p2w_owner.pubkey(),
59+
wh_prog: wh_fixture_program_id,
60+
max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE,
61+
pyth_owner,
62+
};
63+
64+
info!("Before ProgramTest::new()");
65+
66+
// Populate test environment
67+
let mut p2w_test = ProgramTest::new(
68+
"pyth2wormhole",
69+
p2w_program_id,
70+
processor!(pyth2wormhole::instruction::solitaire),
71+
);
72+
73+
// Plant filled config accounts
74+
let old_p2w_config_bytes = old_p2w_config.try_to_vec()?;
75+
let old_p2w_config_account = Account {
76+
lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()),
77+
data: old_p2w_config_bytes,
78+
owner: p2w_program_id,
79+
executable: false,
80+
rent_epoch: 0,
81+
};
82+
let old_p2w_config_addr =
83+
OldP2WConfigAccount::key(None, &p2w_program_id);
84+
85+
info!("Before add_account() calls");
86+
87+
p2w_test.add_account(old_p2w_config_addr, old_p2w_config_account);
88+
89+
// Add system program because the contract creates an account for new configuration account
90+
passthrough::add_passthrough(&mut p2w_test, "system", system_program::id());
91+
92+
info!("Before start_with_context");
93+
let mut ctx = p2w_test.start_with_context().await;
94+
95+
let migrate_tx = p2wc::gen_migrate_tx(
96+
ctx.payer,
97+
p2w_program_id,
98+
p2w_owner,
99+
ctx.last_blockhash,
100+
)?;
101+
info!("Before process_transaction");
102+
103+
// Migration should fail because the new config account is already initialized
104+
ctx.banks_client.process_transaction(migrate_tx).await?;
105+
106+
Ok(())
107+
}
108+
109+
110+
#[tokio::test]
111+
async fn test_migrate_already_migrated() -> Result<(), solitaire::ErrBox> {
112+
info!("Starting");
113+
// Programs
114+
let p2w_program_id = Pubkey::new_unique();
115+
let wh_fixture_program_id = Pubkey::new_unique();
116+
117+
// Authorities
118+
let p2w_owner = Keypair::new();
119+
let pyth_owner = Pubkey::new_unique();
120+
121+
// On-chain state
122+
let old_p2w_config = OldPyth2WormholeConfig {owner: p2w_owner.pubkey(),
123+
wh_prog: wh_fixture_program_id,
124+
max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE,
125+
pyth_owner,
126+
};
127+
128+
let new_p2w_config = Pyth2WormholeConfig {owner: p2w_owner.pubkey(),
129+
wh_prog: wh_fixture_program_id,
130+
max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE,
131+
pyth_owner,
132+
is_active: true,
133+
};
134+
135+
info!("Before ProgramTest::new()");
136+
137+
// Populate test environment
138+
let mut p2w_test = ProgramTest::new(
139+
"pyth2wormhole",
140+
p2w_program_id,
141+
processor!(pyth2wormhole::instruction::solitaire),
142+
);
143+
144+
// Plant filled config accounts
145+
let old_p2w_config_bytes = old_p2w_config.try_to_vec()?;
146+
let old_p2w_config_account = Account {
147+
lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()),
148+
data: old_p2w_config_bytes,
149+
owner: p2w_program_id,
150+
executable: false,
151+
rent_epoch: 0,
152+
};
153+
let old_p2w_config_addr =
154+
OldP2WConfigAccount::key(None, &p2w_program_id);
155+
156+
let new_p2w_config_bytes = new_p2w_config.try_to_vec()?;
157+
let new_p2w_config_account = Account {
158+
lamports: Rent::default().minimum_balance(new_p2w_config_bytes.len()),
159+
data: new_p2w_config_bytes,
160+
owner: p2w_program_id,
161+
executable: false,
162+
rent_epoch: 0,
163+
};
164+
let new_p2w_config_addr =
165+
P2WConfigAccount::<{AccountState::Initialized}>::key(None, &p2w_program_id);
166+
167+
info!("Before add_account() calls");
168+
169+
p2w_test.add_account(old_p2w_config_addr, old_p2w_config_account);
170+
p2w_test.add_account(new_p2w_config_addr, new_p2w_config_account);
171+
172+
info!("Before start_with_context");
173+
let mut ctx = p2w_test.start_with_context().await;
174+
175+
let migrate_tx = p2wc::gen_migrate_tx(
176+
ctx.payer,
177+
p2w_program_id,
178+
p2w_owner,
179+
ctx.last_blockhash,
180+
)?;
181+
info!("Before process_transaction");
182+
183+
// Migration should fail because the new config account is already initialized
184+
assert!(ctx.banks_client.process_transaction(migrate_tx).await.is_err());
185+
186+
Ok(())
187+
}

0 commit comments

Comments
 (0)