Skip to content

Commit

Permalink
CLI anchor idl command to use dump base64 for set-buffer and close
Browse files Browse the repository at this point in the history
  • Loading branch information
ochaloup committed May 14, 2023
1 parent 89e94d1 commit d12ebf8
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 46 deletions.
47 changes: 47 additions & 0 deletions cli/src/base64serialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use anchor_lang::{prelude::*, solana_program::instruction::Instruction};

#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
pub struct TransactionInstruction {
// Target program to execute against.
pub program_id: Pubkey,
// Accounts requried for the transaction.
pub accounts: Vec<TransactionAccount>,
// Instruction data for the transaction.
pub data: Vec<u8>,
}

impl From<&TransactionInstruction> for Instruction {
fn from(tx: &TransactionInstruction) -> Instruction {
Instruction {
program_id: tx.program_id,
accounts: tx.accounts.iter().map(AccountMeta::from).collect(),
data: tx.data.clone(),
}
}
}

#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]
pub struct TransactionAccount {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}

impl From<&TransactionAccount> for AccountMeta {
fn from(account: &TransactionAccount) -> AccountMeta {
match account.is_writable {
false => AccountMeta::new_readonly(account.pubkey, account.is_signer),
true => AccountMeta::new(account.pubkey, account.is_signer),
}
}
}

impl From<&AccountMeta> for TransactionAccount {
fn from(account_meta: &AccountMeta) -> TransactionAccount {
TransactionAccount {
pubkey: account_meta.pubkey,
is_signer: account_meta.is_signer,
is_writable: account_meta.is_writable,
}
}
}
174 changes: 128 additions & 46 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::base64serialize::{TransactionAccount, TransactionInstruction};
use crate::config::{
AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramArch,
ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, WithPath, SHUTDOWN_WAIT,
Expand Down Expand Up @@ -44,6 +45,7 @@ use std::str::FromStr;
use std::string::ToString;
use tar::Archive;

pub mod base64serialize;
pub mod config;
mod path;
pub mod rust_template;
Expand Down Expand Up @@ -337,6 +339,9 @@ pub enum IdlCommand {
},
Close {
program_id: Pubkey,
/// This is the public key of the IDL authority. When used, the content of the instruction will only be printed in base64 form and not executed.
#[clap(short, long)]
authority: Option<Pubkey>,
},
/// Writes an IDL into a buffer account. This can be used with SetBuffer
/// to perform an upgrade.
Expand All @@ -351,6 +356,11 @@ pub enum IdlCommand {
/// Address of the buffer account to set as the idl on the program.
#[clap(short, long)]
buffer: Pubkey,
/// This is the public key of the IDL authority. When used, the content of the instruction will only be printed in base64 form and not executed.
/// Useful for multisig execution when the local wallet keypair is not available.
/// (Note: To use a different IDL authority than the wallet keypair, use --provider.wallet).
#[clap(short, long)]
authority: Option<Pubkey>,
},
/// Upgrades the IDL to the new file. An alias for first writing and then
/// then setting the idl buffer account.
Expand Down Expand Up @@ -1824,14 +1834,19 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
program_id,
filepath,
} => idl_init(cfg_override, program_id, filepath),
IdlCommand::Close { program_id } => idl_close(cfg_override, program_id),
IdlCommand::Close {
program_id,
authority,
} => idl_close(cfg_override, program_id, authority),
IdlCommand::WriteBuffer {
program_id,
filepath,
} => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()),
IdlCommand::SetBuffer { program_id, buffer } => {
idl_set_buffer(cfg_override, program_id, buffer)
}
IdlCommand::SetBuffer {
program_id,
buffer,
authority,
} => idl_set_buffer(cfg_override, program_id, buffer, authority),
IdlCommand::Upgrade {
program_id,
filepath,
Expand Down Expand Up @@ -1867,12 +1882,18 @@ fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: Str
})
}

fn idl_close(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
fn idl_close(
cfg_override: &ConfigOverride,
program_id: Pubkey,
authority: Option<Pubkey>,
) -> Result<()> {
with_workspace(cfg_override, |cfg| {
let idl_address = IdlAccount::address(&program_id);
idl_close_account(cfg, &program_id, idl_address)?;
idl_close_account(cfg, &program_id, idl_address, authority)?;

println!("Idl account closed: {idl_address:?}");
if authority.is_none() {
println!("Idl account closed: {idl_address:?}");
}

Ok(())
})
Expand All @@ -1898,19 +1919,29 @@ fn idl_write_buffer(
})
}

fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> {
fn idl_set_buffer(
cfg_override: &ConfigOverride,
program_id: Pubkey,
buffer: Pubkey,
authority: Option<Pubkey>,
) -> Result<()> {
with_workspace(cfg_override, |cfg| {
let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
.map_err(|_| anyhow!("Unable to read keypair file"))?;
let url = cluster_url(cfg, &cfg.test_validator);
let client = RpcClient::new(url);

let idl_authority = if let Some(idl_authority) = authority {
idl_authority
} else {
keypair.pubkey()
};
// Instruction to set the buffer onto the IdlAccount.
let set_buffer_ix = {
let accounts = vec![
AccountMeta::new(buffer, false),
AccountMeta::new(IdlAccount::address(&program_id), false),
AccountMeta::new(keypair.pubkey(), true),
AccountMeta::new(idl_authority, true),
];
let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
Expand All @@ -1921,24 +1952,47 @@ fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pub
}
};

// Build the transaction.
let latest_hash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[set_buffer_ix],
Some(&keypair.pubkey()),
&[&keypair],
latest_hash,
);
// print only mdde
if authority.is_some() {
let transaction = TransactionInstruction {
program_id: set_buffer_ix.program_id,
accounts: set_buffer_ix
.accounts
.iter()
.map(TransactionAccount::from)
.collect(),
data: set_buffer_ix.data,
};
println!("Print only mode. No execution!");
println!(
"base64 set-buffer to idl account {} of program {}:",
IdlAccount::address(&set_buffer_ix.program_id),
set_buffer_ix.program_id
);
println!(
" {}",
anchor_lang::__private::base64::encode(&transaction.try_to_vec()?)
);
} else {
// Build the transaction.
let latest_hash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[set_buffer_ix],
Some(&keypair.pubkey()),
&[&keypair],
latest_hash,
);

// Send the transaction.
client.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig::confirmed(),
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
},
)?;
// Send the transaction.
client.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig::confirmed(),
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
},
)?;
}

Ok(())
})
Expand All @@ -1950,7 +2004,7 @@ fn idl_upgrade(
idl_filepath: String,
) -> Result<()> {
let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?;
idl_set_buffer(cfg_override, program_id, buffer)
idl_set_buffer(cfg_override, program_id, buffer, None)
}

fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
Expand Down Expand Up @@ -2051,40 +2105,68 @@ fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Res
Ok(())
}

fn idl_close_account(cfg: &Config, program_id: &Pubkey, idl_address: Pubkey) -> Result<()> {
fn idl_close_account(
cfg: &Config,
program_id: &Pubkey,
idl_address: Pubkey,
authority: Option<Pubkey>,
) -> Result<()> {
let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
.map_err(|_| anyhow!("Unable to read keypair file"))?;
let url = cluster_url(cfg, &cfg.test_validator);
let client = RpcClient::new(url);

let idl_authority = if let Some(idl_authority) = authority {
idl_authority
} else {
keypair.pubkey()
};
// Instruction accounts.
let accounts = vec![
AccountMeta::new(idl_address, false),
AccountMeta::new_readonly(keypair.pubkey(), true),
AccountMeta::new(keypair.pubkey(), true),
AccountMeta::new_readonly(idl_authority, true),
AccountMeta::new(keypair.pubkey(), false),
];
// Instruction.
let ix = Instruction {
program_id: *program_id,
accounts,
data: { serialize_idl_ix(anchor_lang::idl::IdlInstruction::Close {})? },
};
// Send transaction.
let latest_hash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair.pubkey()),
&[&keypair],
latest_hash,
);
client.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig::confirmed(),
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
},
)?;

if authority.is_some() {
let transaction = TransactionInstruction {
program_id: ix.program_id,
accounts: ix.accounts.iter().map(TransactionAccount::from).collect(),
data: ix.data,
};
println!("Print only mode. No execution!");
println!(
"base64 close idl account {} of program {}:",
idl_address, ix.program_id
);
println!(
" {}",
anchor_lang::__private::base64::encode(&transaction.try_to_vec()?)
);
} else {
// Send transaction.
let latest_hash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair.pubkey()),
&[&keypair],
latest_hash,
);
client.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig::confirmed(),
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
},
)?;
}

Ok(())
}
Expand Down

0 comments on commit d12ebf8

Please sign in to comment.