diff --git a/program-runtime/benches/instruction_processor.rs b/program-runtime/benches/instruction_processor.rs index 5b6f4dd4e8..5de050e806 100644 --- a/program-runtime/benches/instruction_processor.rs +++ b/program-runtime/benches/instruction_processor.rs @@ -26,6 +26,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { &post, &mut ExecuteDetailsTimings::default(), false, + true, ), Ok(()) ); @@ -39,6 +40,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { &post, &mut ExecuteDetailsTimings::default(), false, + true, ) .unwrap(); }); @@ -63,6 +65,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { &post, &mut ExecuteDetailsTimings::default(), false, + true, ) .unwrap(); }); diff --git a/program-runtime/src/instruction_processor.rs b/program-runtime/src/instruction_processor.rs index e4c450c281..2069211923 100644 --- a/program-runtime/src/instruction_processor.rs +++ b/program-runtime/src/instruction_processor.rs @@ -4,13 +4,14 @@ use solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, account_utils::StateMut, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - feature_set::{demote_program_write_locks, fix_write_privs}, + feature_set::{demote_program_write_locks, do_support_realloc, fix_write_privs}, ic_msg, instruction::{Instruction, InstructionError}, message::Message, process_instruction::{Executor, InvokeContext, ProcessInstructionWithContext}, pubkey::Pubkey, rent::Rent, + system_instruction::MAX_PERMITTED_DATA_LENGTH, system_program, }; use std::{ @@ -115,6 +116,7 @@ impl PreAccount { post: &AccountSharedData, timings: &mut ExecuteDetailsTimings, outermost_call: bool, + do_support_realloc: bool, ) -> Result<(), InstructionError> { let pre = self.account.borrow(); @@ -150,15 +152,30 @@ impl PreAccount { } } - // Only the system program can change the size of the data - // and only if the system program owns the account - let data_len_changed = pre.data().len() != post.data().len(); - if data_len_changed - && (!system_program::check_id(program_id) // line coverage used to get branch coverage - || !system_program::check_id(pre.owner())) - { - return Err(InstructionError::AccountDataSizeChanged); - } + let data_len_changed = if do_support_realloc { + // Account data size cannot exceed a maxumum length + if post.data().len() > MAX_PERMITTED_DATA_LENGTH as usize { + return Err(InstructionError::InvalidRealloc); + } + + // The owner of the account can change the size of the data + let data_len_changed = pre.data().len() != post.data().len(); + if data_len_changed && program_id != pre.owner() { + return Err(InstructionError::AccountDataSizeChanged); + } + data_len_changed + } else { + // Only the system program can change the size of the data + // and only if the system program owns the account + let data_len_changed = pre.data().len() != post.data().len(); + if data_len_changed + && (!system_program::check_id(program_id) // line coverage used to get branch coverage + || !system_program::check_id(pre.owner())) + { + return Err(InstructionError::AccountDataSizeChanged); + } + data_len_changed + }; // Only the owner may change account data // and if the account is writable @@ -502,6 +519,7 @@ impl InstructionProcessor { keyed_account_indices_obsolete: &[usize], signers: &[Pubkey], ) -> Result<(), InstructionError> { + let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id()); let invoke_context = RefCell::new(invoke_context); let mut invoke_context = invoke_context.borrow_mut(); @@ -541,7 +559,8 @@ impl InstructionProcessor { // Verify the called program has not misbehaved for (account, prev_size) in accounts.iter() { - if *prev_size != account.borrow().data().len() && *prev_size != 0 { + if !do_support_realloc && *prev_size != account.borrow().data().len() && *prev_size != 0 + { // Only support for `CreateAccount` at this time. // Need a way to limit total realloc size across multiple CPI calls ic_msg!( @@ -617,7 +636,7 @@ impl InstructionProcessor { #[cfg(test)] mod tests { use super::*; - use solana_sdk::{account::Account, instruction::InstructionError}; + use solana_sdk::{account::Account, instruction::InstructionError, system_program}; #[test] fn test_is_zeroed() { @@ -706,6 +725,7 @@ mod tests { &self.post, &mut ExecuteDetailsTimings::default(), false, + true, ) } } @@ -1013,11 +1033,18 @@ mod tests { "system program should not be able to change another program's account data size" ); assert_eq!( - Change::new(&alice_program_id, &alice_program_id) + Change::new(&alice_program_id, &solana_sdk::pubkey::new_rand()) .data(vec![0], vec![0, 0]) .verify(), Err(InstructionError::AccountDataSizeChanged), - "non-system programs cannot change their data size" + "one program should not be able to change another program's account data size" + ); + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .data(vec![0], vec![0, 0]) + .verify(), + Ok(()), + "programs can change their own data size" ); assert_eq!( Change::new(&system_program::id(), &system_program::id()) @@ -1039,7 +1066,7 @@ mod tests { .executable(false, true) .verify(), Err(InstructionError::ExecutableModified), - "Program should not be able to change owner and executable at the same time" + "program should not be able to change owner and executable at the same time" ); } diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 298bd740ca..48c04b77d9 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2499,6 +2499,8 @@ dependencies = [ "net2", "solana-account-decoder", "solana-bpf-loader-program", + "solana-bpf-rust-realloc", + "solana-bpf-rust-realloc-invoke", "solana-cli-output", "solana-logger 1.8.0", "solana-measure", @@ -2725,6 +2727,21 @@ dependencies = [ "solana-program 1.8.0", ] +[[package]] +name = "solana-bpf-rust-realloc" +version = "1.8.0" +dependencies = [ + "solana-program 1.8.0", +] + +[[package]] +name = "solana-bpf-rust-realloc-invoke" +version = "1.8.0" +dependencies = [ + "solana-bpf-rust-realloc", + "solana-program 1.8.0", +] + [[package]] name = "solana-bpf-rust-ro-account_modify" version = "1.8.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index ee918fe9b4..04c450bdba 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -26,7 +26,9 @@ itertools = "0.10.1" log = "0.4.11" miow = "0.3.6" net2 = "0.2.37" -solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.8.0" } +solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.8.0"} +solana-bpf-rust-realloc = { path = "rust/realloc", version = "=1.8.0", features = ["custom-heap"]} +solana-bpf-rust-realloc-invoke = { path = "rust/realloc_invoke", version = "=1.8.0", features = ["custom-heap"]} solana-cli-output = { path = "../../cli-output", version = "=1.8.0" } solana-logger = { path = "../../logger", version = "=1.8.0" } solana-measure = { path = "../../measure", version = "=1.8.0" } @@ -36,7 +38,6 @@ solana-sdk = { path = "../../sdk", version = "=1.8.0" } solana-transaction-status = { path = "../../transaction-status", version = "=1.8.0" } solana-account-decoder = { path = "../../account-decoder", version = "=1.8.0" } - [[bench]] name = "bpf_loader" @@ -71,6 +72,8 @@ members = [ "rust/param_passing", "rust/param_passing_dep", "rust/rand", + "rust/realloc", + "rust/realloc_invoke", "rust/ro_modify", "rust/ro_account_modify", "rust/sanity", diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index 980f1f433c..c1f2054a43 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -113,6 +113,7 @@ fn bench_program_alu(bencher: &mut Bencher) { executable.as_ref(), &mut inner_iter, &mut invoke_context, + &[], ) .unwrap(); @@ -220,7 +221,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) { // Serialize account data let keyed_accounts = invoke_context.get_keyed_accounts().unwrap(); - let (mut serialized, _account_lengths) = serialize_parameters( + let (mut serialized, account_lengths) = serialize_parameters( &bpf_loader::id(), &solana_sdk::pubkey::new_rand(), keyed_accounts, @@ -243,6 +244,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) { executable.as_ref(), serialized.as_slice_mut(), &mut invoke_context, + &account_lengths, ) .unwrap(); diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 1f7be94e63..64423948d5 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -84,6 +84,8 @@ fn main() { "panic", "param_passing", "rand", + "realloc", + "realloc_invoke", "ro_modify", "ro_account_modify", "sanity", diff --git a/programs/bpf/rust/realloc/Cargo.toml b/programs/bpf/rust/realloc/Cargo.toml new file mode 100644 index 0000000000..0e81372b3b --- /dev/null +++ b/programs/bpf/rust/realloc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "solana-bpf-rust-realloc" +version = "1.8.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-realloc" +edition = "2018" + +[features] +custom-heap = [] + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=1.8.0" } + +[lib] +crate-type = ["lib", "cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/realloc/src/instructions.rs b/programs/bpf/rust/realloc/src/instructions.rs new file mode 100644 index 0000000000..192f75b247 --- /dev/null +++ b/programs/bpf/rust/realloc/src/instructions.rs @@ -0,0 +1,71 @@ +//! @brief Example Rust-based BPF realloc test program + +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +pub const REALLOC: u8 = 1; +pub const REALLOC_EXTEND: u8 = 2; +pub const REALLOC_EXTEND_AND_FILL: u8 = 3; +pub const REALLOC_AND_ASSIGN: u8 = 4; +pub const REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM: u8 = 5; +pub const ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC: u8 = 6; +pub const DEALLOC_AND_ASSIGN_TO_CALLER: u8 = 7; +pub const CHECK: u8 = 8; +pub const ZERO_INIT: u8 = 9; + +pub fn realloc(program_id: &Pubkey, address: &Pubkey, size: usize, bump: &mut u8) -> Instruction { + let mut instruction_data = vec![REALLOC, *bump]; + instruction_data.extend_from_slice(&size.to_le_bytes()); + + *bump += 1; + + Instruction::new_with_bytes( + *program_id, + &instruction_data, + vec![AccountMeta::new(*address, false)], + ) +} + +pub fn realloc_extend( + program_id: &Pubkey, + address: &Pubkey, + size: usize, + bump: &mut u8, +) -> Instruction { + let mut instruction_data = vec![REALLOC_EXTEND, *bump]; + instruction_data.extend_from_slice(&size.to_le_bytes()); + + *bump += 1; + + Instruction::new_with_bytes( + *program_id, + &instruction_data, + vec![AccountMeta::new(*address, false)], + ) +} + +pub fn realloc_extend_and_fill( + program_id: &Pubkey, + address: &Pubkey, + size: usize, + fill: u8, + bump: &mut u64, +) -> Instruction { + let mut instruction_data = vec![ + REALLOC_EXTEND_AND_FILL, + fill, + *bump as u8, + (*bump / 255) as u8, + ]; + instruction_data.extend_from_slice(&size.to_le_bytes()); + + *bump += 1; + + Instruction::new_with_bytes( + *program_id, + &instruction_data, + vec![AccountMeta::new(*address, false)], + ) +} diff --git a/programs/bpf/rust/realloc/src/lib.rs b/programs/bpf/rust/realloc/src/lib.rs new file mode 100644 index 0000000000..5a4c500425 --- /dev/null +++ b/programs/bpf/rust/realloc/src/lib.rs @@ -0,0 +1,134 @@ +//! @brief Example Rust-based BPF realloc test program + +pub mod instructions; + +extern crate solana_program; +use crate::instructions::*; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, + entrypoint::MAX_PERMITTED_DATA_INCREASE, msg, program::invoke, pubkey::Pubkey, + system_instruction, system_program, +}; +use std::convert::TryInto; + +entrypoint!(process_instruction); +#[allow(clippy::unnecessary_wraps)] +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let account = &accounts[0]; + + match instruction_data[0] { + REALLOC => { + let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::()); + let new_len = usize::from_le_bytes(bytes.try_into().unwrap()); + msg!("realloc to {}", new_len); + account.realloc(new_len, false)?; + assert_eq!(new_len, account.data_len()); + } + REALLOC_EXTEND => { + let pre_len = account.data_len(); + let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::()); + let new_len = pre_len + usize::from_le_bytes(bytes.try_into().unwrap()); + msg!("realloc extend by {}", new_len); + account.realloc(new_len, false)?; + assert_eq!(new_len, account.data_len()); + } + REALLOC_EXTEND_AND_FILL => { + let pre_len = account.data_len(); + let fill = instruction_data[2]; + let (bytes, _) = instruction_data[4..].split_at(std::mem::size_of::()); + let new_len = pre_len + usize::from_le_bytes(bytes.try_into().unwrap()); + msg!("realloc extend by {}", new_len); + account.realloc(new_len, false)?; + assert_eq!(new_len, account.data_len()); + account.try_borrow_mut_data()?[pre_len..].fill(fill); + } + REALLOC_AND_ASSIGN => { + msg!("realloc and assign"); + account.realloc(MAX_PERMITTED_DATA_INCREASE, false)?; + assert_eq!(MAX_PERMITTED_DATA_INCREASE, account.data_len()); + account.assign(&system_program::id()); + assert_eq!(*account.owner, system_program::id()); + } + REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM => { + msg!("realloc and assign to self via system program"); + let pre_len = account.data_len(); + account.realloc(pre_len + MAX_PERMITTED_DATA_INCREASE, false)?; + assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len()); + invoke( + &system_instruction::assign(account.key, program_id), + accounts, + )?; + assert_eq!(account.owner, program_id); + } + ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC => { + msg!("assign to self via system program and realloc"); + let pre_len = account.data_len(); + invoke( + &system_instruction::assign(account.key, program_id), + accounts, + )?; + assert_eq!(account.owner, program_id); + account.realloc(pre_len + MAX_PERMITTED_DATA_INCREASE, false)?; + assert_eq!(account.data_len(), pre_len + MAX_PERMITTED_DATA_INCREASE); + } + DEALLOC_AND_ASSIGN_TO_CALLER => { + msg!("dealloc and assign to caller"); + account.realloc(0, false)?; + assert_eq!(account.data_len(), 0); + account.assign(accounts[1].key); + assert_eq!(account.owner, accounts[1].key); + } + CHECK => { + msg!("check"); + assert_eq!(100, account.data_len()); + let data = account.try_borrow_mut_data()?; + for x in data[0..5].iter() { + assert_eq!(0, *x); + } + for x in data[5..].iter() { + assert_eq!(2, *x); + } + } + ZERO_INIT => { + account.realloc(10, false)?; + { + let mut data = account.try_borrow_mut_data()?; + for i in 0..10 { + assert_eq!(0, data[i]); + } + data.fill(1); + for i in 0..10 { + assert_eq!(1, data[i]); + } + } + + account.realloc(5, false)?; + account.realloc(10, false)?; + { + let data = account.try_borrow_data()?; + for i in 0..10 { + assert_eq!(1, data[i]); + } + } + + account.realloc(5, false)?; + account.realloc(10, true)?; + { + let data = account.try_borrow_data()?; + for i in 0..5 { + assert_eq!(1, data[i]); + } + for i in 5..10 { + assert_eq!(0, data[i]); + } + } + } + _ => panic!(), + } + + Ok(()) +} diff --git a/programs/bpf/rust/realloc_invoke/Cargo.toml b/programs/bpf/rust/realloc_invoke/Cargo.toml new file mode 100644 index 0000000000..703762d072 --- /dev/null +++ b/programs/bpf/rust/realloc_invoke/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "solana-bpf-rust-realloc-invoke" +version = "1.8.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-realloc-invoke" +edition = "2018" + +[features] +custom-heap = [] + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=1.8.0" } +solana-bpf-rust-realloc = { path = "../realloc", version = "=1.8.0", features = ["custom-heap"]} + +[lib] +crate-type = ["lib", "cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/realloc_invoke/src/instructions.rs b/programs/bpf/rust/realloc_invoke/src/instructions.rs new file mode 100644 index 0000000000..83d026e8d6 --- /dev/null +++ b/programs/bpf/rust/realloc_invoke/src/instructions.rs @@ -0,0 +1,18 @@ +//! @brief Example Rust-based BPF realloc test program + +pub const INVOKE_REALLOC_ZERO_RO: u8 = 0; +pub const INVOKE_REALLOC_ZERO: u8 = 1; +pub const INVOKE_REALLOC_MAX_PLUS_ONE: u8 = 2; +pub const INVOKE_REALLOC_EXTEND_MAX: u8 = 3; +pub const INVOKE_REALLOC_AND_ASSIGN: u8 = 4; +pub const INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM: u8 = 5; +pub const INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC: u8 = 6; +pub const INVOKE_REALLOC_INVOKE_CHECK: u8 = 7; +pub const INVOKE_OVERFLOW: u8 = 8; +pub const INVOKE_REALLOC_TO: u8 = 9; +pub const INVOKE_REALLOC_RECURSIVE: u8 = 10; +pub const INVOKE_CREATE_ACCOUNT_REALLOC_CHECK: u8 = 11; +pub const INVOKE_DEALLOC_AND_ASSIGN: u8 = 12; +pub const INVOKE_REALLOC_MAX_TWICE: u8 = 13; +pub const INVOKE_REALLOC_MAX_INVOKE_MAX: u8 = 14; +pub const INVOKE_INVOKE_MAX_TWICE: u8 = 15; diff --git a/programs/bpf/rust/realloc_invoke/src/lib.rs b/programs/bpf/rust/realloc_invoke/src/lib.rs new file mode 100644 index 0000000000..ca090bc385 --- /dev/null +++ b/programs/bpf/rust/realloc_invoke/src/lib.rs @@ -0,0 +1,301 @@ +//! @brief Example Rust-based BPF realloc test program + +pub mod instructions; + +extern crate solana_program; +use crate::instructions::*; +use solana_bpf_rust_realloc::instructions::*; +use solana_program::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + entrypoint::MAX_PERMITTED_DATA_INCREASE, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + pubkey::Pubkey, + system_instruction, system_program, +}; +use std::convert::TryInto; + +entrypoint!(process_instruction); +#[allow(clippy::unnecessary_wraps)] +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let account = &accounts[0]; + let invoke_program_id = accounts[1].key; + let pre_len = account.data_len(); + let mut bump = 0; + + match instruction_data[0] { + INVOKE_REALLOC_ZERO_RO => { + msg!("invoke realloc to zero of ro account"); + // Realloc RO account + let mut instruction = realloc(invoke_program_id, account.key, 0, &mut bump); + instruction.accounts[0].is_writable = false; + invoke(&instruction, accounts)?; + } + INVOKE_REALLOC_ZERO => { + msg!("invoke realloc to zero"); + invoke( + &realloc(invoke_program_id, account.key, 0, &mut bump), + accounts, + )?; + assert_eq!(0, account.data_len()); + } + INVOKE_REALLOC_MAX_PLUS_ONE => { + msg!("invoke realloc max + 1"); + invoke( + &realloc( + invoke_program_id, + account.key, + MAX_PERMITTED_DATA_INCREASE + 1, + &mut bump, + ), + accounts, + )?; + } + INVOKE_REALLOC_EXTEND_MAX => { + msg!("invoke realloc max"); + invoke( + &realloc_extend( + invoke_program_id, + account.key, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + accounts, + )?; + assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len()); + } + INVOKE_REALLOC_MAX_TWICE => { + msg!("invoke realloc max twice"); + invoke( + &realloc( + invoke_program_id, + account.key, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + accounts, + )?; + let new_len = pre_len + MAX_PERMITTED_DATA_INCREASE; + assert_eq!(new_len, account.data_len()); + account.realloc(new_len + MAX_PERMITTED_DATA_INCREASE, false)?; + assert_eq!(new_len + MAX_PERMITTED_DATA_INCREASE, account.data_len()); + } + INVOKE_REALLOC_AND_ASSIGN => { + msg!("invoke realloc and assign"); + invoke( + &Instruction::new_with_bytes( + *invoke_program_id, + &[REALLOC_AND_ASSIGN], + vec![AccountMeta::new(*account.key, false)], + ), + accounts, + )?; + assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len()); + assert_eq!(*account.owner, system_program::id()); + } + INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM => { + msg!("invoke realloc and assign to self via system program"); + invoke( + &Instruction::new_with_bytes( + *accounts[1].key, + &[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], + vec![ + AccountMeta::new(*account.key, true), + AccountMeta::new_readonly(*accounts[2].key, false), + ], + ), + accounts, + )?; + } + INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC => { + msg!("invoke assign to self and realloc via system program"); + invoke( + &Instruction::new_with_bytes( + *accounts[1].key, + &[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], + vec![ + AccountMeta::new(*account.key, true), + AccountMeta::new_readonly(*accounts[2].key, false), + ], + ), + accounts, + )?; + } + INVOKE_REALLOC_INVOKE_CHECK => { + msg!("realloc invoke check size"); + account.realloc(100, false)?; + assert_eq!(100, account.data_len()); + account.try_borrow_mut_data()?[pre_len..].fill(2); + invoke( + &Instruction::new_with_bytes( + *accounts[1].key, + &[CHECK], + vec![AccountMeta::new(*account.key, false)], + ), + accounts, + )?; + } + INVOKE_REALLOC_TO => { + let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::()); + let new_len = usize::from_le_bytes(bytes.try_into().unwrap()); + msg!("realloc to {}", new_len); + account.realloc(new_len, false)?; + assert_eq!(new_len, account.data_len()); + if pre_len < new_len { + account.try_borrow_mut_data()?[pre_len..].fill(instruction_data[1]); + } + } + INVOKE_REALLOC_RECURSIVE => { + msg!("realloc invoke recursive"); + let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::()); + let new_len = usize::from_le_bytes(bytes.try_into().unwrap()); + account.realloc(new_len, false)?; + assert_eq!(new_len, account.data_len()); + account.try_borrow_mut_data()?[pre_len..].fill(instruction_data[1]); + let final_len: usize = 200; + let mut new_instruction_data = vec![]; + new_instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO, 2]); + new_instruction_data.extend_from_slice(&final_len.to_le_bytes()); + invoke( + &Instruction::new_with_bytes( + *program_id, + &new_instruction_data, + vec![ + AccountMeta::new(*account.key, false), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ), + accounts, + )?; + assert_eq!(final_len, account.data_len()); + let data = account.try_borrow_mut_data()?; + for i in 0..new_len { + assert_eq!(data[i], instruction_data[1]); + } + for i in new_len..final_len { + assert_eq!(data[i], new_instruction_data[1]); + } + } + INVOKE_CREATE_ACCOUNT_REALLOC_CHECK => { + msg!("Create new account, realloc, and check"); + let pre_len: usize = 100; + invoke( + &system_instruction::create_account( + accounts[0].key, + accounts[1].key, + 1, + pre_len as u64, + program_id, + ), + accounts, + )?; + assert_eq!(pre_len, accounts[1].data_len()); + accounts[1].realloc(pre_len + 1, false)?; + assert_eq!(pre_len + 1, accounts[1].data_len()); + assert_eq!(accounts[1].owner, program_id); + let final_len: usize = 200; + let mut new_instruction_data = vec![]; + new_instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO, 2]); + new_instruction_data.extend_from_slice(&final_len.to_le_bytes()); + invoke( + &Instruction::new_with_bytes( + *program_id, + &new_instruction_data, + vec![ + AccountMeta::new(*accounts[1].key, false), + AccountMeta::new_readonly(*accounts[3].key, false), + ], + ), + accounts, + )?; + assert_eq!(final_len, accounts[1].data_len()); + } + INVOKE_DEALLOC_AND_ASSIGN => { + msg!("realloc zerod"); + let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::()); + let pre_len = usize::from_le_bytes(bytes.try_into().unwrap()); + let new_len = pre_len * 2; + assert_eq!(pre_len, 100); + { + let data = account.try_borrow_mut_data()?; + for i in 0..pre_len { + assert_eq!(data[i], instruction_data[1]); + } + } + + invoke( + &Instruction::new_with_bytes( + *accounts[2].key, + &[DEALLOC_AND_ASSIGN_TO_CALLER], + vec![ + AccountMeta::new(*account.key, false), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ), + accounts, + )?; + assert_eq!(account.owner, program_id); + assert_eq!(account.data_len(), 0); + account.realloc(new_len, false)?; + assert_eq!(account.data_len(), new_len); + { + let data = account.try_borrow_mut_data()?; + for i in 0..new_len { + assert_eq!(data[i], 0); + } + } + } + INVOKE_REALLOC_MAX_INVOKE_MAX => { + msg!("invoke realloc max invoke max"); + assert_eq!(0, account.data_len()); + account.realloc(MAX_PERMITTED_DATA_INCREASE, false)?; + assert_eq!(MAX_PERMITTED_DATA_INCREASE, account.data_len()); + account.assign(invoke_program_id); + assert_eq!(account.owner, invoke_program_id); + invoke( + &realloc_extend( + invoke_program_id, + account.key, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + accounts, + )?; + } + INVOKE_INVOKE_MAX_TWICE => { + msg!("invoke invoke max twice"); + assert_eq!(0, account.data_len()); + account.assign(accounts[2].key); + assert_eq!(account.owner, accounts[2].key); + invoke( + &realloc( + accounts[2].key, + account.key, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + accounts, + )?; + invoke( + &realloc_extend( + accounts[2].key, + account.key, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + accounts, + )?; + panic!("last invoke should fail"); + } + _ => panic!(), + } + + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 687dbdfede..c99dfacff6 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -14,6 +14,8 @@ use solana_bpf_loader_program::{ syscalls::register_syscalls, BpfError, ThisInstructionMeter, }; +use solana_bpf_rust_realloc::instructions::*; +use solana_bpf_rust_realloc_invoke::instructions::*; use solana_cli_output::display::println_transaction; use solana_rbpf::{ static_analysis::Analysis, @@ -43,7 +45,8 @@ use solana_sdk::{ process_instruction::{InvokeContext, MockInvokeContext}, pubkey::Pubkey, signature::{keypair_from_seed, Keypair, Signer}, - system_instruction, system_program, sysvar, + system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, + system_program, sysvar, sysvar::{clock, rent}, transaction::{Transaction, TransactionError}, }; @@ -234,6 +237,7 @@ fn run_program( executable.as_ref(), parameter_bytes.as_slice_mut(), &mut invoke_context, + &account_lengths, ) .unwrap(); let result = if i == 0 { @@ -283,6 +287,7 @@ fn run_program( parameter_accounts, parameter_bytes.as_slice(), &account_lengths, + true, ) .unwrap(); } @@ -2536,3 +2541,716 @@ fn test_program_bpf_ro_account_modify() { TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); } + +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_realloc() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mint_pubkey = mint_keypair.pubkey(); + let signer = &[&mint_keypair]; + + let mut bank = Bank::new_for_tests(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin(&name, id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_realloc", + ); + + let mut bump = 0; + let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); + let account = AccountSharedData::new(42, 5, &program_id); + bank.store_account(&pubkey, &account); + + // Realloc RO account + let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump); + instruction.accounts[0].is_writable = false; + assert_eq!( + bank_client + .send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) + ); + + // Realloc account to overflow + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&program_id, &pubkey, usize::MAX, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc account to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // Realloc to max + 1 + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE + 1, + &mut bump + )], + Some(&mint_pubkey), + ), + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc to max length in max increase increments + for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE { + let mut bump = i as u64; + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc_extend_and_fill( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE, + 1, + &mut bump, + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len()); + } + for i in 0..data.len() { + assert_eq!(data[i], 1); + } + + // and one more time should fail + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc_extend( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE, + &mut bump + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // Realloc and assign + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + program_id, + &[REALLOC_AND_ASSIGN], + vec![AccountMeta::new(pubkey, false)], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let account = bank.get_account(&pubkey).unwrap(); + assert_eq!(&solana_sdk::system_program::id(), account.owner()); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len()); + + // Realloc to 0 with wrong owner + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) + ); + + // realloc and assign to self via cpi + assert_eq!( + bank_client + .send_and_confirm_message( + &[&mint_keypair, &keypair], + Message::new( + &[Instruction::new_with_bytes( + program_id, + &[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) + ); + + // Assign to self and realloc via cpi + bank_client + .send_and_confirm_message( + &[&mint_keypair, &keypair], + Message::new( + &[Instruction::new_with_bytes( + program_id, + &[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let account = bank.get_account(&pubkey).unwrap(); + assert_eq!(&program_id, account.owner()); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len()); + + // Realloc to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // zero-init + bank_client + .send_and_confirm_message( + &[&mint_keypair, &keypair], + Message::new( + &[Instruction::new_with_bytes( + program_id, + &[ZERO_INIT], + vec![AccountMeta::new(pubkey, true)], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); +} + +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_realloc_invoke() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mint_pubkey = mint_keypair.pubkey(); + let signer = &[&mint_keypair]; + + let mut bank = Bank::new_for_tests(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin(&name, id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let realloc_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_realloc", + ); + + let realloc_invoke_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_realloc_invoke", + ); + + let mut bump = 0; + let keypair = Keypair::new(); + let pubkey = keypair.pubkey().clone(); + let account = AccountSharedData::new(42, 5, &realloc_program_id); + bank.store_account(&pubkey, &account); + let invoke_keypair = Keypair::new(); + let invoke_pubkey = invoke_keypair.pubkey().clone(); + + // Realloc RO account + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_ZERO_RO], + vec![ + AccountMeta::new_readonly(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) + ); + + // Realloc account to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // Realloc to max + 1 + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_PLUS_ONE], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc to max twice + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_TWICE], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc to max length in max increase increments + for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE { + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len()); + } + for i in 0..data.len() { + assert_eq!(data[i], 1); + } + + // and one more time should fail + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // Realloc and assign + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_AND_ASSIGN], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let account = bank.get_account(&pubkey).unwrap(); + assert_eq!(&solana_sdk::system_program::id(), account.owner()); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len()); + + // Realloc to 0 with wrong owner + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) + ); + + // realloc and assign to self via system program + assert_eq!( + bank_client + .send_and_confirm_message( + &[&mint_keypair, &keypair], + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new_readonly(realloc_program_id, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) + ); + + // Assign to self and realloc via system program + bank_client + .send_and_confirm_message( + &[&mint_keypair, &keypair], + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new_readonly(realloc_program_id, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let account = bank.get_account(&pubkey).unwrap(); + assert_eq!(&realloc_program_id, account.owner()); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len()); + + // Realloc to 0 + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert_eq!(0, data.len()); + + // Realloc to 100 and check via CPI + let invoke_account = AccountSharedData::new(42, 5, &realloc_invoke_program_id); + bank.store_account(&invoke_pubkey, &invoke_account); + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_INVOKE_CHECK], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client + .get_account_data(&invoke_pubkey) + .unwrap() + .unwrap(); + assert_eq!(100, data.len()); + for i in 0..5 { + assert_eq!(data[i], 0); + } + for i in 5..data.len() { + assert_eq!(data[i], 2); + } + + // Realloc rescursively and fill data + let invoke_keypair = Keypair::new(); + let invoke_pubkey = invoke_keypair.pubkey().clone(); + let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id); + bank.store_account(&invoke_pubkey, &invoke_account); + let mut instruction_data = vec![]; + instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]); + instruction_data.extend_from_slice(&100_usize.to_le_bytes()); + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client + .get_account_data(&invoke_pubkey) + .unwrap() + .unwrap(); + assert_eq!(200, data.len()); + for i in 0..100 { + assert_eq!(data[i], 1); + } + for i in 100..200 { + assert_eq!(data[i], 2); + } + + // Create account, realloc, check + let new_keypair = Keypair::new(); + let new_pubkey = new_keypair.pubkey().clone(); + let mut instruction_data = vec![]; + instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]); + instruction_data.extend_from_slice(&100_usize.to_le_bytes()); + bank_client + .send_and_confirm_message( + &[&mint_keypair, &new_keypair], + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(new_pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap(); + assert_eq!(200, data.len()); + let account = bank.get_account(&new_pubkey).unwrap(); + assert_eq!(&realloc_invoke_program_id, account.owner()); + + // Invoke, dealloc, and assign + let pre_len = 100; + let new_len = pre_len * 2; + let mut invoke_account = AccountSharedData::new(42, pre_len, &realloc_program_id); + invoke_account.set_data_from_slice(&vec![1; pre_len]); + bank.store_account(&invoke_pubkey, &invoke_account); + let mut instruction_data = vec![]; + instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]); + instruction_data.extend_from_slice(&pre_len.to_le_bytes()); + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ), + ) + .unwrap(); + let data = bank_client + .get_account_data(&invoke_pubkey) + .unwrap() + .unwrap(); + assert_eq!(new_len, data.len()); + for i in 0..new_len { + assert_eq!(data[i], 0); + } + + // Realloc to max invoke max + let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id); + bank.store_account(&invoke_pubkey, &invoke_account); + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_INVOKE_MAX], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); + + // Realloc invoke max twice + let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id); + bank.store_account(&invoke_pubkey, &invoke_account); + assert_eq!( + bank_client + .send_and_confirm_message( + signer, + Message::new( + &[Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_INVOKE_MAX_TWICE], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + )], + Some(&mint_pubkey), + ) + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidRealloc) + ); +} diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index e6fad12b82..67ddacbf4f 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -31,8 +31,8 @@ use solana_sdk::{ clock::Clock, entrypoint::{HEAP_LENGTH, SUCCESS}, feature_set::{ - add_missing_program_error_mappings, close_upgradeable_program_accounts, fix_write_privs, - reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero, + add_missing_program_error_mappings, close_upgradeable_program_accounts, do_support_realloc, + fix_write_privs, reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero, }, ic_logger_msg, ic_msg, instruction::{AccountMeta, InstructionError}, @@ -150,12 +150,19 @@ pub fn create_vm<'a>( program: &'a dyn Executable, parameter_bytes: &mut [u8], invoke_context: &'a mut dyn InvokeContext, + orig_data_lens: &'a [usize], ) -> Result, EbpfError> { let compute_budget = invoke_context.get_compute_budget(); let mut heap = AlignedMemory::new_with_size(compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN); let mut vm = EbpfVm::new(program, heap.as_slice_mut(), parameter_bytes)?; - syscalls::bind_syscall_context_objects(loader_id, &mut vm, invoke_context, heap)?; + syscalls::bind_syscall_context_objects( + loader_id, + &mut vm, + invoke_context, + heap, + orig_data_lens, + )?; Ok(vm) } @@ -903,6 +910,7 @@ impl Executor for BpfExecutor { self.executable.as_ref(), parameter_bytes.as_slice_mut(), invoke_context, + &account_lengths, ) { Ok(info) => info, Err(e) => { @@ -979,6 +987,7 @@ impl Executor for BpfExecutor { keyed_accounts, parameter_bytes.as_slice(), &account_lengths, + invoke_context.is_feature_active(&do_support_realloc::id()), )?; deserialize_time.stop(); invoke_context.update_timing( diff --git a/programs/bpf_loader/src/serialization.rs b/programs/bpf_loader/src/serialization.rs index 0d5f222303..d0b0063f44 100644 --- a/programs/bpf_loader/src/serialization.rs +++ b/programs/bpf_loader/src/serialization.rs @@ -7,6 +7,7 @@ use solana_sdk::{ instruction::InstructionError, keyed_account::KeyedAccount, pubkey::Pubkey, + system_instruction::MAX_PERMITTED_DATA_LENGTH, }; use std::{ io::prelude::*, @@ -48,11 +49,12 @@ pub fn deserialize_parameters( keyed_accounts: &[KeyedAccount], buffer: &[u8], account_lengths: &[usize], + do_support_realloc: bool, ) -> Result<(), InstructionError> { if *loader_id == bpf_loader_deprecated::id() { deserialize_parameters_unaligned(keyed_accounts, buffer, account_lengths) } else { - deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths) + deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths, do_support_realloc) } } @@ -261,6 +263,7 @@ pub fn deserialize_parameters_aligned( keyed_accounts: &[KeyedAccount], buffer: &[u8], account_lengths: &[usize], + do_support_realloc: bool, ) -> Result<(), InstructionError> { let mut start = size_of::(); // number of accounts for (i, (keyed_account, pre_len)) in keyed_accounts @@ -285,13 +288,22 @@ pub fn deserialize_parameters_aligned( start += size_of::(); // lamports let post_len = LittleEndian::read_u64(&buffer[start..]) as usize; start += size_of::(); // data length - let mut data_end = start + *pre_len; - if post_len != *pre_len - && (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE - { - data_end = start + post_len; - } - + let data_end = if do_support_realloc { + if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE + || post_len > MAX_PERMITTED_DATA_LENGTH as usize + { + return Err(InstructionError::InvalidRealloc); + } + start + post_len + } else { + let mut data_end = start + *pre_len; + if post_len != *pre_len + && (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE + { + data_end = start + post_len; + } + data_end + }; account.set_data_from_slice(&buffer[start..data_end]); start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data start += (start as *const u8).align_offset(align_of::()); @@ -467,6 +479,7 @@ mod tests { &de_keyed_accounts, serialized.as_slice(), &account_lengths, + true, ) .unwrap(); for ((account, de_keyed_account), key) in @@ -520,6 +533,7 @@ mod tests { &de_keyed_accounts, serialized.as_slice(), &account_lengths, + true, ) .unwrap(); for ((account, de_keyed_account), key) in diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 0e3b41163b..d97ce1f5c8 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -21,8 +21,9 @@ use solana_sdk::{ feature_set::{ allow_native_ids, blake3_syscall_enabled, check_seed_length, close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar, - libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix, return_data_syscall_enabled, - secp256k1_recover_syscall_enabled, sol_log_data_syscall_enabled, + do_support_realloc, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix, + return_data_syscall_enabled, secp256k1_recover_syscall_enabled, + sol_log_data_syscall_enabled, }, hash::{Hasher, HASH_BYTES}, ic_msg, @@ -209,6 +210,7 @@ pub fn bind_syscall_context_objects<'a>( vm: &mut EbpfVm<'a, BpfError, crate::ThisInstructionMeter>, invoke_context: &'a mut dyn InvokeContext, heap: AlignedMemory, + orig_data_lens: &'a [usize], ) -> Result<(), EbpfError> { let compute_budget = invoke_context.get_compute_budget(); @@ -430,6 +432,7 @@ pub fn bind_syscall_context_objects<'a>( vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { invoke_context: invoke_context.clone(), + orig_data_lens, loader_id, }), None, @@ -437,6 +440,7 @@ pub fn bind_syscall_context_objects<'a>( vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedRust { invoke_context: invoke_context.clone(), + orig_data_lens, loader_id, }), None, @@ -1519,9 +1523,10 @@ impl<'a> SyscallObject for SyscallBlake3<'a> { // Cross-program invocation syscalls -struct AccountReferences<'a> { +struct CallerAccount<'a> { lamports: &'a mut u64, owner: &'a mut Pubkey, + original_data_len: usize, data: &'a mut [u8], vm_data_addr: u64, ref_to_len_in_vm: &'a mut u64, @@ -1531,10 +1536,7 @@ struct AccountReferences<'a> { } type TranslatedAccounts<'a> = ( Vec, - Vec<( - Rc>, - Option>, - )>, + Vec<(Rc>, Option>)>, ); /// Implemented by language specific data structure translators @@ -1567,6 +1569,7 @@ trait SyscallInvokeSigned<'a> { /// Cross-program invocation called from Rust pub struct SyscallInvokeSignedRust<'a> { invoke_context: Rc>, + orig_data_lens: &'a [usize], loader_id: &'a Pubkey, } impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { @@ -1694,9 +1697,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { ) }; - Ok(AccountReferences { + Ok(CallerAccount { lamports, owner, + original_data_len: 0, // set later data, vm_data_addr, ref_to_len_in_vm, @@ -1711,6 +1715,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { &account_info_keys, account_infos, invoke_context, + self.orig_data_lens, translate, ) } @@ -1839,6 +1844,7 @@ struct SolSignerSeedsC { /// Cross-program invocation called from C pub struct SyscallInvokeSignedC<'a> { invoke_context: Rc>, + orig_data_lens: &'a [usize], loader_id: &'a Pubkey, } impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { @@ -1964,9 +1970,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { self.loader_id, )?; - Ok(AccountReferences { + Ok(CallerAccount { lamports, owner, + original_data_len: 0, // set later data, vm_data_addr, ref_to_len_in_vm, @@ -1981,6 +1988,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { &account_info_keys, account_infos, invoke_context, + self.orig_data_lens, translate, ) } @@ -2065,10 +2073,11 @@ fn get_translated_accounts<'a, T, F>( account_info_keys: &[&Pubkey], account_infos: &[T], invoke_context: &mut dyn InvokeContext, + orig_data_lens: &[usize], do_translate: F, ) -> Result, EbpfError> where - F: Fn(&T, &mut dyn InvokeContext) -> Result, EbpfError>, + F: Fn(&T, &mut dyn InvokeContext) -> Result, EbpfError>, { let demote_program_write_locks = invoke_context.is_feature_active(&demote_program_write_locks::id()); @@ -2083,25 +2092,27 @@ where account_indices.push(account_index); accounts.push((account, None)); continue; - } else if let Some(account_ref_index) = + } else if let Some(caller_account_index) = account_info_keys.iter().position(|key| *key == account_key) { - let account_ref = do_translate(&account_infos[account_ref_index], invoke_context)?; + let mut caller_account = + do_translate(&account_infos[caller_account_index], invoke_context)?; { let mut account = account.borrow_mut(); - account.copy_into_owner_from_slice(account_ref.owner.as_ref()); - account.set_data_from_slice(account_ref.data); - account.set_lamports(*account_ref.lamports); - account.set_executable(account_ref.executable); - account.set_rent_epoch(account_ref.rent_epoch); + account.copy_into_owner_from_slice(caller_account.owner.as_ref()); + caller_account.original_data_len = orig_data_lens[caller_account_index]; + account.set_data_from_slice(caller_account.data); + account.set_lamports(*caller_account.lamports); + account.set_executable(caller_account.executable); + account.set_rent_epoch(caller_account.rent_epoch); } - let account_ref = if message.is_writable(i, demote_program_write_locks) { - Some(account_ref) + let caller_account = if message.is_writable(i, demote_program_write_locks) { + Some(caller_account) } else { None }; account_indices.push(account_index); - accounts.push((account, account_ref)); + accounts.push((account, caller_account)); continue; } } @@ -2176,6 +2187,7 @@ fn call<'a>( invoke_context .get_compute_meter() .consume(invoke_context.get_compute_budget().invoke_units)?; + let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id()); // Translate and verify caller's data let instruction = @@ -2219,13 +2231,14 @@ fn call<'a>( .map_err(SyscallError::InstructionError)?; // Copy results back to caller - for (account, account_ref) in accounts.iter_mut() { - let account = account.borrow(); - if let Some(account_ref) = account_ref { - *account_ref.lamports = account.lamports(); - *account_ref.owner = *account.owner(); - if account_ref.data.len() != account.data().len() { - if !account_ref.data.is_empty() { + for (callee_account, caller_account) in accounts.iter_mut() { + if let Some(caller_account) = caller_account { + let callee_account = callee_account.borrow(); + *caller_account.lamports = callee_account.lamports(); + *caller_account.owner = *callee_account.owner(); + let new_len = callee_account.data().len(); + if caller_account.data.len() != new_len { + if !do_support_realloc && !caller_account.data.is_empty() { // Only support for `CreateAccount` at this time. // Need a way to limit total realloc size across multiple CPI calls ic_msg!( @@ -2236,28 +2249,36 @@ fn call<'a>( SyscallError::InstructionError(InstructionError::InvalidRealloc).into(), ); } - if account.data().len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE { + let data_overflow = if do_support_realloc { + new_len > caller_account.original_data_len + MAX_PERMITTED_DATA_INCREASE + } else { + new_len > caller_account.data.len() + MAX_PERMITTED_DATA_INCREASE + }; + if data_overflow { ic_msg!( invoke_context, - "SystemProgram::CreateAccount data size limited to {} in inner instructions", + "Account data size realloc limited to {} in inner instructions", MAX_PERMITTED_DATA_INCREASE ); return Err( SyscallError::InstructionError(InstructionError::InvalidRealloc).into(), ); } - account_ref.data = translate_slice_mut::( + if new_len < caller_account.data.len() { + caller_account.data[new_len..].fill(0); + } + caller_account.data = translate_slice_mut::( memory_mapping, - account_ref.vm_data_addr, - account.data().len() as u64, + caller_account.vm_data_addr, + new_len as u64, &bpf_loader_deprecated::id(), // Don't care since it is byte aligned )?; - *account_ref.ref_to_len_in_vm = account.data().len() as u64; - *account_ref.serialized_len_ptr = account.data().len() as u64; + *caller_account.ref_to_len_in_vm = new_len as u64; + *caller_account.serialized_len_ptr = new_len as u64; } - account_ref + caller_account .data - .copy_from_slice(&account.data()[0..account_ref.data.len()]); + .copy_from_slice(&callee_account.data()[0..new_len]); } } diff --git a/rbpf-cli/src/main.rs b/rbpf-cli/src/main.rs index 12006e4a81..529cf0738e 100644 --- a/rbpf-cli/src/main.rs +++ b/rbpf-cli/src/main.rs @@ -153,10 +153,10 @@ native machine code before execting it in the virtual machine.", let mut account_refcells = Vec::new(); let default_account = RefCell::new(AccountSharedData::default()); let key = solana_sdk::pubkey::new_rand(); - let mut mem = match matches.value_of("input").unwrap().parse::() { + let (mut mem, account_lengths) = match matches.value_of("input").unwrap().parse::() { Ok(allocate) => { accounts.push(KeyedAccount::new(&key, false, &default_account)); - vec![0u8; allocate] + (vec![0u8; allocate], vec![allocate]) } Err(_) => { let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap(); @@ -170,9 +170,9 @@ native machine code before execting it in the virtual machine.", } let lid = bpf_loader::id(); let pid = Pubkey::new(&[0u8; 32]); - let (mut bytes, _account_lenghts) = + let (mut bytes, account_lengths) = serialize_parameters(&lid, &pid, &accounts, &input.insndata).unwrap(); - Vec::from(bytes.as_slice_mut()) + (Vec::from(bytes.as_slice_mut()), account_lengths) } }; let mut invoke_context = MockInvokeContext::new(accounts); @@ -228,7 +228,14 @@ native machine code before execting it in the virtual machine.", } let id = bpf_loader::id(); - let mut vm = create_vm(&id, executable.as_ref(), &mut mem, &mut invoke_context).unwrap(); + let mut vm = create_vm( + &id, + executable.as_ref(), + &mut mem, + &mut invoke_context, + &account_lengths, + ) + .unwrap(); let start_time = Instant::now(); let result = if matches.value_of("use").unwrap() == "interpreter" { vm.execute_program_interpreted(&mut instruction_meter) diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 78cf1ad555..3e7c2d6666 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -10,7 +10,8 @@ use solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, compute_budget::ComputeBudget, feature_set::{ - demote_program_write_locks, neon_evm_compute_budget, tx_wide_compute_cap, FeatureSet, + demote_program_write_locks, do_support_realloc, neon_evm_compute_budget, + tx_wide_compute_cap, FeatureSet, }, fee_calculator::FeeCalculator, hash::Hash, @@ -206,6 +207,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { ) -> Result<(), InstructionError> { let program_id = instruction.program_id(&message.account_keys); let demote_program_write_locks = self.is_feature_active(&demote_program_write_locks::id()); + let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id()); // Verify all executable accounts have zero outstanding refs for account_index in program_indices.iter() { @@ -234,6 +236,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { &account, &mut self.timings, true, + do_support_realloc, ) .map_err(|err| { ic_logger_msg!( @@ -262,6 +265,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { account_indices: &[usize], write_privileges: &[bool], ) -> Result<(), InstructionError> { + let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id()); let stack_frame = self .invoke_stack .last() @@ -293,7 +297,15 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { } let account = account.borrow(); pre_account - .verify(program_id, is_writable, rent, &account, timings, false) + .verify( + program_id, + is_writable, + rent, + &account, + timings, + false, + do_support_realloc, + ) .map_err(|err| { ic_logger_msg!(logger, "failed to verify account {}: {}", key, err); err diff --git a/sdk/program/src/account_info.rs b/sdk/program/src/account_info.rs index 084e08c807..d295dbd9a0 100644 --- a/sdk/program/src/account_info.rs +++ b/sdk/program/src/account_info.rs @@ -1,4 +1,6 @@ -use crate::{clock::Epoch, program_error::ProgramError, pubkey::Pubkey}; +use crate::{ + clock::Epoch, program_error::ProgramError, program_memory::sol_memset, pubkey::Pubkey, +}; use std::{ cell::{Ref, RefCell, RefMut}, cmp, fmt, @@ -114,6 +116,53 @@ impl<'a> AccountInfo<'a> { .map_err(|_| ProgramError::AccountBorrowFailed) } + /// Realloc the account's data and optionally zero-initialize the new + /// memory. + /// + /// Note: Account data can be increased within a single call by up to + /// `solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE` bytes. + /// + /// Note: Memory used to grow is already zero-initialized upon program + /// entrypoint and re-zeroing it wastes compute units. If within the same + /// call a program reallocs from larger to smaller and back to larger again + /// the new space could contain stale data. Pass `true` for `zero_init` in + /// this case, otherwise compute units will be wasted re-zero-initializing. + pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> { + let orig_len = self.data_len(); + + // realloc + unsafe { + // First set new length in the serialized data + let ptr = self.try_borrow_mut_data()?.as_mut_ptr().offset(-8) as *mut u64; + *ptr = new_len as u64; + + // Then set the new length in the local slice + let ptr = &mut *(((self.data.as_ptr() as *const u64).offset(1) as u64) as *mut u64); + *ptr = new_len as u64; + } + + // zero-init if requested + if zero_init && new_len > orig_len { + sol_memset( + &mut self.try_borrow_mut_data()?[orig_len..], + 0, + new_len.saturating_sub(orig_len), + ); + } + + Ok(()) + } + + pub fn assign(&self, new_owner: &Pubkey) { + // Set the non-mut owner field + unsafe { + std::ptr::write_volatile( + self.owner as *const Pubkey as *mut [u8; 32], + new_owner.to_bytes(), + ); + } + } + pub fn new( key: &'a Pubkey, is_signer: bool, diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 2db83fa85a..d9c1396b09 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -66,8 +66,8 @@ pub enum InstructionError { #[error("sum of account balances before and after instruction do not match")] UnbalancedInstruction, - /// Program modified an account's program id - #[error("instruction modified the program id of an account")] + /// Program illegally modified an account's program id + #[error("instruction illegally modified the program id of an account")] ModifiedProgramId, /// Program spent the lamports of an account that doesn't belong to it @@ -103,8 +103,8 @@ pub enum InstructionError { #[error("insufficient account keys for instruction")] NotEnoughAccountKeys, - /// A non-system program changed the size of the account data - #[error("non-system instruction changed account size")] + /// Program other than the account's owner changed the size of the account data + #[error("program other than the account's owner changed the size of the account data")] AccountDataSizeChanged, /// The instruction expected an executable account diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 351e87218e..44db6fc860 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -219,6 +219,10 @@ pub mod stakes_remove_delegation_if_inactive { solana_sdk::declare_id!("HFpdDDNQjvcXnXKec697HDDsyk6tFoWS2o8fkxuhQZpL"); } +pub mod do_support_realloc { + solana_sdk::declare_id!("75m6ysz33AfLA5DDEzWM1obBrnPQRSsdVQ2nRmc8Vuu1"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -269,6 +273,7 @@ lazy_static! { (reduce_required_deploy_balance::id(), "reduce required payer balance for program deploys"), (sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"), (stakes_remove_delegation_if_inactive::id(), "remove delegations from stakes cache when inactive"), + (do_support_realloc::id(), "support account data reallocation"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()