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:
parent
b7bada148c
commit
41a4d820d3
|
@ -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
|
||||
|
||||
|
|
|
@ -169,6 +169,8 @@ dependencies = [
|
|||
"anchor-lang",
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
"bincode",
|
||||
"cargo_toml",
|
||||
"chrono",
|
||||
"clap 4.2.4",
|
||||
|
|
|
@ -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" }
|
||||
|
|
213
cli/src/lib.rs
213
cli/src/lib.rs
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue