diff --git a/cli/src/main.rs b/cli/src/main.rs index 3b21579c71..286e4f8976 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,6 @@ use crate::config::{read_all_programs, Config, Program}; use anchor_lang::idl::IdlAccount; -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize}; use anchor_syn::idl::Idl; use anyhow::{anyhow, Result}; use clap::Clap; @@ -99,12 +99,33 @@ pub enum IdlCommand { #[clap(short, long)] filepath: String, }, - /// Updates the IDL to the new file. - Update { + /// Upgrades the IDL to the new file. + Upgrade { program_id: Pubkey, #[clap(short, long)] filepath: String, }, + /// Sets a new authority on the IDL account. + SetAuthority { + /// Program to change the IDL authority. + #[clap(short, long)] + program_id: Pubkey, + /// New authority of the IDL account. + #[clap(short, long)] + new_authority: Pubkey, + }, + /// Command to remove the ability to modify the IDL account. This should + /// likely be used in conjection with eliminating an "upgrade authority" on + /// the program. + EraseAuthority { + #[clap(short, long)] + program_id: Pubkey, + }, + /// Outputs the authority for the IDL account. + Authority { + /// The program to view. + program_id: Pubkey, + }, /// Parses an IDL from source. Parse { /// Path to the program's interface definition. @@ -310,10 +331,16 @@ fn idl(subcmd: IdlCommand) -> Result<()> { program_id, filepath, } => idl_init(program_id, filepath), - IdlCommand::Update { + IdlCommand::Upgrade { program_id, filepath, - } => idl_update(program_id, filepath), + } => idl_upgrade(program_id, filepath), + IdlCommand::SetAuthority { + program_id, + new_authority, + } => idl_set_authority(program_id, new_authority), + IdlCommand::EraseAuthority { program_id } => idl_erase_authority(program_id), + IdlCommand::Authority { program_id } => idl_authority(program_id), IdlCommand::Parse { file, out } => idl_parse(file, out), IdlCommand::Fetch { program_id, out } => idl_fetch(program_id, out), } @@ -333,7 +360,7 @@ fn idl_init(program_id: Pubkey, idl_filepath: String) -> Result<()> { }) } -fn idl_update(program_id: Pubkey, idl_filepath: String) -> Result<()> { +fn idl_upgrade(program_id: Pubkey, idl_filepath: String) -> Result<()> { with_workspace(|cfg, _path, _cargo| { let bytes = std::fs::read(idl_filepath)?; let idl: Idl = serde_json::from_reader(&*bytes)?; @@ -345,6 +372,86 @@ fn idl_update(program_id: Pubkey, idl_filepath: String) -> Result<()> { }) } +fn idl_authority(program_id: Pubkey) -> Result<()> { + with_workspace(|cfg, _path, _cargo| { + let client = RpcClient::new(cfg.cluster.url().to_string()); + let idl_address = IdlAccount::address(&program_id); + + let account = client.get_account(&idl_address)?; + let mut data: &[u8] = &account.data; + let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?; + + println!("{:?}", idl_account.authority); + + Ok(()) + }) +} + +fn idl_set_authority(program_id: Pubkey, new_authority: Pubkey) -> Result<()> { + with_workspace(|cfg, _path, _cargo| { + // Misc. + let idl_address = IdlAccount::address(&program_id); + let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string()) + .map_err(|_| anyhow!("Unable to read keypair file"))?; + let client = RpcClient::new(cfg.cluster.url().to_string()); + + // Instruction data. + let data = + serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?; + + // Instruction accounts. + let accounts = vec![ + AccountMeta::new(idl_address, false), + AccountMeta::new_readonly(keypair.pubkey(), true), + ]; + + // Instruction. + let ix = Instruction { + program_id, + accounts, + data, + }; + // Send transaction. + let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&keypair.pubkey()), + &[&keypair], + recent_hash, + ); + client.send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::single(), + RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + }, + )?; + + println!("Authority update complete."); + + Ok(()) + }) +} + +fn idl_erase_authority(program_id: Pubkey) -> Result<()> { + println!("Are you sure you want to erase the IDL authority: [y/n]"); + + let stdin = std::io::stdin(); + let mut stdin_lines = stdin.lock().lines(); + let input = stdin_lines.next().unwrap().unwrap(); + if input != "y" { + println!("Not erasing."); + return Ok(()); + } + + // Program will treat the zero authority as erased. + let new_authority = Pubkey::new_from_array([0u8; 32]); + idl_set_authority(program_id, new_authority)?; + + Ok(()) +} + // Clears out *all* IDL data. The authority for the IDL must be the configured // wallet. fn idl_clear(cfg: &Config, program_id: &Pubkey) -> Result<()> { diff --git a/lang/src/idl.rs b/lang/src/idl.rs index 759cafcf75..2ab5c2fd92 100644 --- a/lang/src/idl.rs +++ b/lang/src/idl.rs @@ -43,7 +43,7 @@ pub type IdlCreateAccounts<'info> = crate::ctor::Ctor<'info>; pub struct IdlAccounts<'info> { #[account(mut, has_one = authority)] pub idl: ProgramAccount<'info, IdlAccount>, - #[account(signer)] + #[account(signer, "authority.key != &Pubkey::new_from_array([0u8; 32])")] pub authority: AccountInfo<'info>, }