Add Cross-program invocations (#9582)

This commit is contained in:
Jack May
2020-04-28 14:33:56 -07:00
committed by GitHub
parent 063f616a19
commit 068f12fd6f
27 changed files with 2164 additions and 208 deletions

View File

@ -1738,11 +1738,12 @@ version = "1.2.0"
dependencies = [
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
"jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-logger 1.2.0",
"solana-runtime 1.2.0",
"solana-sdk 1.2.0",
"solana_rbpf 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1824,6 +1825,23 @@ dependencies = [
"solana-sdk-bpf-test 1.2.0",
]
[[package]]
name = "solana-bpf-rust-invoke"
version = "1.0.0"
dependencies = [
"solana-bpf-rust-invoked 1.0.0",
"solana-sdk 1.2.0",
"solana-sdk-bpf-test 1.2.0",
]
[[package]]
name = "solana-bpf-rust-invoked"
version = "1.0.0"
dependencies = [
"solana-sdk 1.2.0",
"solana-sdk-bpf-test 1.2.0",
]
[[package]]
name = "solana-bpf-rust-iter"
version = "1.2.0"

View File

@ -40,6 +40,8 @@ members = [
"rust/dup_accounts",
"rust/error_handling",
"rust/external_spend",
"rust/invoke",
"rust/invoked",
"rust/iter",
"rust/many_args",
"rust/many_args_dep",

View File

@ -4,6 +4,14 @@ extern crate test;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use solana_rbpf::EbpfVm;
use solana_sdk::{
account::Account,
entrypoint_native::InvokeContext,
instruction::{CompiledInstruction, InstructionError},
message::Message,
pubkey::Pubkey,
};
use std::{cell::RefCell, rc::Rc};
use std::{env, fs::File, io::Read, mem, path::PathBuf};
use test::Bencher;
@ -69,9 +77,10 @@ fn bench_program_alu(bencher: &mut Bencher) {
.write_u64::<LittleEndian>(ARMSTRONG_LIMIT)
.unwrap();
inner_iter.write_u64::<LittleEndian>(0).unwrap();
let mut invoke_context = MockInvokeContext::default();
let elf = load_elf().unwrap();
let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf).unwrap();
let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf, &mut invoke_context).unwrap();
println!("Interpreted:");
assert_eq!(
@ -122,3 +131,21 @@ fn bench_program_alu(bencher: &mut Bencher) {
// println!(" {:?} MIPS", mips);
// println!("{{ \"type\": \"bench\", \"name\": \"bench_program_alu_jit_to_native_mips\", \"median\": {:?}, \"deviation\": 0 }}", mips);
}
#[derive(Debug, Default)]
pub struct MockInvokeContext {}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Ok(())
}
fn pop(&mut self) {}
fn verify_and_update(
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())
}
}

View File

@ -71,6 +71,8 @@ fn main() {
"dup_accounts",
"error_handling",
"external_spend",
"invoke",
"invoked",
"iter",
"many_args",
"noop",

View File

@ -0,0 +1,131 @@
/**
* @brief Example C-based BPF program that prints out the parameters
* passed to it
*/
#include <solana_sdk.h>
#define MINT_INDEX 0
#define ARGUMENT_INDEX 1
#define INVOKED_PROGRAM_INDEX 2
#define INVOKED_ARGUMENT_INDEX 3
#define INVOKED_PROGRAM_DUP_INDEX 4
#define ARGUMENT_DUP_INDEX 5
#define DERIVED_KEY_INDEX 6
#define DERIVED_KEY2_INDEX 7
extern uint64_t entrypoint(const uint8_t *input) {
sol_log("Invoke C program");
SolAccountInfo accounts[8];
SolParameters params = (SolParameters){.ka = accounts};
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(accounts))) {
return ERROR_INVALID_ARGUMENT;
}
sol_log("Test data translation");
{
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
accounts[ARGUMENT_INDEX].data[i] = i;
}
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}};
uint8_t data[] = {0, 1, 2, 3, 4, 5};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, 4, data, 6};
sol_assert(SUCCESS == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test return error");
{
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {1};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(42 == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test derived signers");
{
SolAccountMeta arguments[] = {
{accounts[DERIVED_KEY_INDEX].key, true, true},
{accounts[DERIVED_KEY2_INDEX].key, false, true}};
uint8_t data[] = {2};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
const SolSignerSeed seeds1[] = {{"Lil'", 4}, {"Bits", 4}};
const SolSignerSeed seeds2[] = {{"Gar Ma Nar Nar", 14}};
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
}
sol_log("Test readonly with writable account");
{
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, false}};
uint8_t data[] = {3};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test invoke");
{
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
*accounts[ARGUMENT_INDEX].lamports -= 5;
*accounts[INVOKED_ARGUMENT_INDEX].lamports += 5;
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[DERIVED_KEY_INDEX].key, true, true}};
uint8_t data[] = {4};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
const SolSignerSeed seeds[] = {{"Lil'", 4}, {"Bits", 4}};
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
sol_log("Fist invoke");
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
sol_log("2nd invoke from first program");
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42 - 5 + 1 + 1);
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10 + 5 - 1 - 1);
}
sol_log("Verify data values are retained and updated");
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data[i] == i);
}
return SUCCESS;
}

View File

@ -0,0 +1,126 @@
/**
* @brief Example C-based BPF program that prints out the parameters
* passed to it
*/
#include <solana_sdk.h>
extern uint64_t entrypoint(const uint8_t *input) {
SolAccountInfo accounts[4];
SolParameters params = (SolParameters){.ka = accounts};
if (!sol_deserialize(input, &params, 0)) {
return ERROR_INVALID_ARGUMENT;
}
switch (params.data[0]) {
case (0): {
sol_log("verify data translations");
static const int ARGUMENT_INDEX = 0;
static const int INVOKED_ARGUMENT_INDEX = 1;
static const int INVOKED_PROGRAM_INDEX = 2;
static const int INVOKED_PROGRAM_DUP_INDEX = 3;
sol_assert(sol_deserialize(input, &params, 4));
SolPubkey bpf_loader_id =
(SolPubkey){.x = {2, 168, 246, 145, 78, 136, 161, 107, 189, 35, 149,
133, 95, 100, 4, 217, 180, 244, 86, 183, 130, 27,
176, 20, 87, 73, 66, 140, 0, 0, 0, 0}};
for (int i = 0; i < params.data_len; i++) {
sol_assert(params.data[i] == i);
}
sol_assert(params.ka_num == 4);
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42);
sol_assert(accounts[ARGUMENT_INDEX].data_len == 100);
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(accounts[ARGUMENT_INDEX].is_writable);
sol_assert(accounts[ARGUMENT_INDEX].rent_epoch == 1);
sol_assert(!accounts[ARGUMENT_INDEX].executable);
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
sol_assert(SolPubkey_same(accounts[INVOKED_ARGUMENT_INDEX].owner,
accounts[INVOKED_PROGRAM_INDEX].key));
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data_len == 10);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_writable);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch == 1);
sol_assert(!accounts[INVOKED_ARGUMENT_INDEX].executable);
sol_assert(
SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key, params.program_id))
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].owner,
&bpf_loader_id));
sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_signer);
sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_writable);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch == 1);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].executable);
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key,
accounts[INVOKED_PROGRAM_DUP_INDEX].key));
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].owner,
accounts[INVOKED_PROGRAM_DUP_INDEX].owner));
sol_assert(*accounts[INVOKED_PROGRAM_INDEX].lamports ==
*accounts[INVOKED_PROGRAM_DUP_INDEX].lamports);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].is_signer ==
accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].is_writable ==
accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch ==
accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].executable ==
accounts[INVOKED_PROGRAM_DUP_INDEX].executable);
break;
}
case (1): {
sol_log("reutrn error");
return 42;
}
case (2): {
sol_log("verify derived signers");
static const int DERIVED_KEY_INDEX = 0;
static const int DERIVED_KEY2_INDEX = 1;
sol_assert(sol_deserialize(input, &params, 2));
sol_assert(accounts[DERIVED_KEY_INDEX].is_signer);
sol_assert(accounts[DERIVED_KEY2_INDEX].is_signer);
break;
}
case (3): {
sol_log("verify writable");
static const int ARGUMENT_INDEX = 0;
sol_assert(sol_deserialize(input, &params, 1));
sol_assert(accounts[ARGUMENT_INDEX].is_writable);
break;
}
case (4): {
sol_log("invoke");
static const int INVOKED_ARGUMENT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
static const int DERIVED_KEY_INDEX = 2;
sol_assert(sol_deserialize(input, &params, 3));
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(accounts[DERIVED_KEY_INDEX].is_signer);
*accounts[INVOKED_ARGUMENT_INDEX].lamports -= 1;
*accounts[ARGUMENT_INDEX].lamports += 1;
sol_log("Last invoke");
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
accounts[INVOKED_ARGUMENT_INDEX].data[i] = i;
}
break;
}
default:
return ERROR_INVALID_INSTRUCTION_DATA;
}
return SUCCESS;
}

View File

@ -0,0 +1,27 @@
# Note: This crate must be built using do.sh
[package]
name = "solana-bpf-rust-invoke"
version = "1.0.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
solana-sdk = { path = "../../../../sdk/", version = "1.0.0", default-features = false }
solana-bpf-rust-invoked = { path = "../invoked"}
[dev_dependencies]
solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "1.0.0" }
[features]
program = ["solana-sdk/program"]
default = ["program"]
[lib]
name = "solana_bpf_rust_invoke"
crate-type = ["cdylib"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,145 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
#![allow(unreachable_code)]
extern crate solana_sdk;
use solana_bpf_rust_invoked::instruction::create_instruction;
use solana_sdk::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
info,
program::{invoke, invoke_signed},
program_error::ProgramError,
pubkey::Pubkey,
};
// const MINT_INDEX: usize = 0;
const ARGUMENT_INDEX: usize = 1;
const INVOKED_PROGRAM_INDEX: usize = 2;
const INVOKED_ARGUMENT_INDEX: usize = 3;
const INVOKED_PROGRAM_DUP_INDEX: usize = 4;
// const ARGUMENT_DUP_INDEX: usize = 5;
const DERIVED_KEY_INDEX: usize = 6;
const DERIVED_KEY2_INDEX: usize = 7;
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
info!("invoke Rust program");
info!("Test data translation");
{
{
let mut data = accounts[ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..100 {
data[i as usize] = i;
}
}
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![0, 1, 2, 3, 4, 5],
);
invoke(&instruction, accounts)?;
}
info!("Test return error");
{
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, true, true)],
vec![1],
);
assert_eq!(
invoke(&instruction, accounts),
Err(ProgramError::Custom(42))
);
}
info!("Test derived signers");
{
assert!(!accounts[DERIVED_KEY_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[DERIVED_KEY_INDEX].key, true, true),
(accounts[DERIVED_KEY2_INDEX].key, false, true),
],
vec![2],
);
invoke_signed(
&invoked_instruction,
accounts,
&[&["Lil'", "Bits"], &["Gar Ma Nar Nar"]],
)?;
}
info!("Test readonly with writable account");
{
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, false, true)],
vec![3],
);
invoke(&invoked_instruction, accounts)?;
}
info!("Test nested invoke");
{
assert!(accounts[ARGUMENT_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() -= 5;
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() += 5;
info!("Fist invoke");
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[DERIVED_KEY_INDEX].key, true, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![4],
);
invoke(&instruction, accounts)?;
info!("2nd invoke from first program");
invoke(&instruction, accounts)?;
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1);
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].lamports(),
10 + 5 - 1 - 1 - 1 - 1
);
}
info!("Verify data values are retained and updated");
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
}
let data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..10 {
assert_eq!(data[i as usize], i);
}
}
Ok(())
}

View File

@ -0,0 +1,26 @@
# Note: This crate must be built using do.sh
[package]
name = "solana-bpf-rust-invoked"
version = "1.0.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
solana-sdk = { path = "../../../../sdk/", version = "1.0.0", default-features = false }
[dev_dependencies]
solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "1.0.0" }
[features]
program = ["solana-sdk/program"]
default = ["program"]
[lib]
name = "solana_bpf_rust_invoked"
crate-type = ["lib", "cdylib"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,28 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
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,
}
}

View File

@ -0,0 +1,160 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
#![allow(unreachable_code)]
pub mod instruction;
extern crate solana_sdk;
use crate::instruction::create_instruction;
use solana_sdk::{
account_info::AccountInfo, bpf_loader, entrypoint, entrypoint::ProgramResult, info,
program::invoke_signed, program_error::ProgramError, pubkey::Pubkey,
};
entrypoint!(process_instruction);
#[allow(clippy::cognitive_complexity)]
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
info!("Invoked program");
match instruction_data[0] {
0 => {
info!("verify data translations");
const ARGUMENT_INDEX: usize = 0;
const INVOKED_ARGUMENT_INDEX: usize = 1;
const INVOKED_PROGRAM_INDEX: usize = 2;
const INVOKED_PROGRAM_DUP_INDEX: usize = 3;
assert_eq!(instruction_data, &[0, 1, 2, 3, 4, 5]);
assert_eq!(accounts.len(), 4);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42);
assert_eq!(accounts[ARGUMENT_INDEX].data_len(), 100);
assert!(accounts[ARGUMENT_INDEX].is_signer);
assert!(accounts[ARGUMENT_INDEX].is_writable);
assert_eq!(accounts[ARGUMENT_INDEX].rent_epoch, 1);
assert!(!accounts[ARGUMENT_INDEX].executable);
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
}
}
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].owner,
accounts[INVOKED_PROGRAM_INDEX].key
);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].data_len(), 10);
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_writable);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch, 1);
assert!(!accounts[INVOKED_ARGUMENT_INDEX].executable);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].key, program_id);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].owner, &bpf_loader::id());
assert!(!accounts[INVOKED_PROGRAM_INDEX].is_signer);
assert!(!accounts[INVOKED_PROGRAM_INDEX].is_writable);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].rent_epoch, 1);
assert!(accounts[INVOKED_PROGRAM_INDEX].executable);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].key,
accounts[INVOKED_PROGRAM_DUP_INDEX].key
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].owner,
accounts[INVOKED_PROGRAM_DUP_INDEX].owner
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].lamports,
accounts[INVOKED_PROGRAM_DUP_INDEX].lamports
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].is_signer,
accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].is_writable,
accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].rent_epoch,
accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].executable,
accounts[INVOKED_PROGRAM_DUP_INDEX].executable
);
{
let data = accounts[INVOKED_PROGRAM_INDEX].try_borrow_data()?;
assert!(accounts[INVOKED_PROGRAM_DUP_INDEX]
.try_borrow_mut_data()
.is_err());
info!(data[0], 0, 0, 0, 0);
}
}
1 => {
info!("return error");
return Err(ProgramError::Custom(42));
}
2 => {
info!("verify derived signers");
const DERIVED_KEY_INDEX: usize = 0;
const DERIVED_KEY2_INDEX: usize = 1;
assert!(accounts[DERIVED_KEY_INDEX].is_signer);
assert!(accounts[DERIVED_KEY2_INDEX].is_signer);
}
3 => {
info!("verify writable");
const ARGUMENT_INDEX: usize = 0;
assert!(!accounts[ARGUMENT_INDEX].is_writable);
}
4 => {
info!("nested invoke");
const ARGUMENT_INDEX: usize = 0;
const INVOKED_ARGUMENT_INDEX: usize = 1;
const DERIVED_KEY_INDEX: usize = 2;
const INVOKED_PROGRAM_INDEX: usize = 3;
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() -= 1;
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() += 1;
if accounts.len() > 3 {
info!("Invoke again");
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[DERIVED_KEY_INDEX].key, true, true),
],
vec![4],
);
invoke_signed(&invoked_instruction, accounts, &[&["Lil'", "Bits"]])?;
} else {
info!("Last invoked");
assert!(accounts[DERIVED_KEY_INDEX].is_signer);
{
let mut data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..10 {
data[i as usize] = i;
}
}
}
}
_ => panic!(),
}
Ok(())
}

View File

@ -13,6 +13,7 @@ mod bpf {
client::SyncClient,
clock::DEFAULT_SLOTS_PER_EPOCH,
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
pubkey::Pubkey,
signature::Keypair,
signature::Signer,
@ -303,4 +304,71 @@ mod bpf {
);
}
}
#[test]
fn test_program_bpf_invoke() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "bpf_c")]
{
programs.extend_from_slice(&[("invoke", "invoked")]);
}
#[cfg(feature = "bpf_rust")]
{
programs.extend_from_slice(&[("solana_bpf_rust_invoke", "solana_bpf_rust_invoked")]);
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
genesis_config
.native_instruction_processors
.push(solana_bpf_loader_program!());
let bank = Arc::new(Bank::new(&genesis_config));
let bank_client = BankClient::new_shared(&bank);
let program_id = load_bpf_program(&bank_client, &mint_keypair, program.0);
let invoked_program_id = load_bpf_program(&bank_client, &mint_keypair, program.1);
let account = Account::new(42, 100, &program_id);
let argument_keypair = Keypair::new();
bank.store_account(&argument_keypair.pubkey(), &account);
let account = Account::new(10, 10, &invoked_program_id);
let invoked_argument_keypair = Keypair::new();
bank.store_account(&invoked_argument_keypair.pubkey(), &account);
let derived_key =
Pubkey::create_program_address(&["Lil'", "Bits"], &invoked_program_id).unwrap();
let derived_key2 =
Pubkey::create_program_address(&["Gar Ma Nar Nar"], &invoked_program_id).unwrap();
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(invoked_argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new(derived_key, false),
AccountMeta::new_readonly(derived_key2, false),
];
let instruction = Instruction::new(program_id, &1u8, account_metas);
let message = Message::new(&[instruction]);
assert!(bank_client
.send_message(
&[&mint_keypair, &argument_keypair, &invoked_argument_keypair],
message,
)
.is_ok());
}
}
}

View File

@ -11,11 +11,12 @@ edition = "2018"
[dependencies]
bincode = "1.2.1"
byteorder = "1.3.4"
libc = "0.2.69"
jemalloc-sys = { version = "0.3.2", features = ["disable_initial_exec_tls"] }
log = "0.4.8"
num-derive = { version = "0.3" }
num-traits = { version = "0.2" }
solana-logger = { path = "../../logger", version = "1.2.0" }
solana-runtime = { path = "../../runtime", version = "1.2.0" }
solana-sdk = { path = "../../sdk", version = "1.2.0" }
solana_rbpf = "=0.1.25"
thiserror = "1.0"

View File

@ -6,10 +6,25 @@ use solana_rbpf::{
memory_region::{translate_addr, MemoryRegion},
EbpfVm,
};
use solana_sdk::instruction::InstructionError;
use solana_runtime::message_processor::MessageProcessor;
use solana_sdk::{
account::Account,
account_info::AccountInfo,
bpf_loader,
entrypoint::SUCCESS,
entrypoint_native::InvokeContext,
hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
program_error::ProgramError,
pubkey::{Pubkey, PubkeyError},
};
use std::{
alloc::Layout,
cell::{RefCell, RefMut},
convert::TryFrom,
mem::{align_of, size_of},
rc::Rc,
slice::from_raw_parts_mut,
str::{from_utf8, Utf8Error},
};
@ -24,6 +39,14 @@ pub enum HelperError {
Abort,
#[error("BPF program Panicked at {0}, {1}:{2}")]
Panic(String, u64, u64),
#[error("cannot borrow invoke context")]
InvokeContextBorrowFailed,
#[error("malformed signer seed: {0}: {1:?}")]
MalformedSignerSeed(Utf8Error, Vec<u8>),
#[error("Could not create program address with signer seeds: {0}")]
BadSeeds(PubkeyError),
#[error("Program id is not supported by cross-program invocations")]
ProgramNotSupported,
#[error("{0}")]
InstructionError(InstructionError),
}
@ -47,6 +70,7 @@ const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
pub fn register_helpers<'a>(
vm: &mut EbpfVm<'a, BPFError>,
invoke_context: &'a mut dyn InvokeContext,
) -> Result<MemoryRegion, EbpfError<BPFError>> {
vm.register_helper_ex("abort", helper_abort)?;
vm.register_helper_ex("sol_panic", helper_sol_panic)?;
@ -56,6 +80,20 @@ pub fn register_helpers<'a>(
vm.register_helper_ex("sol_log_64", helper_sol_log_u64)?;
vm.register_helper_ex("sol_log_64_", helper_sol_log_u64)?;
let invoke_context = Rc::new(RefCell::new(invoke_context));
vm.register_helper_with_context_ex(
"sol_invoke_signed_rust",
Box::new(HelperProcessInstructionRust {
invoke_context: invoke_context.clone(),
}),
)?;
vm.register_helper_with_context_ex(
"sol_invoke_signed_c",
Box::new(HelperProcessSolInstructionC {
invoke_context: invoke_context.clone(),
}),
)?;
let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
vm.register_helper_with_context_ex(
@ -255,13 +293,460 @@ impl HelperObject<BPFError> for HelperSolAllocFree {
}
}
// Cross-program invocation helpers
pub type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<(&'a mut u64, &'a mut [u8])>);
/// Implemented by language specific data structure translators
trait HelperProcessInstruction<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>>;
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>>;
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>>;
}
/// Cross-program invocation called from Rust
pub struct HelperProcessInstructionRust<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessInstructionRust<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix = translate_type!(Instruction, addr, ro_regions)?;
let accounts = translate_slice!(
AccountMeta,
ix.accounts.as_ptr(),
ix.accounts.len(),
ro_regions
)?
.to_vec();
let data = translate_slice!(u8, ix.data.as_ptr(), ix.data.len(), ro_regions)?.to_vec();
Ok(Instruction {
program_id: ix.program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = if account_infos_len > 0 {
translate_slice!(
AccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?
} else {
&[]
};
let mut accounts = Vec::with_capacity(message.account_keys.len());
let mut refs = Vec::with_capacity(message.account_keys.len());
'root: for account_key in message.account_keys.iter() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key as *const _, ro_regions)?;
if account_key == key {
let lamports_ref = {
// Double translate lamports out of RefCell
let ptr = translate_type!(u64, account_info.lamports.as_ptr(), ro_regions)?;
translate_type_mut!(u64, *(ptr as *const u64), rw_regions)?
};
let data = {
// Double translate data out of RefCell
let data = *translate_type!(&[u8], account_info.data.as_ptr(), ro_regions)?;
translate_slice_mut!(u8, data.as_ptr(), data.len(), rw_regions)?
};
let owner =
translate_type!(Pubkey, account_info.owner as *const _, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
let mut signers = Vec::new();
if signers_seeds_len > 0 {
let signers_seeds =
translate_slice!(&[&str], signers_seeds_addr, signers_seeds_len, ro_regions)?;
for signer_seeds in signers_seeds.iter() {
let untranslated_seeds =
translate_slice!(&str, signer_seeds.as_ptr(), signer_seeds.len(), ro_regions)?;
let seeds = untranslated_seeds
.iter()
.map(|untranslated_seed| {
let seed_bytes = translate_slice!(
u8,
untranslated_seed.as_ptr(),
untranslated_seed.len(),
ro_regions
)?;
from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
let signer = Pubkey::create_program_address(&seeds, program_id)
.map_err(HelperError::BadSeeds)?;
signers.push(signer);
}
Ok(signers)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessInstructionRust<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Rust representation of C's SolInstruction
#[derive(Debug)]
struct SolInstruction {
program_id_addr: u64,
accounts_addr: u64,
accounts_len: usize,
data_addr: u64,
data_len: usize,
}
/// Rust representation of C's SolAccountMeta
#[derive(Debug)]
struct SolAccountMeta {
pubkey_addr: u64,
is_writable: bool,
is_signer: bool,
}
/// Rust representation of C's SolAccountInfo
#[derive(Debug)]
struct SolAccountInfo {
key_addr: u64,
lamports_addr: u64,
data_len: usize,
data_addr: u64,
owner_addr: u64,
rent_epoch: u64,
is_signer: bool,
is_writable: bool,
executable: bool,
}
/// Rust representation of C's SolSignerSeed
#[derive(Debug)]
struct SolSignerSeedC {
addr: u64,
len: u64,
}
/// Rust representation of C's SolSignerSeeds
#[derive(Debug)]
struct SolSignerSeedsC {
addr: u64,
len: u64,
}
/// Cross-program invocation called from C
pub struct HelperProcessSolInstructionC<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessSolInstructionC<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix_c = translate_type!(SolInstruction, addr, ro_regions)?;
let program_id = translate_type!(Pubkey, ix_c.program_id_addr, ro_regions)?;
let meta_cs = translate_slice!(
SolAccountMeta,
ix_c.accounts_addr,
ix_c.accounts_len,
ro_regions
)?;
let data = translate_slice!(u8, ix_c.data_addr, ix_c.data_len, ro_regions)?.to_vec();
let accounts = meta_cs
.iter()
.map(|meta_c| {
let pubkey = translate_type!(Pubkey, meta_c.pubkey_addr, ro_regions)?;
Ok(AccountMeta {
pubkey: *pubkey,
is_signer: meta_c.is_signer,
is_writable: meta_c.is_writable,
})
})
.collect::<Result<Vec<AccountMeta>, EbpfError<BPFError>>>()?;
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = translate_slice!(
SolAccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?;
let mut accounts = Vec::with_capacity(message.account_keys.len());
let mut refs = Vec::with_capacity(message.account_keys.len());
'root: for account_key in message.account_keys.iter() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key_addr, ro_regions)?;
if account_key == key {
let lamports_ref =
translate_type_mut!(u64, account_info.lamports_addr, rw_regions)?;
let data = translate_slice_mut!(
u8,
account_info.data_addr,
account_info.data_len,
rw_regions
)?;
let owner = translate_type!(Pubkey, account_info.owner_addr, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
if signers_seeds_len > 0 {
let signers_seeds = translate_slice!(
SolSignerSeedC,
signers_seeds_addr,
signers_seeds_len,
ro_regions
)?;
Ok(signers_seeds
.iter()
.map(|signer_seeds| {
let seeds = translate_slice!(
SolSignerSeedC,
signer_seeds.addr,
signer_seeds.len,
ro_regions
)?;
let seed_strs = seeds
.iter()
.map(|seed| {
let seed_bytes = translate_slice!(u8, seed.addr, seed.len, ro_regions)?;
std::str::from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
Pubkey::create_program_address(&seed_strs, program_id)
.map_err(|err| HelperError::BadSeeds(err).into())
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessSolInstructionC<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Call process instruction, common to both Rust and C
fn call<'a>(
helper: &mut dyn HelperProcessInstruction<'a>,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
let mut invoke_context = helper.get_context_mut()?;
// Translate data passed from the VM
let instruction = helper.translate_instruction(instruction_addr, ro_regions)?;
let message = Message::new(&[instruction]);
let program_id_index = message.instructions[0].program_id_index as usize;
let program_id = message.account_keys[program_id_index];
let (accounts, refs) = helper.translate_accounts(
&message,
account_infos_addr,
account_infos_len as usize,
ro_regions,
rw_regions,
)?;
let signers = helper.translate_signers(
&program_id,
signers_seeds_addr,
signers_seeds_len as usize,
ro_regions,
)?;
// Process instruction
let program_account = (*accounts[program_id_index]).clone();
if program_account.borrow().owner != bpf_loader::id() {
// Only BPF programs supported for now
return Err(HelperError::ProgramNotSupported.into());
}
let executable_accounts = vec![(program_id, program_account)];
#[allow(clippy::deref_addrof)]
match MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&signers,
crate::process_instruction,
*(&mut *invoke_context),
) {
Ok(()) => (),
Err(err) => match ProgramError::try_from(err) {
Ok(err) => return Ok(err.into()),
Err(err) => return Err(HelperError::InstructionError(err).into()),
},
}
// Copy results back into caller's AccountInfos
for (i, (account, (lamport_ref, data))) in accounts.iter().zip(refs).enumerate() {
let account = account.borrow();
if message.is_writable(i) && !account.executable {
*lamport_ref = account.lamports;
data.clone_from_slice(&account.data);
}
}
Ok(SUCCESS)
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
#[test]
fn test_translate() {

View File

@ -16,6 +16,7 @@ use solana_sdk::{
account::KeyedAccount,
bpf_loader,
entrypoint::SUCCESS,
entrypoint_native::InvokeContext,
instruction::InstructionError,
loader_instruction::LoaderInstruction,
program_utils::DecodeError,
@ -54,13 +55,16 @@ pub enum BPFError {
}
impl UserDefinedError for BPFError {}
pub fn create_vm(prog: &[u8]) -> Result<(EbpfVm<BPFError>, MemoryRegion), EbpfError<BPFError>> {
pub fn create_vm<'a>(
prog: &'a [u8],
invoke_context: &'a mut dyn InvokeContext,
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
let mut vm = EbpfVm::new(None)?;
vm.set_verifier(bpf_verifier::check)?;
vm.set_max_instruction_count(100_000)?;
vm.set_elf(&prog)?;
let heap_region = helpers::register_helpers(&mut vm)?;
let heap_region = helpers::register_helpers(&mut vm, invoke_context)?;
Ok((vm, heap_region))
}
@ -155,6 +159,7 @@ pub fn process_instruction(
program_id: &Pubkey,
keyed_accounts: &[KeyedAccount],
instruction_data: &[u8],
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
solana_logger::setup_with_default("solana=info");
@ -177,7 +182,7 @@ pub fn process_instruction(
)?;
{
let program_account = program.try_account_ref_mut()?;
let (mut vm, heap_region) = match create_vm(&program_account.data) {
let (mut vm, heap_region) = match create_vm(&program_account.data, invoke_context) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
@ -250,8 +255,28 @@ pub fn process_instruction(
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{account::Account, rent::Rent};
use std::{fs::File, io::Read};
use solana_sdk::{
account::Account, instruction::CompiledInstruction, message::Message, rent::Rent,
};
use std::{cell::RefCell, fs::File, io::Read, rc::Rc};
#[derive(Debug, Default)]
pub struct MockInvokeContext {}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Ok(())
}
fn pop(&mut self) {}
fn verify_and_update(
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())
}
}
#[test]
#[should_panic(expected = "ExceededMaxInstructions(10)")]
@ -286,13 +311,23 @@ mod tests {
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &instruction_data)
process_instruction(
&bpf_loader::id(),
&vec![],
&instruction_data,
&mut MockInvokeContext::default()
)
);
// Case: Not signed
assert_eq!(
Err(InstructionError::MissingRequiredSignature),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
// Case: Write bytes to an offset
@ -300,7 +335,12 @@ mod tests {
keyed_accounts[0].account.borrow_mut().data = vec![0; 6];
assert_eq!(
Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
assert_eq!(
vec![0, 0, 0, 1, 2, 3],
@ -312,7 +352,12 @@ mod tests {
keyed_accounts[0].account.borrow_mut().data = vec![0; 5];
assert_eq!(
Err(InstructionError::AccountDataTooSmall),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
}
@ -332,20 +377,35 @@ mod tests {
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &instruction_data)
process_instruction(
&bpf_loader::id(),
&vec![],
&instruction_data,
&mut MockInvokeContext::default()
)
);
// Case: Not signed
assert_eq!(
Err(InstructionError::MissingRequiredSignature),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
// Case: Finalize
let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
assert_eq!(
Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
assert!(keyed_accounts[0].account.borrow().executable);
@ -356,7 +416,12 @@ mod tests {
let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
assert_eq!(
Err(InstructionError::InvalidAccountData),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
);
}
@ -380,20 +445,35 @@ mod tests {
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &vec![])
process_instruction(
&bpf_loader::id(),
&vec![],
&vec![],
&mut MockInvokeContext::default()
)
);
// Case: Only a program account
assert_eq!(
Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![])
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
);
// Case: Account not executable
keyed_accounts[0].account.borrow_mut().executable = false;
assert_eq!(
Err(InstructionError::InvalidInstructionData),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![])
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
);
keyed_accounts[0].account.borrow_mut().executable = true;
@ -402,7 +482,12 @@ mod tests {
keyed_accounts.push(KeyedAccount::new(&program_key, false, &parameter_account));
assert_eq!(
Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![])
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
);
// Case: With duplicate accounts
@ -413,7 +498,12 @@ mod tests {
keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, &parameter_account));
assert_eq!(
Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![])
process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
);
}
}

View File

@ -8,6 +8,7 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::KeyedAccount,
account_utils::State,
entrypoint_native::InvokeContext,
instruction::InstructionError,
move_loader::id,
program_utils::DecodeError,
@ -445,6 +446,7 @@ impl MoveProcessor {
_program_id: &Pubkey,
keyed_accounts: &[KeyedAccount],
instruction_data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
solana_logger::setup();