From 068f12fd6f7dc5b20d2f4ed015f664eee0a132d1 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 28 Apr 2020 14:33:56 -0700 Subject: [PATCH] Add Cross-program invocations (#9582) --- Cargo.lock | 53 +- programs/bpf/Cargo.lock | 20 +- programs/bpf/Cargo.toml | 2 + programs/bpf/benches/bpf_loader.rs | 29 +- programs/bpf/build.rs | 2 + programs/bpf/c/src/invoke/invoke.c | 131 ++++ programs/bpf/c/src/invoked/invoked.c | 126 ++++ programs/bpf/rust/invoke/Cargo.toml | 27 + programs/bpf/rust/invoke/Xargo.toml | 2 + programs/bpf/rust/invoke/src/lib.rs | 145 ++++ programs/bpf/rust/invoked/Cargo.toml | 26 + programs/bpf/rust/invoked/Xargo.toml | 2 + programs/bpf/rust/invoked/src/instruction.rs | 28 + programs/bpf/rust/invoked/src/lib.rs | 160 +++++ programs/bpf/tests/programs.rs | 68 ++ programs/bpf_loader/Cargo.toml | 3 +- programs/bpf_loader/src/helpers.rs | 495 ++++++++++++- programs/bpf_loader/src/lib.rs | 126 +++- programs/move_loader/src/processor.rs | 2 + runtime/benches/message_processor.rs | 34 +- runtime/src/message_processor.rs | 696 +++++++++++++++---- runtime/src/native_loader.rs | 12 +- sdk/bpf/c/inc/solana_sdk.h | 95 ++- sdk/src/entrypoint_native.rs | 23 +- sdk/src/instruction.rs | 22 +- sdk/src/lib.rs | 1 + sdk/src/program.rs | 42 ++ 27 files changed, 2164 insertions(+), 208 deletions(-) create mode 100644 programs/bpf/c/src/invoke/invoke.c create mode 100644 programs/bpf/c/src/invoked/invoked.c create mode 100644 programs/bpf/rust/invoke/Cargo.toml create mode 100644 programs/bpf/rust/invoke/Xargo.toml create mode 100644 programs/bpf/rust/invoke/src/lib.rs create mode 100644 programs/bpf/rust/invoked/Cargo.toml create mode 100644 programs/bpf/rust/invoked/Xargo.toml create mode 100644 programs/bpf/rust/invoked/src/instruction.rs create mode 100644 programs/bpf/rust/invoked/src/lib.rs create mode 100644 sdk/src/program.rs diff --git a/Cargo.lock b/Cargo.lock index 67eabb4770..6ec0a4debf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,11 +43,6 @@ name = "anyhow" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "approx" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "ar" version = "0.6.2" @@ -472,16 +467,6 @@ name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cgmath" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "chashmap" version = "2.2.2" @@ -573,11 +558,12 @@ dependencies = [ [[package]] name = "colored" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1000,7 +986,7 @@ dependencies = [ "bloom 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1397,7 +1383,7 @@ dependencies = [ [[package]] name = "hash32" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3151,11 +3137,6 @@ dependencies = [ "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rgb" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "ring" version = "0.16.11" @@ -3755,11 +3736,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.11 (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.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5161,7 +5143,7 @@ dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "elfkit 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6233,17 +6215,6 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "winconsole" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winreg" version = "0.6.2" @@ -6346,7 +6317,6 @@ dependencies = [ "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" -"checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" "checksum ar 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "579681b3fecd1e9d6b5ce6969e05f9feb913f296eddaf595be1166a5ca597bc4" "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" @@ -6402,7 +6372,6 @@ dependencies = [ "checksum cc 1.0.49 (registry+https://github.com/rust-lang/crates.io-index)" = "e450b8da92aa6f274e7c6437692f9f2ce6d701fb73bacfcf87897b3f89a4c20e" "checksum cexpr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" -"checksum cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c" "checksum chashmap 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" "checksum clang-sys 0.29.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" @@ -6412,7 +6381,7 @@ dependencies = [ "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum codespan 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "004def512a9848b23a68ed110927d102b0e6d9f3dc732e28570879afde051f8c" "checksum codespan-reporting 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab081a14ab8f9598ce826890fe896d0addee68c7a58ab49008369ccbb51510a8" -"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03" +"checksum colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" "checksum combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1645a65a99c7c8d345761f4b75a6ffe5be3b3b27a93ee731fccc5050ba6be97c" "checksum console 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "703dd7516d1a3e6483189c8d9a18a9af84877084630875ff9e922c201f9e0209" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" @@ -6506,7 +6475,7 @@ dependencies = [ "checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" "checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" -"checksum hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12d790435639c06a7b798af9e1e331ae245b7ef915b92f70a39b4cf8c00686af" +"checksum hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" @@ -6696,7 +6665,6 @@ dependencies = [ "checksum rental 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "01916ebd9fc2e81978a5dc9542a2fa47f5bb2ca3402e14c7cc42d6e3c5123e1f" "checksum rental-impl 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82260d54cf2cbe9608df161f7e7c98e81fae702aa13af9e4d5d39dc2ffb25ab6" "checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" -"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92" "checksum ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)" = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" "checksum rocksdb 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61aa17a99a2413cd71c1106691bf59dad7de0cd5099127f90e9d99c429c40d4a" "checksum rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f" @@ -6888,7 +6856,6 @@ dependencies = [ "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" -"checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032" "checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" "checksum winreg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" "checksum ws 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a2c47b5798ccc774ffb93ff536aec7c4275d722fd9c740c83cdd1af1f2d94" diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 7881817065..7de4500aac 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -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" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 8df8c5f2ee..90915f5906 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -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", diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index ffb34613df..d9b98988a9 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -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::(ARMSTRONG_LIMIT) .unwrap(); inner_iter.write_u64::(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>], + ) -> Result<(), InstructionError> { + Ok(()) + } +} diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 8b48fbad58..78036080a2 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -71,6 +71,8 @@ fn main() { "dup_accounts", "error_handling", "external_spend", + "invoke", + "invoked", "iter", "many_args", "noop", diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c new file mode 100644 index 0000000000..2aee21d7d1 --- /dev/null +++ b/programs/bpf/c/src/invoke/invoke.c @@ -0,0 +1,131 @@ +/** + * @brief Example C-based BPF program that prints out the parameters + * passed to it + */ +#include + +#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; +} diff --git a/programs/bpf/c/src/invoked/invoked.c b/programs/bpf/c/src/invoked/invoked.c new file mode 100644 index 0000000000..63560f6a15 --- /dev/null +++ b/programs/bpf/c/src/invoked/invoked.c @@ -0,0 +1,126 @@ +/** + * @brief Example C-based BPF program that prints out the parameters + * passed to it + */ +#include + +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; +} diff --git a/programs/bpf/rust/invoke/Cargo.toml b/programs/bpf/rust/invoke/Cargo.toml new file mode 100644 index 0000000000..76cd23a88e --- /dev/null +++ b/programs/bpf/rust/invoke/Cargo.toml @@ -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 "] +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"] diff --git a/programs/bpf/rust/invoke/Xargo.toml b/programs/bpf/rust/invoke/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/invoke/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs new file mode 100644 index 0000000000..e6c4341a0e --- /dev/null +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -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(()) +} diff --git a/programs/bpf/rust/invoked/Cargo.toml b/programs/bpf/rust/invoked/Cargo.toml new file mode 100644 index 0000000000..03b97fcb54 --- /dev/null +++ b/programs/bpf/rust/invoked/Cargo.toml @@ -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 "] +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"] diff --git a/programs/bpf/rust/invoked/Xargo.toml b/programs/bpf/rust/invoked/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/invoked/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/invoked/src/instruction.rs b/programs/bpf/rust/invoked/src/instruction.rs new file mode 100644 index 0000000000..29290a224d --- /dev/null +++ b/programs/bpf/rust/invoked/src/instruction.rs @@ -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, +) -> 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, + } +} diff --git a/programs/bpf/rust/invoked/src/lib.rs b/programs/bpf/rust/invoked/src/lib.rs new file mode 100644 index 0000000000..e90e970e84 --- /dev/null +++ b/programs/bpf/rust/invoked/src/lib.rs @@ -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(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 877e10489c..818cf47c75 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -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()); + } + } } diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index 7d12d07213..bb128133dc 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -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" diff --git a/programs/bpf_loader/src/helpers.rs b/programs/bpf_loader/src/helpers.rs index 8cce6a532e..4cc26ab7e1 100644 --- a/programs/bpf_loader/src/helpers.rs +++ b/programs/bpf_loader/src/helpers.rs @@ -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), + #[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> { 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 for HelperSolAllocFree { } } +// Cross-program invocation helpers + +pub type TranslatedAccounts<'a> = (Vec>>, Vec<(&'a mut u64, &'a mut [u8])>); + +/// Implemented by language specific data structure translators +trait HelperProcessInstruction<'a> { + fn get_context_mut(&self) -> Result, EbpfError>; + fn translate_instruction( + &self, + addr: u64, + ro_regions: &[MemoryRegion], + ) -> Result>; + fn translate_accounts( + &self, + message: &Message, + account_infos_addr: u64, + account_infos_len: usize, + ro_regions: &[MemoryRegion], + rw_regions: &[MemoryRegion], + ) -> Result, EbpfError>; + fn translate_signers( + &self, + program_id: &Pubkey, + signers_seeds_addr: u64, + signers_seeds_len: usize, + ro_regions: &[MemoryRegion], + ) -> Result, EbpfError>; +} + +/// Cross-program invocation called from Rust +pub struct HelperProcessInstructionRust<'a> { + invoke_context: Rc>, +} +impl<'a> HelperProcessInstruction<'a> for HelperProcessInstructionRust<'a> { + fn get_context_mut(&self) -> Result, EbpfError> { + self.invoke_context + .try_borrow_mut() + .map_err(|_| HelperError::InvokeContextBorrowFailed.into()) + } + fn translate_instruction( + &self, + addr: u64, + ro_regions: &[MemoryRegion], + ) -> Result> { + 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, EbpfError> { + 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, EbpfError> { + 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::, EbpfError>>()?; + let signer = Pubkey::create_program_address(&seeds, program_id) + .map_err(HelperError::BadSeeds)?; + signers.push(signer); + } + Ok(signers) + } else { + Ok(vec![]) + } + } +} +impl<'a> HelperObject 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> { + 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>, +} +impl<'a> HelperProcessInstruction<'a> for HelperProcessSolInstructionC<'a> { + fn get_context_mut(&self) -> Result, EbpfError> { + self.invoke_context + .try_borrow_mut() + .map_err(|_| HelperError::InvokeContextBorrowFailed.into()) + } + + fn translate_instruction( + &self, + addr: u64, + ro_regions: &[MemoryRegion], + ) -> Result> { + 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::, EbpfError>>()?; + + 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, EbpfError> { + 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, EbpfError> { + 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::, EbpfError>>()?; + Pubkey::create_program_address(&seed_strs, program_id) + .map_err(|err| HelperError::BadSeeds(err).into()) + }) + .collect::, EbpfError>>()?) + } else { + Ok(vec![]) + } + } +} +impl<'a> HelperObject 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> { + 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> { + 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() { diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 77486e2c2b..848b162812 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -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, MemoryRegion), EbpfError> { +pub fn create_vm<'a>( + prog: &'a [u8], + invoke_context: &'a mut dyn InvokeContext, +) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError> { 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>], + ) -> 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, ¶meter_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, ¶meter_account)); assert_eq!( Ok(()), - process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![]) + process_instruction( + &bpf_loader::id(), + &keyed_accounts, + &vec![], + &mut MockInvokeContext::default() + ) ); } } diff --git a/programs/move_loader/src/processor.rs b/programs/move_loader/src/processor.rs index 704bc8ec7e..c4a4a97afb 100644 --- a/programs/move_loader/src/processor.rs +++ b/programs/move_loader/src/processor.rs @@ -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(); diff --git a/runtime/benches/message_processor.rs b/runtime/benches/message_processor.rs index 75582fbe78..d4d9edcc54 100644 --- a/runtime/benches/message_processor.rs +++ b/runtime/benches/message_processor.rs @@ -3,8 +3,8 @@ extern crate test; use log::*; -use solana_runtime::{message_processor::PreAccount, rent_collector::RentCollector}; -use solana_sdk::{account::Account, pubkey::Pubkey}; +use solana_runtime::message_processor::PreAccount; +use solana_sdk::{account::Account, pubkey::Pubkey, rent::Rent}; use test::Bencher; #[bench] @@ -13,28 +13,36 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { let owner = Pubkey::new_rand(); let non_owner = Pubkey::new_rand(); - let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &owner, true); + let pre = PreAccount::new( + &Pubkey::new_rand(), + &Account::new(0, BUFSIZE, &owner), + true, + false, + ); let post = Account::new(0, BUFSIZE, &owner); - assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(())); + assert_eq!(pre.verify(&owner, &Rent::default(), &post), Ok(())); // this one should be faster bencher.iter(|| { - pre.verify(&owner, &RentCollector::default(), &post) - .unwrap(); + pre.verify(&owner, &Rent::default(), &post).unwrap(); }); let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data no change by owner: {} ns/iter", summary.median); - let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &non_owner, true); - match pre.data { - Some(ref data) => bencher.iter(|| *data == post.data), - None => panic!("No data!"), - } + let pre_data = vec![BUFSIZE]; + let post_data = vec![BUFSIZE]; + bencher.iter(|| pre_data == post_data); let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data compare {} ns/iter", summary.median); + + let pre = PreAccount::new( + &Pubkey::new_rand(), + &Account::new(0, BUFSIZE, &owner), + true, + false, + ); bencher.iter(|| { - pre.verify(&non_owner, &RentCollector::default(), &post) - .unwrap(); + pre.verify(&non_owner, &Rent::default(), &post).unwrap(); }); let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data no change by non owner: {} ns/iter", summary.median); diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 1a3c19d7b7..ce731051c6 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -3,10 +3,12 @@ use serde::{Deserialize, Serialize}; use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, clock::Epoch, + entrypoint_native::InvokeContext, instruction::{CompiledInstruction, InstructionError}, message::Message, native_loader, pubkey::Pubkey, + rent::Rent, system_program, transaction::TransactionError, }; @@ -14,56 +16,35 @@ use std::{cell::RefCell, rc::Rc}; // The relevant state of an account before an Instruction executes, used // to verify account integrity after the Instruction completes -#[derive(Debug)] +#[derive(Clone, Debug, Default)] pub struct PreAccount { - pub is_writable: bool, - pub lamports: u64, - pub data_len: usize, - pub data: Option>, - pub owner: Pubkey, - pub is_executable: bool, - pub rent_epoch: Epoch, + key: Pubkey, + is_signer: bool, + is_writable: bool, + is_executable: bool, + lamports: u64, + data: Vec, + owner: Pubkey, + rent_epoch: Epoch, } impl PreAccount { - pub fn new(account: &Account, program_id: &Pubkey, is_writable: bool) -> Self { + pub fn new(key: &Pubkey, account: &Account, is_signer: bool, is_writable: bool) -> Self { Self { + key: *key, + is_signer, is_writable, lamports: account.lamports, - data_len: account.data.len(), - data: if Self::should_verify_data( - &account.owner, - program_id, - is_writable, - account.executable, - ) { - Some(account.data.clone()) - } else { - None - }, + data: account.data.clone(), owner: account.owner, is_executable: account.executable, rent_epoch: account.rent_epoch, } } - fn should_verify_data( - owner: &Pubkey, - program_id: &Pubkey, - is_writable: bool, - is_executable: bool, - ) -> bool { - // For accounts not assigned to the program, the data may not change. - program_id != owner - // Read-only account data may not change. - || !is_writable - // Executable account data may not change. - || is_executable - } - pub fn verify( &self, program_id: &Pubkey, - rent_collector: &RentCollector, + rent: &Rent, post: &Account, ) -> Result<(), InstructionError> { // Only the owner of the account may change owner and @@ -71,7 +52,7 @@ impl PreAccount { // only if the data is zero-initialized or empty if self.owner != post.owner && (!self.is_writable // line coverage used to get branch coverage - || *program_id != self.owner // line coverage used to get branch coverage + || *program_id != self.owner || !Self::is_zeroed(&post.data)) { return Err(InstructionError::ModifiedProgramId); @@ -96,43 +77,37 @@ impl PreAccount { // Only the system program can change the size of the data // and only if the system program owns the account - if self.data_len != post.data.len() + if self.data.len() != post.data.len() && (!system_program::check_id(program_id) // line coverage used to get branch coverage - || !system_program::check_id(&self.owner)) + || !system_program::check_id(&self.owner)) { return Err(InstructionError::AccountDataSizeChanged); } - if Self::should_verify_data( - &self.owner, - program_id, - self.is_writable, - self.is_executable, - ) { - match &self.data { - Some(data) if *data == post.data => (), - _ => { - if self.is_executable { - return Err(InstructionError::ExecutableDataModified); - } else if self.is_writable { - return Err(InstructionError::ExternalAccountDataModified); - } else { - return Err(InstructionError::ReadonlyDataModified); - } - } + // Only the owner may change account data + // and if the account is writable + // and if the account is not executable + if !(*program_id == self.owner + && self.is_writable // line coverage used to get branch coverage + && !self.is_executable) + && self.data != post.data + { + if self.is_executable { + return Err(InstructionError::ExecutableDataModified); + } else if self.is_writable { + return Err(InstructionError::ExternalAccountDataModified); + } else { + return Err(InstructionError::ReadonlyDataModified); } } // executable is one-way (false->true) and only the account owner may set it. if self.is_executable != post.executable { - if !rent_collector - .rent - .is_exempt(post.lamports, post.data.len()) - { + if !rent.is_exempt(post.lamports, post.data.len()) { return Err(InstructionError::ExecutableAccountNotRentExempt); } if !self.is_writable // line coverage used to get branch coverage - || self.is_executable // line coverage used to get branch coverage + || self.is_executable || *program_id != self.owner { return Err(InstructionError::ExecutableModified); @@ -147,6 +122,50 @@ impl PreAccount { Ok(()) } + pub fn verify_cross_program( + &self, + is_writable: bool, + is_signer: bool, + signers: &[Pubkey], + program_id: &Pubkey, + rent: &Rent, + post: &Account, + ) -> Result<(), InstructionError> { + // Readonly account cannot become writable + if is_writable && !self.is_writable { + return Err(InstructionError::WritableModified); + } + + if is_signer && // If message indicates account is signed + !( // one of the following needs to be true: + self.is_signer // Signed in the original transaction + || signers.contains(&self.key) // Signed by the program + ) { + return Err(InstructionError::SignerModified); + } + + self.verify(program_id, rent, post) + } + + pub fn update(&mut self, account: &Account) { + self.lamports = account.lamports; + if self.data.len() != account.data.len() { + // Only system account can change data size, copy with alloc + self.data = account.data.clone(); + } else { + // Copy without allocate + self.data.clone_from_slice(&account.data); + } + } + + pub fn key(&self) -> Pubkey { + self.key + } + + pub fn lamports(&self) -> u64 { + self.lamports + } + pub fn is_zeroed(buf: &[u8]) -> bool { const ZEROS_LEN: usize = 1024; static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN]; @@ -157,7 +176,64 @@ impl PreAccount { } } +#[derive(Debug, Default)] +pub struct ThisInvokeContext { + pub program_ids: Vec, + pub rent: Rent, + pub pre_accounts: Vec, +} +impl ThisInvokeContext { + const MAX_INVOCATION_DEPTH: usize = 5; + pub fn new(program_id: &Pubkey, rent: Rent, pre_accounts: Vec) -> Self { + let mut program_ids = Vec::with_capacity(Self::MAX_INVOCATION_DEPTH); + program_ids.push(*program_id); + Self { + program_ids, + rent, + pre_accounts, + } + } +} +impl InvokeContext for ThisInvokeContext { + fn push(&mut self, key: &Pubkey) -> Result<(), InstructionError> { + if self.program_ids.len() >= Self::MAX_INVOCATION_DEPTH { + return Err(InstructionError::CallDepth); + } + if self.program_ids.contains(key) && self.program_ids.last() != Some(key) { + // Reentrancy not allowed unless caller is calling itself + return Err(InstructionError::ReentrancyNotAllowed); + } + self.program_ids.push(*key); + Ok(()) + } + fn pop(&mut self) { + self.program_ids.pop(); + } + fn verify_and_update( + &mut self, + message: &Message, + instruction: &CompiledInstruction, + signers: &[Pubkey], + accounts: &[Rc>], + ) -> Result<(), InstructionError> { + match self.program_ids.last() { + Some(key) => MessageProcessor::verify_and_update( + message, + instruction, + &mut self.pre_accounts, + key, + &self.rent, + signers, + accounts, + ), + None => Err(InstructionError::GenericError), // Should never happen + } + } +} + pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>; +pub type ProcessInstructionWithContext = + fn(&Pubkey, &[KeyedAccount], &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>; #[derive(Default, Deserialize, Serialize)] pub struct MessageProcessor { @@ -193,16 +269,14 @@ impl MessageProcessor { } } - /// Process an instruction - /// This method calls the instruction's program entrypoint method - fn process_instruction( - &self, - message: &Message, - instruction: &CompiledInstruction, - executable_accounts: &[(Pubkey, RefCell)], - accounts: &[Rc>], - ) -> Result<(), InstructionError> { - let mut keyed_accounts = create_keyed_readonly_accounts(executable_accounts); + /// Create the KeyedAccounts that will be passed to the program + fn create_keyed_accounts<'a>( + message: &'a Message, + instruction: &'a CompiledInstruction, + executable_accounts: &'a [(Pubkey, RefCell)], + accounts: &'a [Rc>], + ) -> Result>, InstructionError> { + let mut keyed_accounts = create_keyed_readonly_accounts(&executable_accounts); let mut keyed_accounts2: Vec<_> = instruction .accounts .iter() @@ -220,6 +294,21 @@ impl MessageProcessor { .collect(); keyed_accounts.append(&mut keyed_accounts2); assert!(keyed_accounts[0].executable()?, "account not executable"); + Ok(keyed_accounts) + } + + /// Process an instruction + /// This method calls the instruction's program entrypoint method + fn process_instruction( + &self, + message: &Message, + instruction: &CompiledInstruction, + invoke_context: &mut dyn InvokeContext, + executable_accounts: &[(Pubkey, RefCell)], + accounts: &[Rc>], + ) -> Result<(), InstructionError> { + let keyed_accounts = + Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; for (id, process_instruction) in &self.instruction_processors { let root_program_id = keyed_accounts[0].unsigned_key(); @@ -237,12 +326,49 @@ impl MessageProcessor { &native_loader::id(), &keyed_accounts, &instruction.data, + invoke_context, ) } else { Err(InstructionError::UnsupportedProgramId) } } + /// Process a cross-program instruction + /// This method calls the instruction's program entrypoint method + pub fn process_cross_program_instruction( + message: &Message, + executable_accounts: &[(Pubkey, RefCell)], + accounts: &[Rc>], + signers: &[Pubkey], + process_instruction: ProcessInstructionWithContext, + invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + let instruction = &message.instructions[0]; + + // Verify the calling program hasn't misbehaved + invoke_context.verify_and_update(message, instruction, signers, accounts)?; + + // Construct keyed accounts + let keyed_accounts = + Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; + + // Invoke callee + invoke_context.push(instruction.program_id(&message.account_keys))?; + let mut result = process_instruction( + &keyed_accounts[0].owner()?, + &keyed_accounts, + &instruction.data, + invoke_context, + ); + if result.is_ok() { + // Verify the called program has not misbehaved + result = invoke_context.verify_and_update(message, instruction, signers, accounts); + } + invoke_context.pop(); + + result + } + /// Record the initial state of the accounts so that they can be compared /// after the instruction is processed pub fn create_pre_accounts( @@ -252,11 +378,12 @@ impl MessageProcessor { ) -> Vec { let mut pre_accounts = Vec::with_capacity(accounts.len()); { - let program_id = instruction.program_id(&message.account_keys); let mut work = |_unique_index: usize, account_index: usize| { + let key = &message.account_keys[account_index]; + let is_signer = account_index < message.header.num_required_signatures as usize; let is_writable = message.is_writable(account_index); let account = accounts[account_index].borrow(); - pre_accounts.push(PreAccount::new(&account, program_id, is_writable)); + pre_accounts.push(PreAccount::new(key, &account, is_signer, is_writable)); Ok(()) }; let _ = instruction.visit_each_account(&mut work); @@ -266,15 +393,9 @@ impl MessageProcessor { /// Verify there are no outstanding borrows pub fn verify_account_references( - executable_accounts: &[(Pubkey, RefCell)], - accounts: &[Rc>], + accounts: &[(Pubkey, RefCell)], ) -> Result<(), InstructionError> { - for account in accounts.iter() { - account - .try_borrow_mut() - .map_err(|_| InstructionError::AccountBorrowOutstanding)?; - } - for (_, account) in executable_accounts.iter() { + for (_, account) in accounts.iter() { account .try_borrow_mut() .map_err(|_| InstructionError::AccountBorrowOutstanding)?; @@ -291,17 +412,20 @@ impl MessageProcessor { accounts: &[Rc>], rent_collector: &RentCollector, ) -> Result<(), InstructionError> { - // Verify all accounts have zero outstanding refs - Self::verify_account_references(executable_accounts, accounts)?; + // Verify all executable accounts have zero outstanding refs + Self::verify_account_references(executable_accounts)?; // Verify the per-account instruction results let (mut pre_sum, mut post_sum) = (0_u128, 0_u128); { let program_id = instruction.program_id(&message.account_keys); let mut work = |unique_index: usize, account_index: usize| { - let account = accounts[account_index].borrow(); - pre_accounts[unique_index].verify(&program_id, rent_collector, &account)?; - pre_sum += u128::from(pre_accounts[unique_index].lamports); + // Verify account has no outstanding references and take one + let account = accounts[account_index] + .try_borrow_mut() + .map_err(|_| InstructionError::AccountBorrowOutstanding)?; + pre_accounts[unique_index].verify(&program_id, &rent_collector.rent, &account)?; + pre_sum += u128::from(pre_accounts[unique_index].lamports()); post_sum += u128::from(account.lamports); Ok(()) }; @@ -315,6 +439,56 @@ impl MessageProcessor { Ok(()) } + /// Verify the results of a cross-program instruction + fn verify_and_update( + message: &Message, + instruction: &CompiledInstruction, + pre_accounts: &mut [PreAccount], + program_id: &Pubkey, + rent: &Rent, + signers: &[Pubkey], + accounts: &[Rc>], + ) -> Result<(), InstructionError> { + // Verify the per-account instruction results + let (mut pre_sum, mut post_sum) = (0_u128, 0_u128); + let mut work = |_unique_index: usize, account_index: usize| { + let key = &message.account_keys[account_index]; + let account = &accounts[account_index]; + // Find the matching PreAccount + for pre_account in pre_accounts.iter_mut() { + if *key == pre_account.key() { + // Verify account has no outstanding references and take one + let account = account + .try_borrow_mut() + .map_err(|_| InstructionError::AccountBorrowOutstanding)?; + + pre_account.verify_cross_program( + message.is_writable(account_index), + message.is_signer(account_index), + signers, + &program_id, + &rent, + &account, + )?; + pre_sum += u128::from(pre_account.lamports()); + post_sum += u128::from(account.lamports); + + pre_account.update(&account); + + return Ok(()); + } + } + Err(InstructionError::MissingAccount) + }; + instruction.visit_each_account(&mut work)?; + + // Verify that the total sum of all the lamports did not change + if pre_sum != post_sum { + return Err(InstructionError::UnbalancedInstruction); + } + Ok(()) + } + /// Execute an instruction /// This method calls the instruction's program entrypoint method and verifies that the result of /// the call does not violate the bank's accounting rules. @@ -328,11 +502,22 @@ impl MessageProcessor { rent_collector: &RentCollector, ) -> Result<(), InstructionError> { let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); - self.process_instruction(message, instruction, executable_accounts, accounts)?; + let mut invoke_context = ThisInvokeContext::new( + instruction.program_id(&message.account_keys), + rent_collector.rent, + pre_accounts, + ); + self.process_instruction( + message, + instruction, + &mut invoke_context, + executable_accounts, + accounts, + )?; Self::verify( message, instruction, - &pre_accounts, + &invoke_context.pre_accounts, executable_accounts, accounts, rent_collector, @@ -378,6 +563,86 @@ mod tests { native_loader::create_loadable_account, }; + #[test] + fn test_invoke_context() { + const MAX_DEPTH: usize = 10; + let mut program_ids = vec![]; + let mut keys = vec![]; + let mut pre_accounts = vec![]; + let mut accounts = vec![]; + for i in 0..MAX_DEPTH { + program_ids.push(Pubkey::new_rand()); + keys.push(Pubkey::new_rand()); + accounts.push(Rc::new(RefCell::new(Account::new( + i as u64, + 1, + &program_ids[i], + )))); + pre_accounts.push(PreAccount::new( + &keys[i], + &accounts[i].borrow(), + false, + true, + )) + } + let mut invoke_context = + ThisInvokeContext::new(&program_ids[0], Rent::default(), pre_accounts); + + // Check call depth increases and has a limit + let mut depth_reached = 1; + for i in 1..MAX_DEPTH { + if Err(InstructionError::CallDepth) == invoke_context.push(&program_ids[i]) { + break; + } + depth_reached += 1; + } + assert_ne!(depth_reached, 0); + assert!(depth_reached < MAX_DEPTH); + + // Mock each invocation + for owned_index in (1..depth_reached).rev() { + let not_owned_index = owned_index - 1; + let metas = vec![ + AccountMeta::new(keys[not_owned_index], false), + AccountMeta::new(keys[owned_index], false), + ]; + let message = + Message::new(&[Instruction::new(program_ids[owned_index], &[0_u8], metas)]); + + // modify account owned by the program + accounts[owned_index].borrow_mut().data[0] = (MAX_DEPTH + owned_index) as u8; + invoke_context + .verify_and_update( + &message, + &message.instructions[0], + &[], + &accounts[not_owned_index..owned_index + 1], + ) + .unwrap(); + assert_eq!( + invoke_context.pre_accounts[owned_index].data[0], + (MAX_DEPTH + owned_index) as u8 + ); + + // modify account not owned by the program + let data = accounts[not_owned_index].borrow_mut().data[0]; + accounts[not_owned_index].borrow_mut().data[0] = (MAX_DEPTH + not_owned_index) as u8; + assert_eq!( + invoke_context.verify_and_update( + &message, + &message.instructions[0], + &[], + &accounts[not_owned_index..owned_index + 1], + ), + Err(InstructionError::ExternalAccountDataModified) + ); + assert_eq!(invoke_context.pre_accounts[not_owned_index].data[0], data); + accounts[not_owned_index].borrow_mut().data[0] = data; + + invoke_context.pop(); + } + } + #[test] fn test_is_zeroed() { const ZEROS_LEN: usize = 1024; @@ -402,51 +667,44 @@ mod tests { #[test] fn test_verify_account_references() { - let executable_accounts = vec![(Pubkey::new_rand(), RefCell::new(Account::default()))]; - let program_accounts = vec![Rc::new(RefCell::new(Account::default()))]; + let accounts = vec![(Pubkey::new_rand(), RefCell::new(Account::default()))]; - assert!(MessageProcessor::verify_account_references( - &executable_accounts, - &program_accounts, - ) - .is_ok()); + assert!(MessageProcessor::verify_account_references(&accounts).is_ok()); - let cloned = program_accounts[0].clone(); - let _borrowed = cloned.borrow(); + let mut _borrowed = accounts[0].1.borrow(); assert_eq!( - MessageProcessor::verify_account_references(&executable_accounts, &program_accounts,), - Err(InstructionError::AccountBorrowOutstanding) - ); - - let cloned = executable_accounts[0].1.clone(); - let _borrowed = cloned.borrow(); - assert_eq!( - MessageProcessor::verify_account_references(&executable_accounts, &program_accounts,), + MessageProcessor::verify_account_references(&accounts), Err(InstructionError::AccountBorrowOutstanding) ); } - struct Change { - // key: Pubkey, + struct Change<'a> { program_id: Pubkey, - rent_collector: RentCollector, + message_is_writable: bool, + message_is_signer: bool, + signers: &'a [Pubkey], + rent: Rent, pre: PreAccount, post: Account, } - impl Change { + impl<'a> Change<'a> { pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self { Self { // key: Pubkey::new_rand(), program_id: *program_id, - rent_collector: RentCollector::default(), + message_is_writable: false, + message_is_signer: false, + signers: &[], + rent: Rent::default(), pre: PreAccount::new( + &Pubkey::new_rand(), &Account { owner: *owner, lamports: std::u64::MAX, data: vec![], ..Account::default() }, - &Pubkey::new_rand(), + false, true, ), post: Account { @@ -456,10 +714,26 @@ mod tests { }, } } + pub fn new_cross_program(owner: &Pubkey, program_id: &Pubkey, key: &Pubkey) -> Self { + let mut change = Change::new(owner, program_id); + change.pre.key = key.clone(); + change + } pub fn read_only(mut self) -> Self { self.pre.is_writable = false; self } + pub fn writable(mut self, pre: bool, message_is_writable: bool) -> Self { + self.pre.is_writable = pre; + self.message_is_writable = message_is_writable; + self + } + pub fn signer(mut self, pre: bool, message_is_signer: bool, signers: &'a [Pubkey]) -> Self { + self.pre.is_signer = pre; + self.message_is_signer = message_is_signer; + self.signers = signers; + self + } pub fn executable(mut self, pre: bool, post: bool) -> Self { self.pre.is_executable = pre; self.post.executable = post; @@ -475,8 +749,7 @@ mod tests { self } pub fn data(mut self, pre: Vec, post: Vec) -> Self { - self.pre.data_len = pre.len(); - self.pre.data = Some(pre); + self.pre.data = pre; self.post.data = post; self } @@ -486,8 +759,17 @@ mod tests { self } pub fn verify(&self) -> Result<(), InstructionError> { - self.pre - .verify(&self.program_id, &self.rent_collector, &self.post) + self.pre.verify(&self.program_id, &self.rent, &self.post) + } + pub fn verify_cross_program(&self) -> Result<(), InstructionError> { + self.pre.verify_cross_program( + self.message_is_writable, + self.message_is_signer, + self.signers, + &self.program_id, + &self.rent, + &self.post, + ) } } @@ -631,7 +913,7 @@ mod tests { "owner should not be able to subtract lamports once marked executable" ); let data = vec![1; 100]; - let min_lamports = RentCollector::default().rent.minimum_balance(data.len()); + let min_lamports = Rent::default().minimum_balance(data.len()); assert_eq!( Change::new(&owner, &owner) .executable(false, true) @@ -651,6 +933,59 @@ mod tests { ); } + #[test] + fn test_verify_account_changes_writable() { + let owner = Pubkey::new_rand(); + let system_program_id = system_program::id(); + + assert_eq!( + Change::new(&owner, &system_program_id) + .writable(true, false) + .verify_cross_program(), + Ok(()), + "account can we changed to readonly" + ); + + assert_eq!( + Change::new(&owner, &system_program_id) + .writable(false, true) + .verify_cross_program(), + Err(InstructionError::WritableModified), + "account cannot be changed to writable" + ); + } + + #[test] + fn test_verify_account_changes_signer() { + let owner = Pubkey::new_rand(); + let system_program_id = system_program::id(); + let key = Pubkey::new_rand(); + + assert_eq!( + Change::new_cross_program(&owner, &system_program_id, &key) + .signer(false, true, &[key.clone()]) + .verify_cross_program(), + Ok(()), + "account signed by a signer" + ); + + assert_eq!( + Change::new_cross_program(&owner, &system_program_id, &key) + .signer(false, true, &[]) + .verify_cross_program(), + Err(InstructionError::SignerModified), + "account cannot be changed to signed if no signer" + ); + + assert_eq!( + Change::new_cross_program(&owner, &system_program_id, &key) + .signer(false, true, &[Pubkey::new_rand(), Pubkey::new_rand()]) + .verify_cross_program(), + Err(InstructionError::SignerModified), + "account cannot be changed to signed if no signer exists" + ); + } + #[test] fn test_verify_account_changes_data_len() { let alice_program_id = Pubkey::new_rand(); @@ -666,8 +1001,8 @@ mod tests { Change::new(&alice_program_id, &system_program::id()) .data(vec![0], vec![0,0]) .verify(), - Err(InstructionError::AccountDataSizeChanged), - "system program should not be able to change the data length of accounts it does not own" + Err(InstructionError::AccountDataSizeChanged), + "system program should not be able to change the data length of accounts it does not own" ); } @@ -730,9 +1065,9 @@ mod tests { .lamports(42, 1) .data(vec![42], vec![0]) .verify(), - Ok(()), - "alice should be able to deduct lamports and give the account to bob if the data is zeroed", - ); + Ok(()), + "alice should be able to deduct lamports and give the account to bob if the data is zeroed", + ); } #[test] @@ -1023,4 +1358,125 @@ mod tests { assert_eq!(accounts[1].borrow().lamports, 20); assert_eq!(accounts[0].borrow().data, vec![42]); } + + #[test] + fn test_process_cross_program() { + #[derive(Serialize, Deserialize)] + enum MockInstruction { + NoopSuccess, + NoopFail, + ModifyOwned, + ModifyNotOwned, + } + + fn mock_process_instruction( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + data: &[u8], + _invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + assert_eq!(*program_id, keyed_accounts[0].owner()?); + assert_eq!( + keyed_accounts[1].owner()?, + *keyed_accounts[0].unsigned_key() + ); + assert_ne!( + keyed_accounts[2].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[1].try_account_ref_mut()?.data[0] = 1 + } + MockInstruction::ModifyNotOwned => { + keyed_accounts[2].try_account_ref_mut()?.data[0] = 1 + } + } + } else { + return Err(InstructionError::InvalidInstructionData); + } + Ok(()) + } + + let caller_program_id = Pubkey::new_rand(); + let callee_program_id = Pubkey::new_rand(); + let mut program_account = Account::new(1, 0, &Pubkey::new_rand()); + program_account.executable = true; + let executable_accounts = vec![(callee_program_id, RefCell::new(program_account))]; + + let owned_key = Pubkey::new_rand(); + let owned_account = Account::new(42, 1, &callee_program_id); + let owned_preaccount = PreAccount::new(&owned_key, &owned_account, false, true); + + let not_owned_key = Pubkey::new_rand(); + let not_owned_account = Account::new(84, 1, &Pubkey::new_rand()); + let not_owned_preaccount = PreAccount::new(¬_owned_key, ¬_owned_account, false, true); + + let mut accounts = vec![ + Rc::new(RefCell::new(owned_account)), + Rc::new(RefCell::new(not_owned_account)), + ]; + let mut invoke_context = ThisInvokeContext::new( + &caller_program_id, + Rent::default(), + vec![owned_preaccount, not_owned_preaccount], + ); + let metas = vec![ + AccountMeta::new(owned_key, false), + AccountMeta::new(not_owned_key, false), + ]; + + // not owned account modified by the caller (before the invoke) + accounts[0].borrow_mut().data[0] = 1; + let message = Message::new(&[Instruction::new( + callee_program_id, + &MockInstruction::NoopSuccess, + metas.clone(), + )]); + assert_eq!( + MessageProcessor::process_cross_program_instruction( + &message, + &executable_accounts, + &accounts, + &[], + mock_process_instruction, + &mut invoke_context, + ), + Err(InstructionError::ExternalAccountDataModified) + ); + accounts[0].borrow_mut().data[0] = 0; + + let cases = vec![ + (MockInstruction::NoopSuccess, Ok(())), + ( + MockInstruction::NoopFail, + Err(InstructionError::GenericError), + ), + (MockInstruction::ModifyOwned, Ok(())), + ( + MockInstruction::ModifyNotOwned, + Err(InstructionError::ExternalAccountDataModified), + ), + ]; + + for case in cases { + let message = + Message::new(&[Instruction::new(callee_program_id, &case.0, metas.clone())]); + assert_eq!( + MessageProcessor::process_cross_program_instruction( + &message, + &executable_accounts, + &accounts, + &[], + mock_process_instruction, + &mut invoke_context, + ), + case.1 + ); + } + } } diff --git a/runtime/src/native_loader.rs b/runtime/src/native_loader.rs index 0c8c7dc352..3d0711f265 100644 --- a/runtime/src/native_loader.rs +++ b/runtime/src/native_loader.rs @@ -7,7 +7,7 @@ use log::*; use num_derive::{FromPrimitive, ToPrimitive}; use solana_sdk::{ account::KeyedAccount, - entrypoint_native::{LoaderEntrypoint, ProgramEntrypoint}, + entrypoint_native::{InvokeContext, LoaderEntrypoint, ProgramEntrypoint}, instruction::InstructionError, program_utils::{next_keyed_account, DecodeError}, pubkey::Pubkey, @@ -126,6 +126,7 @@ impl NativeLoader { _program_id: &Pubkey, keyed_accounts: &[KeyedAccount], instruction_data: &[u8], + invoke_context: &dyn InvokeContext, ) -> Result<(), InstructionError> { let mut keyed_accounts_iter = keyed_accounts.iter(); let program = next_keyed_account(&mut keyed_accounts_iter)?; @@ -142,7 +143,14 @@ impl NativeLoader { if name.ends_with("loader_program") { let entrypoint = Self::get_entrypoint::(name, &self.loader_symbol_cache)?; - unsafe { entrypoint(program.unsigned_key(), params, instruction_data) } + unsafe { + entrypoint( + program.unsigned_key(), + params, + instruction_data, + invoke_context, + ) + } } else { let entrypoint = Self::get_entrypoint::(name, &self.program_symbol_cache)?; diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index bb1702c00c..bcf9f0f02c 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -253,7 +253,7 @@ if (!(expr)) { \ */ typedef struct { SolAccountInfo* ka; /** Pointer to an array of SolAccountInfo, must already - point to an array of SolAccountInfos */ + point to an array of SolAccountInfos */ uint64_t ka_num; /** Number of SolAccountInfo entries in `ka` */ const uint8_t *data; /** pointer to the instruction data */ uint64_t data_len; /** Length in bytes of the instruction data */ @@ -364,6 +364,99 @@ SOL_FN_PREFIX bool sol_deserialize( return true; } +/** + * Account Meta + */ +typedef struct { + SolPubkey *pubkey; /** An account's public key */ + bool is_writable; /** True if the `pubkey` can be loaded as a read-write account */ + bool is_signer; /** True if an Instruction requires a Transaction signature matching `pubkey` */ +} SolAccountMeta; + +/** + * Instruction + */ +typedef struct { + SolPubkey *program_id; /** Pubkey of the instruction processor that executes this instruction */ + SolAccountMeta *accounts; /** Metadata for what accounts should be passed to the instruction processor */ + uint64_t account_len; /** Number of SolAccountMetas */ + uint8_t *data; /** Opaque data passed to the instruction processor */ + uint64_t data_len; /** Length of the data in bytes */ +} SolInstruction; + +/** + * Seed used to create a program address + */ +typedef struct { + const char *addr; /** Seed string */ + uint64_t len; /** Length of the seed string */ +} SolSignerSeed; + +/** + * Seeds used by a signer to create a program address + */ +typedef struct { + const SolSignerSeed *addr; /** An arry of a signer's seeds */ + uint64_t len; /** Number of seeds */ +} SolSignerSeeds; + +/** + * Cross-program invocation + * * @{ + */ + +/* + * @param instruction Instruction to process + * @param account_infos Accounts used by instruction + * @param account_infos_len Length of account_infos array + * @param seeds Seed strings used to sign program accounts + * @param seeds_len Length of the seeds array + */ +SOL_FN_PREFIX uint64_t sol_invoke_signed( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len, + const SolSignerSeeds *signers_seeds, + int signers_seeds_len +) { + uint64_t sol_invoke_signed_c( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len, + const SolSignerSeeds *signers_seeds, + int signers_seeds_len + ); + + return sol_invoke_signed_c( + instruction, + account_infos, + account_infos_len, + signers_seeds, + signers_seeds_len + ); +} +/* +* @param instruction Instruction to process +* @param account_infos Accounts used by instruction +* @param account_infos_len Length of account_infos array +*/ +SOL_FN_PREFIX uint64_t sol_invoke( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len +) { + const SolSignerSeeds signers_seeds[] = {{}}; + return sol_invoke_signed( + instruction, + account_infos, + account_infos_len, + signers_seeds, + 0 + ); +} + +/**@}*/ + /** * Debugging utilities * @{ diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index 5f61f8c1d3..5b2e872dbc 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -1,6 +1,10 @@ //! @brief Solana Native program entry point -use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey}; +use crate::{ + account::Account, account::KeyedAccount, instruction::CompiledInstruction, + instruction::InstructionError, message::Message, pubkey::Pubkey, +}; +use std::{cell::RefCell, rc::Rc}; // Prototype of a native program entry point /// @@ -23,6 +27,7 @@ pub type LoaderEntrypoint = unsafe extern "C" fn( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], instruction_data: &[u8], + invoke_context: &dyn InvokeContext, ) -> Result<(), InstructionError>; /// Convenience macro to declare a native program @@ -134,8 +139,22 @@ macro_rules! declare_loader( program_id: &$crate::pubkey::Pubkey, keyed_accounts: &[$crate::account::KeyedAccount], instruction_data: &[u8], + invoke_context: &mut dyn $crate::entrypoint_native::InvokeContext, ) -> Result<(), $crate::instruction::InstructionError> { - $entrypoint(program_id, keyed_accounts, instruction_data) + $entrypoint(program_id, keyed_accounts, instruction_data, invoke_context) } ) ); + +/// Cross-program invocation context passed to loaders +pub trait InvokeContext { + fn push(&mut self, key: &Pubkey) -> Result<(), InstructionError>; + fn pop(&mut self); + fn verify_and_update( + &mut self, + message: &Message, + instruction: &CompiledInstruction, + signers: &[Pubkey], + accounts: &[Rc>], + ) -> Result<(), InstructionError>; +} diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index ea3862f133..ab7cc077ec 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -87,7 +87,7 @@ pub enum InstructionError { RentEpochModified, /// The instruction expected additional account keys - #[error("insufficient account key count for instruction")] + #[error("insufficient account keys for instruction")] NotEnoughAccountKeys, /// A non-system program changed the size of the account data @@ -138,6 +138,26 @@ pub enum InstructionError { /// Unsupported program id #[error("Unsupported program id")] UnsupportedProgramId, + + /// Writable bit on account info changed, but shouldn't have + #[error("Writable bit on account info changed, but shouldn't have")] + WritableModified, + + /// Signer bit on account info changed, but shouldn't have + #[error("Signer bit on account info changed, but shouldn't have")] + SignerModified, + + /// Cross-program invocation call depth too deep + #[error("Cross-program invocation call depth too deep")] + CallDepth, + + /// An account required by the instruction is missing + #[error("An account required by the instruction is missing")] + MissingAccount, + + /// Cross-program invocation reentrancy not allowed for this instruction + #[error("Cross-program invocation reentrancy not allowed for this instruction")] + ReentrancyNotAllowed, } impl InstructionError { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 12bbdd1dcc..a62a57ae63 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -61,6 +61,7 @@ pub use solana_sdk_macro::declare_id; pub mod account_info; pub mod entrypoint; pub mod log; +pub mod program; pub mod program_error; // Modules not usable by on-chain programs diff --git a/sdk/src/program.rs b/sdk/src/program.rs new file mode 100644 index 0000000000..57ba73eaee --- /dev/null +++ b/sdk/src/program.rs @@ -0,0 +1,42 @@ +#![cfg(feature = "program")] + +use crate::{ + account_info::AccountInfo, entrypoint::ProgramResult, entrypoint::SUCCESS, + instruction::Instruction, +}; + +/// Invoke a cross-program instruction +pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { + invoke_signed(instruction, account_infos, &[]) +} + +/// Invoke a cross-program instruction with program signatures +pub fn invoke_signed( + instruction: &Instruction, + account_infos: &[AccountInfo], + signers_seeds: &[&[&str]], +) -> ProgramResult { + let result = unsafe { + sol_invoke_signed_rust( + instruction as *const _ as *const u8, + account_infos as *const _ as *const u8, + account_infos.len() as u64, + signers_seeds as *const _ as *const u8, + signers_seeds.len() as u64, + ) + }; + match result { + SUCCESS => Ok(()), + _ => Err(result.into()), + } +} + +extern "C" { + fn sol_invoke_signed_rust( + instruction_addr: *const u8, + account_infos_addr: *const u8, + account_infos_len: u64, + signers_seeds_addr: *const u8, + signers_seeds_len: u64, + ) -> u64; +}