Fix native invoke writable privileges (#19750)
* Fix native invoke writable privileges * build downstream spl bpf programs for tests
This commit is contained in:
parent
910f241c3f
commit
00d7981f64
|
@ -4,7 +4,7 @@ use solana_sdk::{
|
||||||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||||
feature_set::demote_program_write_locks,
|
feature_set::{demote_program_write_locks, fix_write_privs},
|
||||||
ic_logger_msg, ic_msg,
|
ic_logger_msg, ic_msg,
|
||||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||||
keyed_account::{keyed_account_at_index, KeyedAccount},
|
keyed_account::{keyed_account_at_index, KeyedAccount},
|
||||||
|
@ -474,28 +474,30 @@ impl InstructionProcessor {
|
||||||
) = {
|
) = {
|
||||||
let invoke_context = invoke_context.borrow();
|
let invoke_context = invoke_context.borrow();
|
||||||
|
|
||||||
// Translate and verify caller's data
|
let caller_keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
let callee_keyed_accounts = keyed_account_indices
|
||||||
let keyed_accounts = keyed_account_indices
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|index| keyed_account_at_index(keyed_accounts, *index))
|
.map(|index| keyed_account_at_index(caller_keyed_accounts, *index))
|
||||||
.collect::<Result<Vec<&KeyedAccount>, InstructionError>>()?;
|
.collect::<Result<Vec<&KeyedAccount>, InstructionError>>()?;
|
||||||
let (message, callee_program_id, _) =
|
let (message, callee_program_id, _) = Self::create_message(
|
||||||
Self::create_message(&instruction, &keyed_accounts, signers, &invoke_context)?;
|
&instruction,
|
||||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
&callee_keyed_accounts,
|
||||||
let mut caller_write_privileges = keyed_account_indices
|
signers,
|
||||||
.iter()
|
&invoke_context,
|
||||||
.map(|index| keyed_accounts[*index].is_writable())
|
)?;
|
||||||
.collect::<Vec<bool>>();
|
let mut keyed_account_indices_reordered =
|
||||||
caller_write_privileges.insert(0, false);
|
Vec::with_capacity(message.account_keys.len());
|
||||||
let mut accounts = vec![];
|
let mut accounts = Vec::with_capacity(message.account_keys.len());
|
||||||
let mut keyed_account_indices_reordered = vec![];
|
let mut caller_write_privileges = Vec::with_capacity(message.account_keys.len());
|
||||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
|
||||||
|
// Translate and verify caller's data
|
||||||
|
if invoke_context.is_feature_active(&fix_write_privs::id()) {
|
||||||
'root: for account_key in message.account_keys.iter() {
|
'root: for account_key in message.account_keys.iter() {
|
||||||
for keyed_account_index in keyed_account_indices {
|
for keyed_account_index in keyed_account_indices {
|
||||||
let keyed_account = &keyed_accounts[*keyed_account_index];
|
let keyed_account = &caller_keyed_accounts[*keyed_account_index];
|
||||||
if account_key == keyed_account.unsigned_key() {
|
if account_key == keyed_account.unsigned_key() {
|
||||||
accounts.push((*account_key, Rc::new(keyed_account.account.clone())));
|
accounts.push((*account_key, Rc::new(keyed_account.account.clone())));
|
||||||
|
caller_write_privileges.push(keyed_account.is_writable());
|
||||||
keyed_account_indices_reordered.push(*keyed_account_index);
|
keyed_account_indices_reordered.push(*keyed_account_index);
|
||||||
continue 'root;
|
continue 'root;
|
||||||
}
|
}
|
||||||
|
@ -507,6 +509,30 @@ impl InstructionProcessor {
|
||||||
);
|
);
|
||||||
return Err(InstructionError::MissingAccount);
|
return Err(InstructionError::MissingAccount);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||||
|
for index in keyed_account_indices.iter() {
|
||||||
|
caller_write_privileges.push(keyed_accounts[*index].is_writable());
|
||||||
|
}
|
||||||
|
caller_write_privileges.insert(0, false);
|
||||||
|
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||||
|
'root2: for account_key in message.account_keys.iter() {
|
||||||
|
for keyed_account_index in keyed_account_indices {
|
||||||
|
let keyed_account = &keyed_accounts[*keyed_account_index];
|
||||||
|
if account_key == keyed_account.unsigned_key() {
|
||||||
|
accounts.push((*account_key, Rc::new(keyed_account.account.clone())));
|
||||||
|
keyed_account_indices_reordered.push(*keyed_account_index);
|
||||||
|
continue 'root2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ic_msg!(
|
||||||
|
invoke_context,
|
||||||
|
"Instruction references an unknown account {}",
|
||||||
|
account_key
|
||||||
|
);
|
||||||
|
return Err(InstructionError::MissingAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process instruction
|
// Process instruction
|
||||||
|
|
||||||
|
|
|
@ -1162,6 +1162,7 @@ mod tests {
|
||||||
NoopFail,
|
NoopFail,
|
||||||
ModifyOwned,
|
ModifyOwned,
|
||||||
ModifyNotOwned,
|
ModifyNotOwned,
|
||||||
|
ModifyReadonly,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mock_process_instruction(
|
fn mock_process_instruction(
|
||||||
|
@ -1186,6 +1187,9 @@ mod tests {
|
||||||
MockInstruction::ModifyNotOwned => {
|
MockInstruction::ModifyNotOwned => {
|
||||||
keyed_accounts[1].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
keyed_accounts[1].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
||||||
}
|
}
|
||||||
|
MockInstruction::ModifyReadonly => {
|
||||||
|
keyed_accounts[2].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(InstructionError::InvalidInstructionData);
|
return Err(InstructionError::InvalidInstructionData);
|
||||||
|
@ -1196,11 +1200,11 @@ mod tests {
|
||||||
let caller_program_id = solana_sdk::pubkey::new_rand();
|
let caller_program_id = solana_sdk::pubkey::new_rand();
|
||||||
let callee_program_id = solana_sdk::pubkey::new_rand();
|
let callee_program_id = solana_sdk::pubkey::new_rand();
|
||||||
|
|
||||||
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
|
|
||||||
program_account.set_executable(true);
|
|
||||||
|
|
||||||
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
|
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
|
||||||
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
|
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
|
||||||
|
let readonly_account = AccountSharedData::new(168, 1, &caller_program_id);
|
||||||
|
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
|
||||||
|
program_account.set_executable(true);
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
|
@ -1213,26 +1217,29 @@ mod tests {
|
||||||
Rc::new(RefCell::new(not_owned_account)),
|
Rc::new(RefCell::new(not_owned_account)),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
callee_program_id,
|
solana_sdk::pubkey::new_rand(),
|
||||||
Rc::new(RefCell::new(program_account.clone())),
|
Rc::new(RefCell::new(readonly_account)),
|
||||||
),
|
),
|
||||||
|
(callee_program_id, Rc::new(RefCell::new(program_account))),
|
||||||
];
|
];
|
||||||
let program_indices = vec![2];
|
let program_indices = vec![3];
|
||||||
|
|
||||||
let compiled_instruction = CompiledInstruction::new(2, &(), vec![0, 1, 2]);
|
|
||||||
let programs: Vec<(_, ProcessInstructionWithContext)> =
|
let programs: Vec<(_, ProcessInstructionWithContext)> =
|
||||||
vec![(callee_program_id, mock_process_instruction)];
|
vec![(callee_program_id, mock_process_instruction)];
|
||||||
let metas = vec![
|
let metas = vec![
|
||||||
AccountMeta::new(accounts[0].0, false),
|
AccountMeta::new(accounts[0].0, false),
|
||||||
AccountMeta::new(accounts[1].0, false),
|
AccountMeta::new(accounts[1].0, false),
|
||||||
|
AccountMeta::new_readonly(accounts[2].0, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
let instruction = Instruction::new_with_bincode(
|
let caller_instruction = CompiledInstruction::new(2, &(), vec![0, 1, 2, 3]);
|
||||||
|
let callee_instruction = Instruction::new_with_bincode(
|
||||||
callee_program_id,
|
callee_program_id,
|
||||||
&MockInstruction::NoopSuccess,
|
&MockInstruction::NoopSuccess,
|
||||||
metas.clone(),
|
metas.clone(),
|
||||||
);
|
);
|
||||||
let message = Message::new(&[instruction], None);
|
let message = Message::new(&[callee_instruction], None);
|
||||||
|
|
||||||
let feature_set = FeatureSet::all_enabled();
|
let feature_set = FeatureSet::all_enabled();
|
||||||
let demote_program_write_locks = feature_set.is_active(&demote_program_write_locks::id());
|
let demote_program_write_locks = feature_set.is_active(&demote_program_write_locks::id());
|
||||||
|
|
||||||
|
@ -1243,7 +1250,7 @@ mod tests {
|
||||||
&caller_program_id,
|
&caller_program_id,
|
||||||
Rent::default(),
|
Rent::default(),
|
||||||
&message,
|
&message,
|
||||||
&compiled_instruction,
|
&caller_instruction,
|
||||||
&program_indices,
|
&program_indices,
|
||||||
&accounts,
|
&accounts,
|
||||||
programs.as_slice(),
|
programs.as_slice(),
|
||||||
|
@ -1280,6 +1287,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||||
|
|
||||||
|
// readonly account modified by the invoker
|
||||||
|
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||||
|
assert_eq!(
|
||||||
|
InstructionProcessor::process_cross_program_instruction(
|
||||||
|
&message,
|
||||||
|
&program_indices,
|
||||||
|
&accounts,
|
||||||
|
&caller_write_privileges,
|
||||||
|
&mut invoke_context,
|
||||||
|
),
|
||||||
|
Err(InstructionError::ReadonlyDataModified)
|
||||||
|
);
|
||||||
|
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||||
|
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
(MockInstruction::NoopSuccess, Ok(())),
|
(MockInstruction::NoopSuccess, Ok(())),
|
||||||
(
|
(
|
||||||
|
@ -1294,9 +1315,9 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for case in cases {
|
for case in cases {
|
||||||
let instruction =
|
let callee_instruction =
|
||||||
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
||||||
let message = Message::new(&[instruction], None);
|
let message = Message::new(&[callee_instruction], None);
|
||||||
|
|
||||||
let ancestors = Ancestors::default();
|
let ancestors = Ancestors::default();
|
||||||
let blockhash = Hash::default();
|
let blockhash = Hash::default();
|
||||||
|
@ -1305,7 +1326,7 @@ mod tests {
|
||||||
&caller_program_id,
|
&caller_program_id,
|
||||||
Rent::default(),
|
Rent::default(),
|
||||||
&message,
|
&message,
|
||||||
&compiled_instruction,
|
&caller_instruction,
|
||||||
&program_indices,
|
&program_indices,
|
||||||
&accounts,
|
&accounts,
|
||||||
programs.as_slice(),
|
programs.as_slice(),
|
||||||
|
@ -1340,4 +1361,198 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_native_invoke() {
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum MockInstruction {
|
||||||
|
NoopSuccess,
|
||||||
|
NoopFail,
|
||||||
|
ModifyOwned,
|
||||||
|
ModifyNotOwned,
|
||||||
|
ModifyReadonly,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
data: &[u8],
|
||||||
|
invoke_context: &mut dyn InvokeContext,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||||
|
assert_eq!(*program_id, keyed_accounts[0].owner()?);
|
||||||
|
assert_ne!(
|
||||||
|
keyed_accounts[1].owner()?,
|
||||||
|
*keyed_accounts[0].unsigned_key()
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(instruction) = bincode::deserialize(data) {
|
||||||
|
match instruction {
|
||||||
|
MockInstruction::NoopSuccess => (),
|
||||||
|
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
|
||||||
|
MockInstruction::ModifyOwned => {
|
||||||
|
keyed_accounts[0].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
||||||
|
}
|
||||||
|
MockInstruction::ModifyNotOwned => {
|
||||||
|
keyed_accounts[1].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
||||||
|
}
|
||||||
|
MockInstruction::ModifyReadonly => {
|
||||||
|
keyed_accounts[2].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(InstructionError::InvalidInstructionData);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let caller_program_id = solana_sdk::pubkey::new_rand();
|
||||||
|
let callee_program_id = solana_sdk::pubkey::new_rand();
|
||||||
|
|
||||||
|
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
|
||||||
|
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
|
||||||
|
let readonly_account = AccountSharedData::new(168, 1, &caller_program_id);
|
||||||
|
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
|
||||||
|
program_account.set_executable(true);
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let accounts = vec![
|
||||||
|
(
|
||||||
|
solana_sdk::pubkey::new_rand(),
|
||||||
|
Rc::new(RefCell::new(owned_account)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
solana_sdk::pubkey::new_rand(),
|
||||||
|
Rc::new(RefCell::new(not_owned_account)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
solana_sdk::pubkey::new_rand(),
|
||||||
|
Rc::new(RefCell::new(readonly_account)),
|
||||||
|
),
|
||||||
|
(callee_program_id, Rc::new(RefCell::new(program_account))),
|
||||||
|
];
|
||||||
|
let program_indices = vec![3];
|
||||||
|
let programs: Vec<(_, ProcessInstructionWithContext)> =
|
||||||
|
vec![(callee_program_id, mock_process_instruction)];
|
||||||
|
let metas = vec![
|
||||||
|
AccountMeta::new(accounts[0].0, false),
|
||||||
|
AccountMeta::new(accounts[1].0, false),
|
||||||
|
AccountMeta::new_readonly(accounts[2].0, false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let caller_instruction = CompiledInstruction::new(2, &(), vec![0, 1, 2, 3]);
|
||||||
|
let callee_instruction = Instruction::new_with_bincode(
|
||||||
|
callee_program_id,
|
||||||
|
&MockInstruction::NoopSuccess,
|
||||||
|
metas.clone(),
|
||||||
|
);
|
||||||
|
let message = Message::new(&[callee_instruction.clone()], None);
|
||||||
|
|
||||||
|
let feature_set = FeatureSet::all_enabled();
|
||||||
|
let ancestors = Ancestors::default();
|
||||||
|
let blockhash = Hash::default();
|
||||||
|
let fee_calculator = FeeCalculator::default();
|
||||||
|
let mut invoke_context = ThisInvokeContext::new(
|
||||||
|
&caller_program_id,
|
||||||
|
Rent::default(),
|
||||||
|
&message,
|
||||||
|
&caller_instruction,
|
||||||
|
&program_indices,
|
||||||
|
&accounts,
|
||||||
|
programs.as_slice(),
|
||||||
|
None,
|
||||||
|
ComputeBudget::default(),
|
||||||
|
Rc::new(RefCell::new(MockComputeMeter::default())),
|
||||||
|
Rc::new(RefCell::new(Executors::default())),
|
||||||
|
None,
|
||||||
|
Arc::new(feature_set),
|
||||||
|
Arc::new(Accounts::default_for_tests()),
|
||||||
|
&ancestors,
|
||||||
|
&blockhash,
|
||||||
|
&fee_calculator,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// not owned account modified by the invoker
|
||||||
|
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||||
|
assert_eq!(
|
||||||
|
InstructionProcessor::native_invoke(
|
||||||
|
&mut invoke_context,
|
||||||
|
callee_instruction.clone(),
|
||||||
|
&[0, 1, 2, 3],
|
||||||
|
&[]
|
||||||
|
),
|
||||||
|
Err(InstructionError::ExternalAccountDataModified)
|
||||||
|
);
|
||||||
|
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||||
|
|
||||||
|
// readonly account modified by the invoker
|
||||||
|
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||||
|
assert_eq!(
|
||||||
|
InstructionProcessor::native_invoke(
|
||||||
|
&mut invoke_context,
|
||||||
|
callee_instruction,
|
||||||
|
&[0, 1, 2, 3],
|
||||||
|
&[]
|
||||||
|
),
|
||||||
|
Err(InstructionError::ReadonlyDataModified)
|
||||||
|
);
|
||||||
|
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||||
|
|
||||||
|
// Other test cases
|
||||||
|
let cases = vec![
|
||||||
|
(MockInstruction::NoopSuccess, Ok(())),
|
||||||
|
(
|
||||||
|
MockInstruction::NoopFail,
|
||||||
|
Err(InstructionError::GenericError),
|
||||||
|
),
|
||||||
|
(MockInstruction::ModifyOwned, Ok(())),
|
||||||
|
(
|
||||||
|
MockInstruction::ModifyNotOwned,
|
||||||
|
Err(InstructionError::ExternalAccountDataModified),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
MockInstruction::ModifyReadonly,
|
||||||
|
Err(InstructionError::ReadonlyDataModified),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for case in cases {
|
||||||
|
let callee_instruction =
|
||||||
|
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
||||||
|
let message = Message::new(&[callee_instruction.clone()], None);
|
||||||
|
|
||||||
|
let ancestors = Ancestors::default();
|
||||||
|
let blockhash = Hash::default();
|
||||||
|
let fee_calculator = FeeCalculator::default();
|
||||||
|
let mut invoke_context = ThisInvokeContext::new(
|
||||||
|
&caller_program_id,
|
||||||
|
Rent::default(),
|
||||||
|
&message,
|
||||||
|
&caller_instruction,
|
||||||
|
&program_indices,
|
||||||
|
&accounts,
|
||||||
|
programs.as_slice(),
|
||||||
|
None,
|
||||||
|
ComputeBudget::default(),
|
||||||
|
Rc::new(RefCell::new(MockComputeMeter::default())),
|
||||||
|
Rc::new(RefCell::new(Executors::default())),
|
||||||
|
None,
|
||||||
|
Arc::new(FeatureSet::all_enabled()),
|
||||||
|
Arc::new(Accounts::default_for_tests()),
|
||||||
|
&ancestors,
|
||||||
|
&blockhash,
|
||||||
|
&fee_calculator,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
InstructionProcessor::native_invoke(
|
||||||
|
&mut invoke_context,
|
||||||
|
callee_instruction,
|
||||||
|
&[0, 1, 2, 3],
|
||||||
|
&[]
|
||||||
|
),
|
||||||
|
case.1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ spl() {
|
||||||
|
|
||||||
$cargo build
|
$cargo build
|
||||||
$cargo test
|
$cargo test
|
||||||
|
$cargo_build_bpf
|
||||||
$cargo_test_bpf
|
$cargo_test_bpf
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,10 @@ pub mod return_data_syscall_enabled {
|
||||||
solana_sdk::declare_id!("BJVXq6NdLC7jCDGjfqJv7M1XHD4Y13VrpDqRF2U7UBcC");
|
solana_sdk::declare_id!("BJVXq6NdLC7jCDGjfqJv7M1XHD4Y13VrpDqRF2U7UBcC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod fix_write_privs {
|
||||||
|
solana_sdk::declare_id!("7Tr5C1tdcCeBVD8jxtHYnvjL1DGdFboYBHCJkEFdenBb");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -258,7 +262,8 @@ lazy_static! {
|
||||||
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
|
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
|
||||||
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
|
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
|
||||||
(check_seed_length::id(), "Check program address seed lengths"),
|
(check_seed_length::id(), "Check program address seed lengths"),
|
||||||
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall")
|
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall"),
|
||||||
|
(fix_write_privs::id(), "fix native invoke write privileges"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Reference in New Issue