cli: Add print base64 instruction option for some of the IDL commands (#2486)

Co-authored-by: acheron <acheroncrypto@gmail.com>
This commit is contained in:
chalda 2023-05-18 19:12:25 +02:00 committed by GitHub
parent b7bada148c
commit 41a4d820d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 159 additions and 59 deletions

View File

@ -16,6 +16,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- spl: Add `token_program` constraint to `Token`, `Mint`, and `AssociatedToken` accounts in order to override required `token_program` fields and use different token interface implementations in the same instruction ([#2460](https://github.com/coral-xyz/anchor/pull/2460))
- cli: Add support for Solidity programs. `anchor init` and `anchor new` take an option `--solidity` which creates solidity code rather than rust. `anchor build` and `anchor test` work accordingly ([#2421](https://github.com/coral-xyz/anchor/pull/2421))
- bench: Add benchmarking for compute units usage ([#2466](https://github.com/coral-xyz/anchor/pull/2466))
- cli: `idl set-buffer`, `idl set-authority` and `idl close` take an option `--print-only`. which prints transaction in a base64 Borsh compatible format but not sent to the cluster. It's helpful when managing authority under a multisig, e.g., a user can create a proposal for a `Custom Instruction` in SPL Governance ([#2486](https://github.com/coral-xyz/anchor/pull/2486)).
### Fixes

2
Cargo.lock generated
View File

@ -169,6 +169,8 @@ dependencies = [
"anchor-lang",
"anchor-syn",
"anyhow",
"base64 0.13.1",
"bincode",
"cargo_toml",
"chrono",
"clap 4.2.4",

View File

@ -19,6 +19,8 @@ default = []
[dependencies]
clap = { version = "4.2.4", features = ["derive"] }
anyhow = "1.0.32"
base64 = "0.13.1"
bincode = "1.3.3"
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
anchor-lang = { path = "../lang", version = "0.27.0" }
anchor-client = { path = "../client", version = "0.27.0" }

View File

@ -336,6 +336,10 @@ pub enum IdlCommand {
},
Close {
program_id: Pubkey,
/// 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.
#[clap(long)]
print_only: bool,
},
/// Writes an IDL into a buffer account. This can be used with SetBuffer
/// to perform an upgrade.
@ -350,6 +354,10 @@ pub enum IdlCommand {
/// Address of the buffer account to set as the idl on the program.
#[clap(short, long)]
buffer: Pubkey,
/// 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.
#[clap(long)]
print_only: bool,
},
/// Upgrades the IDL to the new file. An alias for first writing and then
/// then setting the idl buffer account.
@ -369,6 +377,10 @@ pub enum IdlCommand {
/// New authority of the IDL account.
#[clap(short, long)]
new_authority: Pubkey,
/// 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.
#[clap(long)]
print_only: bool,
},
/// Command to remove the ability to modify the IDL account. This should
/// likely be used in conjection with eliminating an "upgrade authority" on
@ -1823,14 +1835,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,
print_only,
} => idl_close(cfg_override, program_id, print_only),
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,
print_only,
} => idl_set_buffer(cfg_override, program_id, buffer, print_only),
IdlCommand::Upgrade {
program_id,
filepath,
@ -1839,7 +1856,8 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
program_id,
address,
new_authority,
} => idl_set_authority(cfg_override, program_id, address, new_authority),
print_only,
} => idl_set_authority(cfg_override, program_id, address, new_authority, print_only),
IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
IdlCommand::Parse {
@ -1852,6 +1870,12 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
}
}
fn get_idl_account(client: &RpcClient, idl_address: &Pubkey) -> Result<IdlAccount> {
let account = client.get_account(idl_address)?;
let mut data: &[u8] = &account.data;
AccountDeserialize::try_deserialize(&mut data).map_err(|e| anyhow!("{:?}", e))
}
fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> {
with_workspace(cfg_override, |cfg| {
let keypair = cfg.provider.wallet.to_string();
@ -1866,12 +1890,14 @@ 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, print_only: bool) -> 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, print_only)?;
println!("Idl account closed: {idl_address:?}");
if !print_only {
println!("Idl account closed: {idl_address:?}");
}
Ok(())
})
@ -1897,19 +1923,30 @@ 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,
print_only: bool,
) -> 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_address = IdlAccount::address(&program_id);
let idl_authority = if print_only {
get_idl_account(&client, &idl_address)?.authority
} else {
keypair.pubkey()
};
// Instruction to set the buffer onto the IdlAccount.
let set_buffer_ix = {
let ix = {
let accounts = vec![
AccountMeta::new(buffer, false),
AccountMeta::new(IdlAccount::address(&program_id), false),
AccountMeta::new(keypair.pubkey(), true),
AccountMeta::new(idl_address, false),
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()?);
@ -1920,20 +1957,24 @@ 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,
);
if print_only {
print_idl_instruction("SetBuffer", &ix, &idl_address)?;
} else {
// Build the transaction.
let latest_hash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair.pubkey()),
&[&keypair],
latest_hash,
);
// Send the transaction.
client.send_and_confirm_transaction_with_spinner_and_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
// Send the transaction.
client.send_and_confirm_transaction_with_spinner_and_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
}
Ok(())
})
@ -1945,7 +1986,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, false)
}
fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
@ -1964,9 +2005,7 @@ fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()
}
};
let account = client.get_account(&idl_address)?;
let mut data: &[u8] = &account.data;
let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?;
let idl_account = get_idl_account(&client, &idl_address)?;
println!("{:?}", idl_account.authority);
@ -1979,6 +2018,7 @@ fn idl_set_authority(
program_id: Pubkey,
address: Option<Pubkey>,
new_authority: Pubkey,
print_only: bool,
) -> Result<()> {
with_workspace(cfg_override, |cfg| {
// Misc.
@ -1991,6 +2031,12 @@ fn idl_set_authority(
let url = cluster_url(cfg, &cfg.test_validator);
let client = RpcClient::new(url);
let idl_authority = if print_only {
get_idl_account(&client, &idl_address)?.authority
} else {
keypair.pubkey()
};
// Instruction data.
let data =
serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
@ -1998,7 +2044,7 @@ fn idl_set_authority(
// Instruction accounts.
let accounts = vec![
AccountMeta::new(idl_address, false),
AccountMeta::new_readonly(keypair.pubkey(), true),
AccountMeta::new_readonly(idl_authority, true),
];
// Instruction.
@ -2007,20 +2053,25 @@ fn idl_set_authority(
accounts,
data,
};
// 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_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
println!("Authority update complete.");
if print_only {
print_idl_instruction("SetAuthority", &ix, &idl_address)?;
} 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_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
println!("Authority update complete.");
}
Ok(())
})
@ -2037,22 +2088,32 @@ fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Res
return Ok(());
}
idl_set_authority(cfg_override, program_id, None, ERASED_AUTHORITY)?;
idl_set_authority(cfg_override, program_id, None, ERASED_AUTHORITY, false)?;
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,
print_only: bool,
) -> 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 print_only {
get_idl_account(&client, &idl_address)?.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 {
@ -2060,18 +2121,23 @@ fn idl_close_account(cfg: &Config, program_id: &Pubkey, idl_address: Pubkey) ->
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_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
if print_only {
print_idl_instruction("Close", &ix, &idl_address)?;
} 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_commitment(
&tx,
CommitmentConfig::confirmed(),
)?;
}
Ok(())
}
@ -2179,6 +2245,35 @@ fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
Ok(())
}
/// Print `base64+borsh` encoded IDL instruction.
fn print_idl_instruction(ix_name: &str, ix: &Instruction, idl_address: &Pubkey) -> Result<()> {
println!("Print only mode. No execution!");
println!("Instruction: {ix_name}");
println!("IDL address: {idl_address}");
println!("Program: {}", ix.program_id);
// Serialize with `bincode` because `Instruction` does not implement `BorshSerialize`
let mut serialized_ix = bincode::serialize(ix)?;
// Remove extra bytes in order to make the serialized instruction `borsh` compatible
// `bincode` uses 8 bytes(LE) for length meanwhile `borsh` uses 4 bytes(LE)
let mut remove_extra_vec_bytes = |index: usize| {
serialized_ix.drain((index + 4)..(index + 8));
};
let accounts_index = std::mem::size_of_val(&ix.program_id);
remove_extra_vec_bytes(accounts_index);
let data_index = accounts_index + 4 + std::mem::size_of_val(&*ix.accounts);
remove_extra_vec_bytes(data_index);
println!(
"Base64 encoded instruction: {}",
base64::encode(serialized_ix)
);
Ok(())
}
fn account(
cfg_override: &ConfigOverride,
account_type: String,