Add Cross-program invocations (#9582)
This commit is contained in:
20
programs/bpf/Cargo.lock
generated
20
programs/bpf/Cargo.lock
generated
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,8 @@ fn main() {
|
||||
"dup_accounts",
|
||||
"error_handling",
|
||||
"external_spend",
|
||||
"invoke",
|
||||
"invoked",
|
||||
"iter",
|
||||
"many_args",
|
||||
"noop",
|
||||
|
131
programs/bpf/c/src/invoke/invoke.c
Normal file
131
programs/bpf/c/src/invoke/invoke.c
Normal 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, ¶ms, 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;
|
||||
}
|
126
programs/bpf/c/src/invoked/invoked.c
Normal file
126
programs/bpf/c/src/invoked/invoked.c
Normal 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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;
|
||||
}
|
27
programs/bpf/rust/invoke/Cargo.toml
Normal file
27
programs/bpf/rust/invoke/Cargo.toml
Normal 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"]
|
2
programs/bpf/rust/invoke/Xargo.toml
Normal file
2
programs/bpf/rust/invoke/Xargo.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
145
programs/bpf/rust/invoke/src/lib.rs
Normal file
145
programs/bpf/rust/invoke/src/lib.rs
Normal 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(())
|
||||
}
|
26
programs/bpf/rust/invoked/Cargo.toml
Normal file
26
programs/bpf/rust/invoked/Cargo.toml
Normal 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"]
|
2
programs/bpf/rust/invoked/Xargo.toml
Normal file
2
programs/bpf/rust/invoked/Xargo.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
28
programs/bpf/rust/invoked/src/instruction.rs
Normal file
28
programs/bpf/rust/invoked/src/instruction.rs
Normal 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,
|
||||
}
|
||||
}
|
160
programs/bpf/rust/invoked/src/lib.rs
Normal file
160
programs/bpf/rust/invoked/src/lib.rs
Normal 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(())
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user