Allow programs to realloc their accounts within limits (#19475)
This commit is contained in:
parent
578efdd59f
commit
4e27543415
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -84,6 +84,8 @@ fn main() {
|
|||
"panic",
|
||||
"param_passing",
|
||||
"rand",
|
||||
"realloc",
|
||||
"realloc_invoke",
|
||||
"ro_modify",
|
||||
"ro_account_modify",
|
||||
"sanity",
|
||||
|
|
|
@ -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"]
|
|
@ -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)],
|
||||
)
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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"]
|
|
@ -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;
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<BpfError, ThisInstructionMeter>,
|
||||
parameter_bytes: &mut [u8],
|
||||
invoke_context: &'a mut dyn InvokeContext,
|
||||
orig_data_lens: &'a [usize],
|
||||
) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> {
|
||||
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(
|
||||
|
|
|
@ -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::<u64>(); // number of accounts
|
||||
for (i, (keyed_account, pre_len)) in keyed_accounts
|
||||
|
@ -285,13 +288,22 @@ pub fn deserialize_parameters_aligned(
|
|||
start += size_of::<u64>(); // lamports
|
||||
let post_len = LittleEndian::read_u64(&buffer[start..]) as usize;
|
||||
start += size_of::<u64>(); // 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::<u128>());
|
||||
|
@ -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
|
||||
|
|
|
@ -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<BpfError>> {
|
||||
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<BpfError> 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<usize>,
|
||||
Vec<(
|
||||
Rc<RefCell<AccountSharedData>>,
|
||||
Option<AccountReferences<'a>>,
|
||||
)>,
|
||||
Vec<(Rc<RefCell<AccountSharedData>>, Option<CallerAccount<'a>>)>,
|
||||
);
|
||||
|
||||
/// 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<RefCell<&'a mut dyn InvokeContext>>,
|
||||
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<RefCell<&'a mut dyn InvokeContext>>,
|
||||
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<TranslatedAccounts<'a>, EbpfError<BpfError>>
|
||||
where
|
||||
F: Fn(&T, &mut dyn InvokeContext) -> Result<AccountReferences<'a>, EbpfError<BpfError>>,
|
||||
F: Fn(&T, &mut dyn InvokeContext) -> Result<CallerAccount<'a>, EbpfError<BpfError>>,
|
||||
{
|
||||
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::<u8>(
|
||||
if new_len < caller_account.data.len() {
|
||||
caller_account.data[new_len..].fill(0);
|
||||
}
|
||||
caller_account.data = translate_slice_mut::<u8>(
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::<usize>() {
|
||||
let (mut mem, account_lengths) = match matches.value_of("input").unwrap().parse::<usize>() {
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Pubkey, &'static str> = [
|
||||
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue