Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPI: improve test coverage #31986

Merged
merged 10 commits into from
Sep 5, 2023
Merged
1 change: 1 addition & 0 deletions programs/sbf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions programs/sbf/c/src/invoke/invoke.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ static const uint8_t TEST_RETURN_DATA_TOO_LARGE = 18;
static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER = 19;
static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE = 20;
static const uint8_t TEST_MAX_ACCOUNT_INFOS_EXCEEDED = 21;
// TEST_CPI_INVALID_* must match the definitions in
// https://github.com/solana-labs/solana/blob/master/programs/sbf/rust/invoke/src/instructions.rs
static const uint8_t TEST_CPI_INVALID_KEY_POINTER = 34;
static const uint8_t TEST_CPI_INVALID_OWNER_POINTER = 35;
static const uint8_t TEST_CPI_INVALID_LAMPORTS_POINTER = 36;
static const uint8_t TEST_CPI_INVALID_DATA_POINTER = 37;

static const int MINT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
Expand Down Expand Up @@ -663,6 +669,73 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
break;
}
case TEST_CPI_INVALID_KEY_POINTER:
{
sol_log("Test TEST_CPI_INVALID_KEY_POINTER");
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, false, false},
{accounts[INVOKED_ARGUMENT_INDEX].key, false, false},
};
uint8_t data[] = {};
SolPubkey key = *accounts[ARGUMENT_INDEX].key;
accounts[ARGUMENT_INDEX].key = &key;

const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, 4);
break;
}
case TEST_CPI_INVALID_LAMPORTS_POINTER:
{
sol_log("Test TEST_CPI_INVALID_LAMPORTS_POINTER");
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, false, false},
{accounts[INVOKED_ARGUMENT_INDEX].key, false, false},
};
uint8_t data[] = {};
uint64_t lamports = *accounts[ARGUMENT_INDEX].lamports;
accounts[ARGUMENT_INDEX].lamports = &lamports;

const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, 4);
break;
}
case TEST_CPI_INVALID_OWNER_POINTER:
{
sol_log("Test TEST_CPI_INVALID_OWNER_POINTER");
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, false, false},
{accounts[INVOKED_ARGUMENT_INDEX].key, false, false},
};
uint8_t data[] = {};
SolPubkey owner = *accounts[ARGUMENT_INDEX].owner;
accounts[ARGUMENT_INDEX].owner = &owner;

const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, 4);
break;
}
case TEST_CPI_INVALID_DATA_POINTER:
{
sol_log("Test TEST_CPI_INVALID_DATA_POINTER");
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, false, false},
{accounts[INVOKED_ARGUMENT_INDEX].key, false, false},
};
uint8_t data[] = {};
accounts[ARGUMENT_INDEX].data = data;

const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, 4);
break;
}

default:
sol_panic();
Expand Down
152 changes: 124 additions & 28 deletions programs/sbf/rust/deprecated_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@

extern crate solana_program;
use solana_program::{
account_info::AccountInfo, bpf_loader, entrypoint_deprecated::ProgramResult, log::*, msg,
account_info::AccountInfo,
bpf_loader,
entrypoint_deprecated::ProgramResult,
instruction::{AccountMeta, Instruction},
log::*,
msg,
program::invoke,
pubkey::Pubkey,
};

pub const REALLOC: u8 = 1;
pub const REALLOC_EXTEND_FROM_SLICE: u8 = 12;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS: u8 = 28;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_NESTED: u8 = 29;

#[derive(Debug, PartialEq)]
struct SStruct {
x: u64,
Expand Down Expand Up @@ -39,37 +50,122 @@ fn process_instruction(

assert!(!bpf_loader::check_id(program_id));

// Log the provided account keys and instruction input data. In the case of
// the no-op program, no account keys or input data are expected but real
// programs will have specific requirements so they can do their work.
msg!("Account keys and instruction input data:");
sol_log_params(accounts, instruction_data);

{
// Test - use std methods, unwrap

// valid bytes, in a stack-allocated array
let sparkle_heart = [240, 159, 146, 150];
let result_str = std::str::from_utf8(&sparkle_heart).unwrap();
assert_eq!(4, result_str.len());
assert_eq!("💖", result_str);
msg!(result_str);
// test_sol_alloc_free_no_longer_deployable calls this program with
// bpf_loader instead of bpf_loader_deprecated, so instruction_data isn't
// deserialized correctly and is empty.
match instruction_data.first() {
Some(&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);
let account = &accounts[0];
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
}
Some(&REALLOC_EXTEND_FROM_SLICE) => {
msg!("realloc extend from slice deprecated");
let data = &instruction_data[1..];
let account = &accounts[0];
let prev_len = account.data_len();
account.realloc(prev_len + data.len(), false)?;
account.data.borrow_mut()[prev_len..].copy_from_slice(data);
}
Some(&TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS) => {
msg!("DEPRECATED TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS");
const ARGUMENT_INDEX: usize = 1;
const CALLEE_PROGRAM_INDEX: usize = 3;
let account = &accounts[ARGUMENT_INDEX];
let callee_program_id = accounts[CALLEE_PROGRAM_INDEX].key;

let expected = {
let data = &instruction_data[1..];
let prev_len = account.data_len();
// when direct mapping is off, this will accidentally clobber
// whatever comes after the data slice (owner, executable, rent
// epoch etc). When direct mapping is on, you get an
// InvalidRealloc error.
account.realloc(prev_len + data.len(), false)?;
account.data.borrow_mut()[prev_len..].copy_from_slice(data);
account.data.borrow().to_vec()
};

let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_NESTED];
instruction_data.extend_from_slice(&expected);
invoke(
&create_instruction(
*callee_program_id,
&[
(accounts[ARGUMENT_INDEX].key, true, false),
(callee_program_id, false, false),
],
instruction_data,
),
accounts,
)
.unwrap();
}
Some(&TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_NESTED) => {
msg!("DEPRECATED LOADER TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_NESTED");
const ARGUMENT_INDEX: usize = 0;
let account = &accounts[ARGUMENT_INDEX];
assert_eq!(*account.data.borrow(), &instruction_data[1..]);
}
_ => {
{
// Log the provided account keys and instruction input data. In the case of
// the no-op program, no account keys or input data are expected but real
// programs will have specific requirements so they can do their work.
msg!("Account keys and instruction input data:");
sol_log_params(accounts, instruction_data);

// Test - use std methods, unwrap

// valid bytes, in a stack-allocated array
let sparkle_heart = [240, 159, 146, 150];
let result_str = std::str::from_utf8(&sparkle_heart).unwrap();
assert_eq!(4, result_str.len());
assert_eq!("💖", result_str);
msg!(result_str);
}

{
// Test - struct return

let s = return_sstruct();
assert_eq!(s.x + s.y + s.z, 6);
}

{
// Test - arch config
#[cfg(not(target_os = "solana"))]
panic!();
}
}
}

{
// Test - struct return

let s = return_sstruct();
assert_eq!(s.x + s.y + s.z, 6);
}
Ok(())
}

{
// Test - arch config
#[cfg(not(target_os = "solana"))]
panic!();
pub fn create_instruction(
program_id: Pubkey,
arguments: &[(&Pubkey, bool, bool)],
data: Vec<u8>,
) -> Instruction {
let accounts = arguments
.iter()
.map(|(key, is_writable, is_signer)| {
if *is_writable {
AccountMeta::new(**key, *is_signer)
} else {
AccountMeta::new_readonly(**key, *is_signer)
}
})
.collect();
Instruction {
program_id,
accounts,
data,
}

Ok(())
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions programs/sbf/rust/invoke/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ program = []
[dependencies]
solana-program = { workspace = true }
solana-sbf-rust-invoked = { workspace = true }
solana-sbf-rust-realloc = { workspace = true }

[lib]
crate-type = ["lib", "cdylib"]
Expand Down
18 changes: 18 additions & 0 deletions programs/sbf/rust/invoke/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ pub const TEST_RETURN_DATA_TOO_LARGE: u8 = 18;
pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER: u8 = 19;
pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE: u8 = 20;
pub const TEST_MAX_ACCOUNT_INFOS_EXCEEDED: u8 = 21;
pub const TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLEE: u8 = 22;
pub const TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLEE_NESTED: u8 = 23;
pub const TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLER: u8 = 24;
pub const TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE_MOVING_DATA_POINTER: u8 = 25;
pub const TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE: u8 = 26;
pub const TEST_ALLOW_WRITE_AFTER_OWNERSHIP_CHANGE_TO_CALLER: u8 = 27;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS: u8 = 28;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_NESTED: u8 = 29;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLEE_GROWS: u8 = 30;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN: u8 = 31;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS: u8 = 32;
pub const TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED: u8 = 33;
pub const TEST_CPI_INVALID_KEY_POINTER: u8 = 34;
pub const TEST_CPI_INVALID_OWNER_POINTER: u8 = 35;
pub const TEST_CPI_INVALID_LAMPORTS_POINTER: u8 = 36;
pub const TEST_CPI_INVALID_DATA_POINTER: u8 = 37;
pub const TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION: u8 = 38;
pub const TEST_WRITE_ACCOUNT: u8 = 39;

pub const MINT_INDEX: usize = 0;
pub const ARGUMENT_INDEX: usize = 1;
Expand Down
Loading