Program Management

Operation
Tx
Initialize Buffer
Write
Deploy
Upgrade

Deploy

Initialize Buffer

During program deployment, the first thing to do is to initialize a buffer account, which will record the whole code data.
  • It first creates a buffer account, allocates space, assign the owner to be BPF Upgradeable Loader.
  • Then it calls BPF Upgradeable Loader to initialize buffer.
notion image
 
Steps:
  1. Load Contexts:
    1. Transaction Context
    2. Instruction Context
    3. Instruction Data.
    4. Program ID
  1. Deserialize Instruction Data and Match Handler
  1. Ensure the Instruction has Two Accounts
    1. One is buffer account address, the other is authority address which has authority to insert data into the buffer account.
  1. Get Buffer Account
    1. Buffer account is the first account in instruction account lists.
  1. Check Buffer Account isn’t Initialized
  1. Get Authority Account Address
    1. Authority account is the second account in instruction account lists.
  1. Update State of Buffer
    1. Set buffer account’s type to be UpgradeableLoaderState::Buffer, and specify authority address.
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { UpgradeableLoaderInstruction::InitializeBuffer => { /// Ensure the Instruction has Two Accounts instruction_context.check_number_of_instruction_accounts(2)?; /// Get Buffer Account let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; /// Check Buffer Account isn’t Initialized if UpgradeableLoaderState::Uninitialized != buffer.get_state()? { ic_logger_msg!(log_collector, "Buffer account already initialized"); return Err(InstructionError::AccountAlreadyInitialized); } /// Get Authority Account Address let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?); /// Update State of Buffer buffer.set_state(&UpgradeableLoaderState::Buffer { authority_address: authority_key, })?; } /// ... } } /// --- sdk/program/src/bpf_loader_upgradeable.rs --- #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, AbiExample)] pub enum UpgradeableLoaderState { /// Account is not initialized. Uninitialized, /// A Buffer account. Buffer { /// Authority address authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, /// An Program account. Program { /// Address of the ProgramData account. programdata_address: Pubkey, }, // A ProgramData account. ProgramData { /// Slot that the program was last modified. slot: u64, /// Address of the Program's upgrade authority. upgrade_authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, } /// --- sdk/src/transaction_context.rs --- impl<'a> BorrowedAccount<'a> { /// Serializes a state into the account data #[cfg(not(target_os = "solana"))] pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> { let data = self.get_data_mut()?; let serialized_size = bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?; if serialized_size > data.len() as u64 { return Err(InstructionError::AccountDataTooSmall); } bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?; Ok(()) } /// ... }

Write

After the buffer account has been initialized, deployer needs fill the buffer account with program’s bytecode.
Steps:
  1. Load Contexts:
    1. Transaction Context
    2. Instruction Context
    3. Instruction Data.
    4. Program ID
  1. Deserialize Instruction Data and Match Handler
  1. Ensure the Instruction has Two Accounts
    1. One is buffer account address, the other is authority address which has authority to insert data into the buffer account.
  1. Get Buffer Account
    1. Buffer account is the first account in instruction account lists.
  1. Check Buffer Account has been Initialized
  1. Check Authority Address in Buffer Account not Null
    1. Null authority means the buffer account is immutable.
  1. Match Authority Account in Instruction Account List with Registered Authority in Buffer Acount
  1. Check Authority Account is Signer
  1. Write Program Data in Buffer Account
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { /// ... UpgradeableLoaderInstruction::Write { offset, bytes } => { /// Ensure the Instruction has Two Accounts instruction_context.check_number_of_instruction_accounts(2)?; /// Get Buffer Account let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; /// Check Buffer Account has been Initialized if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { /// Check Authority Address in Buffer Account not Null if authority_address.is_none() { ic_logger_msg!(log_collector, "Buffer is immutable"); return Err(InstructionError::Immutable); // TODO better error code } /// Match Authority Account in Instruction Account List with Registered Authority in Buffer Acount let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?); if authority_address != authority_key { ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); return Err(InstructionError::IncorrectAuthority); } /// Check Authority Account is Signer if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Buffer authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidAccountData); } drop(buffer); /// Write Program Data in Buffer Account write_program_data( UpgradeableLoaderState::size_of_buffer_metadata().saturating_add(offset as usize), &bytes, invoke_context, )?; } /// ... } } /// --- programs/bpf_loader/src/lib.rs --- fn write_program_data( program_data_offset: usize, bytes: &[u8], invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let data = program.get_data_mut()?; let write_offset = program_data_offset.saturating_add(bytes.len()); if data.len() < write_offset { ic_msg!( invoke_context, "Write overflow: {} < {}", data.len(), write_offset, ); return Err(InstructionError::AccountDataTooSmall); } data.get_mut(program_data_offset..write_offset) .ok_or(InstructionError::AccountDataTooSmall)? .copy_from_slice(bytes); Ok(()) }

Deploy With Max Data Len

DeployWithMaxDataLen instruction creates program account and programData account, and initialized them.
  • program account is the program account to which we call in the future to execute instructions. It contains programdata_address which is the address of the program_data account.
  • program_data account:
    • program_data account is pda of BFP Upgradeable Loader account, with the seed being program account address.
      It stores:
    • slot: Slot that the program was last modified.
    • upgrade_authority_address: authority who can upgrade and close the account.
    • executable bytecode: bytecode copied from buffer account.
 
Note that the buffer account is closed during deployment. The lamport in the buffer account is first transferred to payer address, then the payer address transfer lamports to program_data account to pay corresponding rent.
/// --- sdk/program/src/bpf_loader_upgradeable.rs --- /// Upgradeable loader account states #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, AbiExample)] pub enum UpgradeableLoaderState { /// Account is not initialized. Uninitialized, /// A Buffer account. Buffer { /// Authority address authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, /// An Program account. Program { /// Address of the ProgramData account. programdata_address: Pubkey, }, // A ProgramData account. ProgramData { /// Slot that the program was last modified. slot: u64, /// Address of the Program's upgrade authority. upgrade_authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, }
 
notion image
 
Steps:
  1. Instruction Account Validation
    1. Ensures that the instruction includes at least four accounts.
  1. Retrieving Key Accounts
      • Payer Account (Index 0): The account responsible for paying the necessary lamports (Solana's native tokens) for the deployment.
      • ProgramData Account (Index 1): Stores the actual program data, including its code and metadata.
  1. Fetching Sysvars
      • Rent Sysvar: Provides information about rent costs, ensuring that the ProgramData account remains rent-exempt.
      • Clock Sysvar: Offers the current slot information.
      • Additional Account Check: The instruction now expects at least eight accounts, indicating that further accounts are required for the deployment process.
  1. Authority Verification
    1. Authority Account (Index 7): The account that has the authority to authorize the deployment, ensuring that only authorized entities can deploy or modify programs.
  1. Program Account Verification
      • Borrowing the Program Account (Index 2): This account will represent the new program being deployed.
      • State Check: Ensures the account is uninitialized to prevent overwriting existing programs.
      • Data Size Check: Verifies that the account has sufficient data space to store program metadata.
      • Rent-Exemption Check: Confirms that the account has enough lamports to remain rent-exempt, preventing it from being purged due to insufficient funds.
      • Program ID Extraction: Retrieves the public key of the new program, which uniquely identifies it on the blockchain.
  1. Buffer Account Verification
      • Borrowing the Buffer Account (Index 3): This account temporarily holds the program data before deployment.
      • State Validation: Confirms that the buffer is in the correct Buffer state, ensuring it contains the necessary data for deployment.
      • Authority Matching: Verifies that the buffer's authority matches the provided authority account, maintaining strict access control.
      • Signature Check: Ensures that the authority has signed the transaction, adding a layer of security against unauthorized deployments.
  1. Buffer Data and Length Checks
      • Data Offsets and Lengths: Calculates offsets and lengths to correctly manage and validate the program data within the accounts.
      • Buffer Size Validation: Ensures the buffer has adequate space to hold the program data.
      • Max Data Length Check: Validates that the specified max_data_len can accommodate the buffer's data.
      • Maximum Permitted Length: Prevents excessively large programs that could strain network resources or violate protocol constraints.
  1. ProgramData Account Creation
      • Address Derivation: Uses the program ID and loader program ID to derive the expected ProgramData account address.
      • Address Verification: Ensures that the provided ProgramData account matches the derived address, maintaining the integrity of the account linkage.
  1. Draining Buffer to Payer
      • Lamport Transfer: Moves all lamports from the buffer account to the payer, effectively draining the buffer to fund the deployment process.
      • Buffer Reset: Sets the buffer's lamport balance to zero to close it.
  1. Creating ProgramData Account via System Instruction
      • System Instruction: Utilizes the create_account instruction from Solana's system program to initialize the ProgramData account.
      • Lamport Allocation: Ensures the ProgramData account is funded with at least the minimum rent-exempt balance.
      • Data Allocation: Allocates the specified programdata_len bytes for storing the program's data.
      • Owner Assignment: Sets the owner of the ProgramData account to the loader program (program_id), establishing ownership and control.
      • Native Invocation: Executes the system instruction within the native runtime environment, effectively creating the ProgramData account on the blockchain.
  1. Loading and Verifying Program Bits
      • Program Deployment Macro: The deploy_program! macro handles the actual deployment of the program's bytecode into the Solana runtime.
  1. Updating ProgramData Account
      • State Update: Sets the state of the ProgramData account to include the current slot and the upgrade authority, establishing metadata about the program.
      • Data Transfer: Copies the program data from the buffer account to the ProgramData account, ensuring the program's code is securely stored.
      • Buffer Reset: Resets the buffer's data length, preparing it for future operations or safe closure.
  1. Updating Program Account
      • State Linking: Updates the Program account to reference the newly created ProgramData account, creating a linkage between the program's executable state and its data.
      • Executable Flag: Marks the Program account as executable, allowing it to be invoked and interacted with on the blockchain.
      • Finalization: Releases the mutable borrow on the Program account, ensuring no further unauthorized modifications occur within this scope.
  1. Logging Deployment Success
    1. Records a success message indicating the deployment of the new program, aiding in transparency and debugging.
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { /// ... UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => { /// Instruction Account Validation instruction_context.check_number_of_instruction_accounts(4)?; /// Retrieving Key Accounts let payer_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(0)?, )?; let programdata_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?; /// Fetching Sysvars let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?; instruction_context.check_number_of_instruction_accounts(8)?; /// Authority Verification let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(7)?, )?); /// Program Account Verification let program = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; if UpgradeableLoaderState::Uninitialized != program.get_state()? { ic_logger_msg!(log_collector, "Program account already initialized"); return Err(InstructionError::AccountAlreadyInitialized); } if program.get_data().len() < UpgradeableLoaderState::size_of_program() { ic_logger_msg!(log_collector, "Program account too small"); return Err(InstructionError::AccountDataTooSmall); } if program.get_lamports() < rent.minimum_balance(program.get_data().len()) { ic_logger_msg!(log_collector, "Program account not rent-exempt"); return Err(InstructionError::ExecutableAccountNotRentExempt); } let new_program_id = *program.get_key(); drop(program); /// Buffer Account Verification let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { if authority_address != authority_key { ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(7)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidArgument); } /// Buffer Data and Length Checks let buffer_key = *buffer.get_key(); let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata(); let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset); let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); let programdata_len = UpgradeableLoaderState::size_of_programdata(max_data_len); if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata() || buffer_data_len == 0 { ic_logger_msg!(log_collector, "Buffer account too small"); return Err(InstructionError::InvalidAccountData); } drop(buffer); if max_data_len < buffer_data_len { ic_logger_msg!( log_collector, "Max data length is too small to hold Buffer data" ); return Err(InstructionError::AccountDataTooSmall); } if programdata_len > MAX_PERMITTED_DATA_LENGTH as usize { ic_logger_msg!(log_collector, "Max data length is too large"); return Err(InstructionError::InvalidArgument); } /// ProgramData Account Creation let (derived_address, bump_seed) = Pubkey::find_program_address(&[new_program_id.as_ref()], program_id); if derived_address != programdata_key { ic_logger_msg!(log_collector, "ProgramData address is not derived"); return Err(InstructionError::InvalidArgument); } /// Drain the Buffer account to payer before paying for programdata account { let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; let mut payer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; payer.checked_add_lamports(buffer.get_lamports())?; buffer.set_lamports(0)?; } /// Creating ProgramData Account via System Instruction let owner_id = *program_id; let mut instruction = system_instruction::create_account( &payer_key, &programdata_key, 1.max(rent.minimum_balance(programdata_len)), programdata_len as u64, program_id, ); // pass an extra account to avoid the overly strict UnbalancedInstruction error instruction .accounts .push(AccountMeta::new(buffer_key, false)); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let caller_program_id = instruction_context.get_last_program_key(transaction_context)?; let signers = [[new_program_id.as_ref(), &[bump_seed]]] .iter() .map(|seeds| Pubkey::create_program_address(seeds, caller_program_id)) .collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?; invoke_context.native_invoke(instruction.into(), signers.as_slice())?; /// Load and verify the program bits let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; deploy_program!( invoke_context, new_program_id, &owner_id, UpgradeableLoaderState::size_of_program().saturating_add(programdata_len), clock.slot, { drop(buffer); }, buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; /// Update the ProgramData account and record the program bits { let mut programdata = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; programdata.set_state(&UpgradeableLoaderState::ProgramData { slot: clock.slot, upgrade_authority_address: authority_key, })?; let dst_slice = programdata .get_data_mut()? .get_mut( programdata_data_offset ..programdata_data_offset.saturating_add(buffer_data_len), ) .ok_or(InstructionError::AccountDataTooSmall)?; let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; let src_slice = buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?; dst_slice.copy_from_slice(src_slice); buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?; } /// Update the Program account let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; program.set_state(&UpgradeableLoaderState::Program { programdata_address: programdata_key, })?; program.set_executable(true)?; drop(program); /// Logging Deployment Success ic_logger_msg!(log_collector, "Deployed program {:?}", new_program_id); } /// ... } }

Upgrade

BPF Upgradeable Loader supports to upgrade program.
During upgrade:
  1. Deploy a new buffer account
  1. Write new bytecode to the buffer account
  1. Call Upgrade instruction to upgrade program.
It requires that the program_data account has enough size to store new program’s bytecode, and remaning space will be zeroed out. The lamports in buffer will be first used to satisify program_data account’s rent need, then remaning part will be transferred to the spill account which typically is the authority account who executes the upgrade.
 
The UpgradeableLoaderInstruction::Upgrade instruction facilitates the upgrade process of an existing upgradeable program. This involves validating the provided accounts, ensuring proper authorization, managing funds, and updating program data to reflect the new logic. The upgrade mechanism ensures that only authorized entities can modify the program, maintaining the integrity and security of the blockchain ecosystem.
notion image
 
Steps:
  1. Instruction Account Validation
    1. Ensures that the instruction includes at least three accounts.
  1. Retrieving Key Accounts
      • ProgramData Account (Index 0): Holds the program's data, including its code and metadata.
      • Rent Sysvar (Index 4): Provides information about rent costs to ensure the ProgramData account remains rent-exempt.
      • Clock Sysvar (Index 5): Offers current slot information.
      • Authority Account (Index 6): The account authorized to perform the upgrade, ensuring only trusted entities can modify the program.
      • Additional Account Check: Confirms that at least seven accounts are provided for the upgrade process, indicating the need for additional accounts beyond the initial three.
  1. Verifying the Program Account
      • Borrowing the Program Account (Index 1): Represents the program to be upgraded.
      • Executable Check: Ensures the program account is marked as executable, a prerequisite for any program logic execution.
      • Writable Check: Confirms that the program account is writable, allowing modifications during the upgrade.
      • Ownership Verification: Validates that the program account is owned by the loader program (program_id), ensuring control is maintained.
      • ProgramData Address Matching: Ensures that the ProgramData account linked to the program matches the provided programdata_key, maintaining consistency.
      • Program ID Extraction: Retrieves the public key of the program, which uniquely identifies it on the blockchain.
  1. Verifying the Buffer Account
      • Borrowing the Buffer Account (Index 2): Temporarily holds the new program data to be deployed.
      • State Validation: Ensures the buffer is in the Buffer state, containing necessary data for the upgrade.
      • Authority Matching: Verifies that the buffer's authority matches the provided authority_key, maintaining strict access control.
      • Signature Verification: Checks that the authority has signed the transaction, preventing unauthorized upgrades.
      • Lamport and Data Length Retrieval:
        • buffer_lamports: Amount of lamports (Solana's native tokens) in the buffer account.
        • buffer_data_offset: Offset to skip buffer metadata, accessing the actual program data.
        • buffer_data_len: Length of the program data in the buffer.
      • Buffer Size Validation: Ensures the buffer has sufficient space to hold program data, preventing overflow or data truncation.
  1. Verifying the ProgramData Account
      • Borrowing the ProgramData Account (Index 0): Contains the current state and data of the program.
      • Data Offsets and Lengths:
        • programdata_data_offset: Offset to skip ProgramData metadata.
        • programdata_balance_required: Minimum lamports required for the ProgramData account to remain rent-exempt.
      • Data Size Validation: Ensures the ProgramData account can accommodate the new program data length.
      • Lamport Balance Check: Verifies that the combined lamports from the ProgramData and buffer accounts meet the rent-exemption requirement.
      • ProgramData State Validation:
        • Slot Check: Prevents multiple deployments within the same slot, maintaining consistency.
        • Upgrade Authority Verification: Ensures the program is upgradeable and that the correct authority is provided.
        • Signature Verification: Confirms that the authority has signed the transaction.
  1. Loading and Verifying the Program Bits
      • Borrowing the Buffer Account Again (Index 2): Accesses the new program data for deployment.
      • deploy_program! Macro: Handles the actual deployment of the new program's bytecode into the Solana runtime.
  1. Updating the ProgramData Account
      • Borrowing the ProgramData Account (Index 0): Accesses the account to update its state and data.
      • State Update: Sets the ProgramData state with the current slot and the new upgrade authority.
      • Data Transfer:
        • Destination Slice: The region in ProgramData where the new program data will be stored.
        • Source Slice: Extracts the new program data from the buffer.
        • Copy Operation: Transfers the program data from the buffer to the ProgramData account.
      • Zeroing Remaining Data: Clears any residual data beyond the new program data length to prevent data leakage or corruption.
      Note here it requires the program_data account has enough space to copy bytecode in the buffer account. And after copy, it sets the remaining data if any to zero, because the new program’s size might be smaller than the previous one, so it’s necessary to erase remaining data.
  1. Funding the ProgramData Account and Handling Excess Funds
      • Borrowing Buffer and Spill Accounts (Indices 2 & 3):
        • Buffer Account (Index 2): Previously used to hold new program data; now drained.
        • Spill Account (Index 3): Receives any excess lamports from the buffer.
      • Lamport Transfer:
        • Calculation: Determines the excess lamports after ensuring ProgramData remains rent-exempt.
        • Transfer: Moves excess lamports to the spill account to prevent fund loss or accumulation in the buffer.
      • Buffer Reset:
        • Lamport Balance: Sets buffer's lamport balance to zero, preparing it for potential future use or closure.
        • Data Length: Resets the buffer's data length to reflect an empty state.
  1. Logging Upgrade Success
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { UpgradeableLoaderInstruction::Upgrade => { /// Instruction Account Validation instruction_context.check_number_of_instruction_accounts(3)?; /// Retrieving Key Accounts let programdata_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(0)?, )?; let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?; instruction_context.check_number_of_instruction_accounts(7)?; let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(6)?, )?); /// Verify Program account let program = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; if !program.is_executable() { ic_logger_msg!(log_collector, "Program account not executable"); return Err(InstructionError::AccountNotExecutable); } if !program.is_writable() { ic_logger_msg!(log_collector, "Program account not writeable"); return Err(InstructionError::InvalidArgument); } if program.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::IncorrectProgramId); } if let UpgradeableLoaderState::Program { programdata_address, } = program.get_state()? { if programdata_address != programdata_key { ic_logger_msg!(log_collector, "Program and ProgramData account mismatch"); return Err(InstructionError::InvalidArgument); } } else { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidAccountData); } let new_program_id = *program.get_key(); drop(program); /// Verify Buffer account let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { if authority_address != authority_key { ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(6)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidArgument); } let buffer_lamports = buffer.get_lamports(); let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata(); let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset); if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata() || buffer_data_len == 0 { ic_logger_msg!(log_collector, "Buffer account too small"); return Err(InstructionError::InvalidAccountData); } drop(buffer); /// Verify ProgramData account let programdata = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); let programdata_balance_required = 1.max(rent.minimum_balance(programdata.get_data().len())); if programdata.get_data().len() < UpgradeableLoaderState::size_of_programdata(buffer_data_len) { ic_logger_msg!(log_collector, "ProgramData account not large enough"); return Err(InstructionError::AccountDataTooSmall); } if programdata.get_lamports().saturating_add(buffer_lamports) < programdata_balance_required { ic_logger_msg!( log_collector, "Buffer account balance too low to fund upgrade" ); return Err(InstructionError::InsufficientFunds); } if let UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } = programdata.get_state()? { if clock.slot == slot { ic_logger_msg!(log_collector, "Program was deployed in this block already"); return Err(InstructionError::InvalidArgument); } if upgrade_authority_address.is_none() { ic_logger_msg!(log_collector, "Program not upgradeable"); return Err(InstructionError::Immutable); } if upgrade_authority_address != authority_key { ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(6)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid ProgramData account"); return Err(InstructionError::InvalidAccountData); }; let programdata_len = programdata.get_data().len(); drop(programdata); /// Load and verify the program bits let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; deploy_program!( invoke_context, new_program_id, program_id, UpgradeableLoaderState::size_of_program().saturating_add(programdata_len), clock.slot, { drop(buffer); }, buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; /// Update the ProgramData account, record the upgraded data, and zero /// the rest let mut programdata = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; { programdata.set_state(&UpgradeableLoaderState::ProgramData { slot: clock.slot, upgrade_authority_address: authority_key, })?; let dst_slice = programdata .get_data_mut()? .get_mut( programdata_data_offset ..programdata_data_offset.saturating_add(buffer_data_len), ) .ok_or(InstructionError::AccountDataTooSmall)?; let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; let src_slice = buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?; dst_slice.copy_from_slice(src_slice); } programdata .get_data_mut()? .get_mut(programdata_data_offset.saturating_add(buffer_data_len)..) .ok_or(InstructionError::AccountDataTooSmall)? .fill(0); /// Fund ProgramData to rent-exemption, spill the rest let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; let mut spill = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; spill.checked_add_lamports( programdata .get_lamports() .saturating_add(buffer_lamports) .saturating_sub(programdata_balance_required), )?; buffer.set_lamports(0)?; programdata.set_lamports(programdata_balance_required)?; buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?; ic_logger_msg!(log_collector, "Upgraded program {:?}", new_program_id); } /// ... } } /// --- sdk/program/src/bpf_loader_upgradeable.rs --- #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, AbiExample)] pub enum UpgradeableLoaderState { /// Account is not initialized. Uninitialized, /// A Buffer account. Buffer { /// Authority address authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, /// An Program account. Program { /// Address of the ProgramData account. programdata_address: Pubkey, }, // A ProgramData account. ProgramData { /// Slot that the program was last modified. slot: u64, /// Address of the Program's upgrade authority. upgrade_authority_address: Option<Pubkey>, // The raw program data follows this serialized structure in the // account's data. }, }

Set Authority

SetAuthority is an instruction that allows the authority of certain upgradeable loader accounts (e.g., a Buffer account or a ProgramData account) to be changed.
 
Steps:
  1. Instruction Account Checks
    1. Ensures that at least two accounts are provided with the instruction.
  1. Retrieving Accounts and Keys
      • Borrowing the Target Account (Index 0): This is the account whose authority you are trying to change.
      • Present Authority Account (Index 1): The key of the current authority of the target account. If this authority does not match the account’s expected authority, or if it is not a signer, the operation fails.
      • New Authority (Index 2, Optional): If present, this represents the new authority key. If not present, it indicates to remove the authority from the account which makes that account immutable.
  1. Matching the Current Account State
      • State Inspection: The loader expects the account to be in one of the UpgradeableLoaderState variants that support having authorities.
      • Unsupported States: If the account state does not correspond to Buffer or ProgramData, the instruction fails with InvalidArgument.
  1. Handling Buffer Accounts
      • Buffer Authority is Required: A Buffer account must have an authority; it cannot be set to None.
      • Immutable Check: If the Buffer is already immutable (authority set to None), you cannot reassign an authority.
      • Authority Matching: The provided present_authority_key must match the Buffer's current authority_address.
      • Signature Verification: The current authority must have signed the transaction.
      • Set New Authority: If all checks pass, the Buffer account's authority is updated to the new_authority.
  1. Handling ProgramData Accounts
      • Check Upgradeability: If the ProgramData account does not have an upgrade authority (None), the program is immutable, and authority cannot be set.
      • Authority Matching: The present_authority_key must match the upgrade_authority_address.
      • Signature Check: The present authority must have signed to authorize the change.
      • Set New Authority: Updates the ProgramData's upgrade_authority_address to the new_authority provided, which may be None if removing the authority (making the program immutable).
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { /// ... UpgradeableLoaderInstruction::SetAuthority => { /// Instruction Account Checks instruction_context.check_number_of_instruction_accounts(2)?; /// Retrieving Accounts and Keys let mut account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let present_authority_key = transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?; let new_authority = instruction_context .get_index_of_instruction_account_in_transaction(2) .and_then(|index_in_transaction| { transaction_context.get_key_of_account_at_index(index_in_transaction) }) .ok(); /// Matching the Current Account State match account.get_state()? { /// Handling Buffer Accounts UpgradeableLoaderState::Buffer { authority_address } => { if new_authority.is_none() { ic_logger_msg!(log_collector, "Buffer authority is not optional"); return Err(InstructionError::IncorrectAuthority); } if authority_address.is_none() { ic_logger_msg!(log_collector, "Buffer is immutable"); return Err(InstructionError::Immutable); } if authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Buffer authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::Buffer { authority_address: new_authority.cloned(), })?; } /// Handling ProgramData Accounts UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } => { if upgrade_authority_address.is_none() { ic_logger_msg!(log_collector, "Program not upgradeable"); return Err(InstructionError::Immutable); } if upgrade_authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: new_authority.cloned(), })?; } _ => { ic_logger_msg!(log_collector, "Account does not support authorities"); return Err(InstructionError::InvalidArgument); } } ic_logger_msg!(log_collector, "New authority {:?}", new_authority); } /// ... } }

Set Authority Checked

SetAuthorityChecked requires that the new authority exists.

Close

The Close instruction allows for reclaiming lamports from accounts associated with the upgradeable loader after they are no longer needed. These accounts can be:
  • Uninitialized: A program or buffer account that was never fully initialized. Any account can close it.
  • Buffer: An account holding program code before deployment or during upgrades. Only authority can close it.
  • ProgramData: The data account associated with an upgradeable program that holds program data and metadata. Only the upgrade authority can close it, and the program account is also closed in the same instruction.
 
Steps:
  1. Instruction Account Verification
      • Minimum Accounts: At least two accounts are required:
        • Account 0 (Close Account): The account being closed.
        • Account 1 (Recipient Account): The account to receive the lamports from the closed account.
      • Distinct Accounts Check: Ensures the close target and the recipient are not the same account. Closing an account into itself is nonsensical and a potential security risk.
  1. Borrowing the Close Account
      • Close Account Retrieval: Obtains a mutable reference to the account to be closed.
      • State Extraction: Reads the current UpgradeableLoaderState of the account.
      • Resetting Data Length: Resets the account data length to the size of an uninitialized state, effectively clearing any program or buffer data. This is part of sanitizing the closed account.
  1. Handling Different Account States
    1. The Close logic now depends on what state the closed account is in. Each variant of the UpgradeableLoaderState is handled differently.
  1. Uninitialized
      • Lamport Transfer: All lamports are transferred to the recipient.
      • Zeroing Lamports: After transfer, the closed account’s lamport balance is set to zero.
      • Logging: A message is logged indicating the successful closure of the uninitialized account.
      Note that anyone can close uninitialized buffer account. This is fine, because solana cli wraps buffer account creation and initialization instructions in single transaction.
  1. Buffer Account
      • Additional Account Check: Requires at least three accounts in the instruction. Beyond the close target and recipient, another authority-related account is required.
      • Authority Verification via common_close_account:
        • The common_close_account function (not fully shown here, but implied) handles verifying that the current authority matches the authority_address of the Buffer account and that this authority has properly signed the transaction.
        • This ensures that only the rightful authority can close the buffer, preventing unauthorized closure and potential loss of program data mid-upgrade.
      If the checks in common_close_account pass:
      • Lamport Transfer and Data Cleanup: Similar to the uninitialized case, lamports are transferred, and the account is effectively cleared.
      • Logging: Records the closure of the Buffer account.
  1. ProgramData Account
    1. Additional Account Requirements: At least four accounts must be present. Apart from the close target, recipient, and authority-related accounts, the associated Program account must also be provided.
    2. Program Account Checks:
        • Writable: The associated Program account must be writable. If it isn't, the operation fails.
        • Ownership: The Program account must be owned by the loader, ensuring that we are dealing with a valid upgradeable program controlled by the loader.
        • Slot Check: If the ProgramData was deployed in the current slot, it cannot be closed immediately.
    3. Matching the ProgramData with the Program:
        • The code retrieves the Program account’s state and checks that its programdata_address matches the close_key. This ensures that the ProgramData being closed truly belongs to this program and prevents mismatched closures.
    4. Authority Check via common_close_account:
        • Similar to the Buffer case, the common_close_account function verifies authority. This ensures that only the rightful upgrade authority (if any exists) can close the ProgramData account.
    5. Marking Program as Closed:
        • After a successful closure, the code updates the runtime with a "tombstone" record of the program, noting that it has been closed. This step is crucial for the runtime to keep track of closed programs and prevent future invocations of a now-closed program.
    6. Logging: Records the closure of the Program.
  1. Unsupported States
      • If the account's state does not match Uninitialized, Buffer, or ProgramData, the closure fails with InvalidArgument.
      • This prevents closing accounts that are not meant to be closed via this instruction, such as a Program account directly.
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { /// ... UpgradeableLoaderInstruction::Close => { /// Instruction Account Verification instruction_context.check_number_of_instruction_accounts(2)?; if instruction_context.get_index_of_instruction_account_in_transaction(0)? == instruction_context.get_index_of_instruction_account_in_transaction(1)? { ic_logger_msg!( log_collector, "Recipient is the same as the account being closed" ); return Err(InstructionError::InvalidArgument); } /// Borrowing the Close Account let mut close_account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let close_key = *close_account.get_key(); let close_account_state = close_account.get_state()?; close_account.set_data_length(UpgradeableLoaderState::size_of_uninitialized())?; /// Handling Different Account States match close_account_state { UpgradeableLoaderState::Uninitialized => { let mut recipient_account = instruction_context .try_borrow_instruction_account(transaction_context, 1)?; recipient_account.checked_add_lamports(close_account.get_lamports())?; close_account.set_lamports(0)?; ic_logger_msg!(log_collector, "Closed Uninitialized {}", close_key); } UpgradeableLoaderState::Buffer { authority_address } => { instruction_context.check_number_of_instruction_accounts(3)?; drop(close_account); common_close_account( &authority_address, transaction_context, instruction_context, &log_collector, )?; ic_logger_msg!(log_collector, "Closed Buffer {}", close_key); } UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: authority_address, } => { instruction_context.check_number_of_instruction_accounts(4)?; drop(close_account); let program_account = instruction_context .try_borrow_instruction_account(transaction_context, 3)?; let program_key = *program_account.get_key(); if !program_account.is_writable() { ic_logger_msg!(log_collector, "Program account is not writable"); return Err(InstructionError::InvalidArgument); } if program_account.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::IncorrectProgramId); } let clock = invoke_context.get_sysvar_cache().get_clock()?; if clock.slot == slot { ic_logger_msg!(log_collector, "Program was deployed in this block already"); return Err(InstructionError::InvalidArgument); } match program_account.get_state()? { UpgradeableLoaderState::Program { programdata_address, } => { if programdata_address != close_key { ic_logger_msg!( log_collector, "ProgramData account does not match ProgramData account" ); return Err(InstructionError::InvalidArgument); } drop(program_account); common_close_account( &authority_address, transaction_context, instruction_context, &log_collector, )?; let clock = invoke_context.get_sysvar_cache().get_clock()?; invoke_context.programs_modified_by_tx.replenish( program_key, Arc::new(LoadedProgram::new_tombstone( clock.slot, LoadedProgramType::Closed, )), ); } _ => { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidArgument); } } ic_logger_msg!(log_collector, "Closed Program {}", program_key); } _ => { ic_logger_msg!(log_collector, "Account does not support closing"); return Err(InstructionError::InvalidArgument); } } } /// ... } } /// --- programs/bpf_loader/src/lib.rs --- fn common_close_account( authority_address: &Option<Pubkey>, transaction_context: &TransactionContext, instruction_context: &InstructionContext, log_collector: &Option<Rc<RefCell<LogCollector>>>, ) -> Result<(), InstructionError> { if authority_address.is_none() { ic_logger_msg!(log_collector, "Account is immutable"); return Err(InstructionError::Immutable); } if *authority_address != Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(2)?, )?) { ic_logger_msg!(log_collector, "Incorrect authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(2)? { ic_logger_msg!(log_collector, "Authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } let mut close_account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let mut recipient_account = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; recipient_account.checked_add_lamports(close_account.get_lamports())?; close_account.set_lamports(0)?; close_account.set_state(&UpgradeableLoaderState::Uninitialized)?; Ok(()) }

Extend Program

ExtendProgram is designed to add more data space to a ProgramData account linked to a deployed upgradeable program. This could be necessary when preparing for future updates that require additional code or data. The process involves verifying that the account is eligible for extension, that sufficient authority is present, and that rent-exemption is maintained, all without violating system constraints such as the maximum permitted account data size.
 
Steps:
  1. Instruction Data Verification
    1. Ensures that the request to extend the program data by zero bytes is rejected. This prevents nonsensical or no-op instructions that could unnecessarily consume compute or lead to confusion.
  1. Account Index Setup
      • ProgramData Account (Index 0): The account whose size is being extended.
      • Program Account (Index 1): The executable upgradeable program linked to the ProgramData.
      • System Program (Index 2): Potentially required for CPI (not always needed, but reserved).
      • Payer Account (Index 3): The account that will cover any additional lamports needed for rent-exemption after extension.
  1. Validating the ProgramData Account
      • Ownership Check: The ProgramData account must be owned by the loader program (program_id), ensuring that it is the correct upgradeable program ecosystem.
      • Writability Check: The account must be writable to allow extension of its data.
  1. Validating the Program Account
      • Program Account Checks:
        • Writability: Must be writable, indicating the program can be modified (e.g., to point to extended ProgramData).
        • Ownership: Must be owned by the same loader program, preventing foreign programs from being tampered with.
      • State Validation: The Program state must reference the same ProgramData account being extended. If there's a mismatch, it's an integrity issue.
      • Invalid States: If the Program account is not in a proper Program state, or does not match the ProgramData account, the instruction aborts with InvalidAccountData.
  1. Calculating the New Length
      • Length Calculation: Safely increments the account data size by additional_bytes.
      • Maximum Size Check: If the new length exceeds the MAX_PERMITTED_DATA_LENGTH, the extension is disallowed to maintain network stability and prevent resource abuse.
  1. Verifying Upgradeability and Slot Constraints
      • Single-Extension-Per-Slot Rule: Ensures that the same program cannot be extended multiple times in the same slot, maintaining state consistency.
      • Upgrade Authority Presence: If upgrade_authority_address is None, the program is immutable and cannot be extended further.
      • State Validation: Confirms that the account is indeed in a ProgramData state. Otherwise, it returns InvalidAccountData.
  1. Ensuring Rent-Exemption
      • Rent Calculation: Determines how many additional lamports, if any, must be added to maintain rent-exemption after increasing the account size.
      • Required Payment: If the new required minimum balance exceeds the current balance, the difference must be provided by a payer.
  1. Funding the Extended Space
      • Payer Transfer: If additional lamports are required, invokes the system transfer instruction to fund the ProgramData account.
      • No Additional Signers Needed: The logic expects that the payer and related signatures are already validated by the runtime outside this function.
  1. Extending the Data Length
      • Updating the Data Length: The account’s data length is updated to the new size.
      • Offset Calculation: Retrieves the offset at which the actual program data begins, skipping over metadata.
  1. Redeploying the Extended Program
      • Re-Verification of the Program Data: Uses the deploy_program! macro to ensure the extended program still passes the necessary validations (e.g., BPF verification) after extension.
      • Slot Recording: Records the current slot to prevent multiple extensions in the same slot.
      • Data Slice Retrieval: Extracts the raw program data from the account (beyond the metadata) to verify its integrity again, ensuring no corruption occurred.
  1. Updating the ProgramData State
      • State Update: Updates the ProgramData state with the new slot and maintains the upgrade authority.
      • Preserving Authority: The upgrade authority remains unchanged unless explicitly modified by another instruction.
  1. Logging Success
/// --- programs/bpf_loader/src/lib.rs --- fn process_loader_upgradeable_instruction( invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); /// Load Contexts: let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; /// Deserialize Instruction Data and Match Handler match limited_deserialize(instruction_data)? { /// ... UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => { /// Instruction Data Verification if additional_bytes == 0 { ic_logger_msg!(log_collector, "Additional bytes must be greater than 0"); return Err(InstructionError::InvalidInstructionData); } /// Account Index Setup const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0; const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1; #[allow(dead_code)] // System program is only required when a CPI is performed const OPTIONAL_SYSTEM_PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 2; const OPTIONAL_PAYER_ACCOUNT_INDEX: IndexOfAccount = 3; /// Validating the ProgramData Account let programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; let programdata_key = *programdata_account.get_key(); if program_id != programdata_account.get_owner() { ic_logger_msg!(log_collector, "ProgramData owner is invalid"); return Err(InstructionError::InvalidAccountOwner); } if !programdata_account.is_writable() { ic_logger_msg!(log_collector, "ProgramData is not writable"); return Err(InstructionError::InvalidArgument); } /// Validating the Program Account let program_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_ACCOUNT_INDEX)?; if !program_account.is_writable() { ic_logger_msg!(log_collector, "Program account is not writable"); return Err(InstructionError::InvalidArgument); } if program_account.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::InvalidAccountOwner); } let program_key = *program_account.get_key(); match program_account.get_state()? { UpgradeableLoaderState::Program { programdata_address, } => { if programdata_address != programdata_key { ic_logger_msg!( log_collector, "Program account does not match ProgramData account" ); return Err(InstructionError::InvalidArgument); } } _ => { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidAccountData); } } drop(program_account); /// Calculating the New Length let old_len = programdata_account.get_data().len(); let new_len = old_len.saturating_add(additional_bytes as usize); if new_len > MAX_PERMITTED_DATA_LENGTH as usize { ic_logger_msg!( log_collector, "Extended ProgramData length of {} bytes exceeds max account data length of {} bytes", new_len, MAX_PERMITTED_DATA_LENGTH ); return Err(InstructionError::InvalidRealloc); } /// Verifying Upgradeability and Slot Constraints let clock_slot = invoke_context .get_sysvar_cache() .get_clock() .map(|clock| clock.slot)?; let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } = programdata_account.get_state()? { if clock_slot == slot { ic_logger_msg!(log_collector, "Program was extended in this block already"); return Err(InstructionError::InvalidArgument); } if upgrade_authority_address.is_none() { ic_logger_msg!( log_collector, "Cannot extend ProgramData accounts that are not upgradeable" ); return Err(InstructionError::Immutable); } upgrade_authority_address } else { ic_logger_msg!(log_collector, "ProgramData state is invalid"); return Err(InstructionError::InvalidAccountData); }; /// Ensuring Rent-Exemption let required_payment = { let balance = programdata_account.get_lamports(); let rent = invoke_context.get_sysvar_cache().get_rent()?; let min_balance = rent.minimum_balance(new_len).max(1); min_balance.saturating_sub(balance) }; // Borrowed accounts need to be dropped before native_invoke drop(programdata_account); /// Funding the Extended Space // Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context let program_id = *program_id; if required_payment > 0 { let payer_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction( OPTIONAL_PAYER_ACCOUNT_INDEX, )?, )?; invoke_context.native_invoke( system_instruction::transfer(&payer_key, &programdata_key, required_payment) .into(), &[], )?; } /// Extending the Data Length let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let mut programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; programdata_account.set_data_length(new_len)?; let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); /// Redeploying the Extended Program deploy_program!( invoke_context, program_key, &program_id, UpgradeableLoaderState::size_of_program().saturating_add(new_len), clock_slot, { drop(programdata_account); }, programdata_account .get_data() .get(programdata_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); /// Updating the ProgramData State let mut programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; programdata_account.set_state(&UpgradeableLoaderState::ProgramData { slot: clock_slot, upgrade_authority_address, })?; /// Logging Success ic_logger_msg!( log_collector, "Extended ProgramData account by {} bytes", additional_bytes ); } }

Load and Execute