Allow programs to realloc their accounts within limits (#19475)

This commit is contained in:
Jack May
2021-09-28 01:13:03 -07:00
committed by GitHub
parent 578efdd59f
commit 4e27543415
21 changed files with 1536 additions and 78 deletions

View File

@@ -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"

View File

@@ -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",

View File

@@ -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();

View File

@@ -84,6 +84,8 @@ fn main() {
"panic",
"param_passing",
"rand",
"realloc",
"realloc_invoke",
"ro_modify",
"ro_account_modify",
"sanity",

View File

@@ -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 <maintainers@solana.foundation>"]
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"]

View File

@@ -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)],
)
}

View File

@@ -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::<usize>());
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::<usize>());
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::<usize>());
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(())
}

View File

@@ -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 <maintainers@solana.foundation>"]
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"]

View File

@@ -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;

View File

@@ -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::<usize>());
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::<usize>());
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::<usize>());
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(())
}

View File

@@ -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)
);
}