upgrade-program 1.0.1

Distributed Lab - Solana Program Library
Documentation
use solana_program::{
    account_info::{AccountInfo, next_account_info},
    entrypoint::ProgramResult, msg,
    program::{invoke_signed}, pubkey::Pubkey, system_instruction,
    sysvar::{rent::Rent, Sysvar},
};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::secp256k1_recover::{SECP256K1_PUBLIC_KEY_LENGTH, SECP256K1_SIGNATURE_LENGTH};
use crate::state::{MAX_ADMIN_SIZE, UpgradeAdmin};
use crate::instructions::UpgradeInstruction;
use crate::ecdsa::verify_ecdsa_signature;
use crate::{HASH_CONSTANT, PDA_ADMIN_SEED};
use crate::error::UpgradeError;

pub fn process_instruction<'a>(
    program_id: &'a Pubkey,
    accounts: &'a [AccountInfo<'a>],
    input: &[u8],
) -> ProgramResult {
    let instruction = UpgradeInstruction::try_from_slice(input)?;
    match instruction {
        UpgradeInstruction::InitializeAdmin(args) => {
            msg!("Instruction: Create upgrade admin");
            process_init_admin(program_id, accounts, args.public_key, args.contract)
        }
        UpgradeInstruction::ChangePublicKey(args) => {
            msg!("Instruction: Change public key");
            process_change_public_key(program_id, accounts, args.new_public_key, args.signature, args.recovery_id)
        }
        UpgradeInstruction::ChangeAuthority(args) => {
            msg!("Instruction: Transfer upgrade authority");
            process_change_authority(program_id, accounts, args.signature, args.recovery_id)
        }
        UpgradeInstruction::Upgrade(args) => {
            msg!("Instruction: Upgrade");
            process_upgrade(program_id, accounts, args.signature, args.recovery_id)
        }
    }
}


pub fn process_init_admin<'a>(
    program_id: &'a Pubkey,
    accounts: &'a [AccountInfo<'a>],
    public_key: [u8; SECP256K1_PUBLIC_KEY_LENGTH],
    upgrade_program: Pubkey,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let upgrade_admin_info = next_account_info(account_info_iter)?;
    let fee_payer_info = next_account_info(account_info_iter)?;
    let system_program = next_account_info(account_info_iter)?;
    let rent_info = next_account_info(account_info_iter)?;

    let (upgrade_key, bump) = Pubkey::find_program_address(&[PDA_ADMIN_SEED.as_bytes(), upgrade_program.as_ref()], &program_id);
    if upgrade_key != *upgrade_admin_info.key {
        return Err(UpgradeError::WrongAdmin.into());
    }

    let rent = Rent::from_account_info(rent_info)?;

    let instruction = system_instruction::create_account(
        fee_payer_info.key,
        upgrade_admin_info.key,
        rent.minimum_balance(MAX_ADMIN_SIZE),
        MAX_ADMIN_SIZE as u64,
        program_id,
    );

    invoke_signed(
        &instruction,
        &[
            fee_payer_info.clone(),
            upgrade_admin_info.clone(),
            system_program.clone(),
        ],
        &[&[PDA_ADMIN_SEED.as_bytes(), upgrade_program.as_ref(), &[bump]]],
    )?;

    let mut upgrade_admin: UpgradeAdmin = BorshDeserialize::deserialize(&mut upgrade_admin_info.data.borrow_mut().as_ref())?;
    if upgrade_admin.is_initialized {
        return Err(UpgradeError::AlreadyInUse.into());
    }

    upgrade_admin.contract = upgrade_program;
    upgrade_admin.public_key = public_key;
    upgrade_admin.is_initialized = true;
    upgrade_admin.nonce = 0;
    upgrade_admin.serialize(&mut *upgrade_admin_info.data.borrow_mut())?;
    Ok(())
}


pub fn process_change_public_key<'a>(
    program_id: &'a Pubkey,
    accounts: &'a [AccountInfo<'a>],
    new_public_key: [u8; SECP256K1_PUBLIC_KEY_LENGTH],
    signature: [u8; SECP256K1_SIGNATURE_LENGTH],
    recovery_id: u8,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let upgrade_admin_info = next_account_info(account_info_iter)?;

    let mut upgrade_admin: UpgradeAdmin = BorshDeserialize::deserialize(&mut upgrade_admin_info.data.borrow_mut().as_ref())?;
    if !upgrade_admin.is_initialized {
        return Err(UpgradeError::NotInitialized.into());
    }

    let (upgrade_admin_key, _) = Pubkey::find_program_address(&[PDA_ADMIN_SEED.as_bytes(),  upgrade_admin.contract.as_ref()], &program_id);
    if upgrade_admin_key != *upgrade_admin_info.key {
        return Err(UpgradeError::WrongSeeds.into());
    }

    verify_ecdsa_signature(
        solana_program::keccak::hash(
            &[
                upgrade_admin.contract.as_ref(),
                upgrade_admin.nonce.to_be_bytes().as_ref(),
                HASH_CONSTANT.as_bytes(),
                new_public_key.as_ref(),
            ].concat()
        ).as_ref(),
        signature.as_slice(),
        recovery_id,
        upgrade_admin.public_key,
    )?;

    upgrade_admin.public_key = new_public_key;
    upgrade_admin.nonce = upgrade_admin.nonce + 1;
    upgrade_admin.serialize(&mut *upgrade_admin_info.data.borrow_mut())?;
    Ok(())
}


pub fn process_change_authority<'a>(
    program_id: &'a Pubkey,
    accounts: &'a [AccountInfo<'a>],
    signature: [u8; SECP256K1_SIGNATURE_LENGTH],
    recovery_id: u8,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let upgrade_admin_info = next_account_info(account_info_iter)?;
    let upgrade_program_data = next_account_info(account_info_iter)?;
    let authority = next_account_info(account_info_iter)?;


    let mut upgrade_admin: UpgradeAdmin = BorshDeserialize::deserialize(&mut upgrade_admin_info.data.borrow_mut().as_ref())?;
    if !upgrade_admin.is_initialized {
        return Err(UpgradeError::NotInitialized.into());
    }

    let (upgrade_admin_key, bump) = Pubkey::find_program_address(&[PDA_ADMIN_SEED.as_bytes(), upgrade_admin.contract.as_ref()], &program_id);
    if upgrade_admin_key != *upgrade_admin_info.key {
        return Err(UpgradeError::WrongSeeds.into());
    }

    verify_ecdsa_signature(
        solana_program::keccak::hash(
            &[
                upgrade_admin.contract.as_ref(),
                upgrade_admin.nonce.to_be_bytes().as_ref(),
                HASH_CONSTANT.as_bytes(),
                authority.key.as_ref(),
            ].concat()
        ).as_ref(),
        signature.as_slice(),
        recovery_id,
        upgrade_admin.public_key,
    )?;


    let instruction = solana_program::bpf_loader_upgradeable::set_upgrade_authority(
        &upgrade_admin.contract,
        upgrade_admin_info.key,
        Some(authority.key),
    );

    invoke_signed(
        &instruction,
        &[
            upgrade_program_data.clone(),
            upgrade_admin_info.clone(),
            authority.clone(),
        ],
        &[&[PDA_ADMIN_SEED.as_bytes(),  upgrade_admin.contract.as_ref(), &[bump]]],
    )?;


    upgrade_admin.nonce = upgrade_admin.nonce + 1;
    upgrade_admin.serialize(&mut *upgrade_admin_info.data.borrow_mut())?;
    Ok(())
}


pub fn process_upgrade<'a>(
    program_id: &'a Pubkey,
    accounts: &'a [AccountInfo<'a>],
    signature: [u8; SECP256K1_SIGNATURE_LENGTH],
    recovery_id: u8,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let upgrade_admin_info = next_account_info(account_info_iter)?;
    let upgrade_program_data = next_account_info(account_info_iter)?;
    let upgrade_program = next_account_info(account_info_iter)?;
    let upgrade_buffer = next_account_info(account_info_iter)?;
    let upgrade_spill = next_account_info(account_info_iter)?;
    let rent_info = next_account_info(account_info_iter)?;
    let clock_info = next_account_info(account_info_iter)?;

    let (upgrade_admin_key, bump) = Pubkey::find_program_address(&[PDA_ADMIN_SEED.as_bytes(), upgrade_program.key.as_ref()], &program_id);
    if upgrade_admin_key != *upgrade_admin_info.key {
        return Err(UpgradeError::WrongSeeds.into());
    }

    let mut upgrade_admin: UpgradeAdmin = BorshDeserialize::deserialize(&mut upgrade_admin_info.data.borrow_mut().as_ref())?;
    if !upgrade_admin.is_initialized {
        return Err(UpgradeError::NotInitialized.into());
    }

    verify_ecdsa_signature(
        solana_program::keccak::hash(
            &[
                upgrade_admin.contract.as_ref(),
                upgrade_admin.nonce.to_be_bytes().as_ref(),
                HASH_CONSTANT.as_bytes(),
                upgrade_buffer.key.as_ref(),
            ].concat()
        ).as_ref(),
        signature.as_slice(),
        recovery_id,
        upgrade_admin.public_key,
    )?;

    let instruction = solana_program::bpf_loader_upgradeable::upgrade(
        upgrade_program.key,
        upgrade_buffer.key,
        &upgrade_admin_key,
        upgrade_spill.key,
    );

    invoke_signed(
        &instruction,
        &[
            upgrade_program_data.clone(),
            upgrade_program.clone(),
            upgrade_buffer.clone(),
            upgrade_spill.clone(),
            rent_info.clone(),
            clock_info.clone(),
            upgrade_admin_info.clone(),
        ],
        &[&[PDA_ADMIN_SEED.as_bytes(), upgrade_program.key.as_ref(), &[bump]]],
    )?;

    upgrade_admin.nonce = upgrade_admin.nonce + 1;
    upgrade_admin.serialize(&mut *upgrade_admin_info.data.borrow_mut())?;
    Ok(())
}