Add BPF support & C-based BPF tic-tac-toe (#1422)
Add initial support for BPF and a C port of tictactoe
This commit is contained in:
		
							
								
								
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -66,12 +66,13 @@ path = "src/bin/wallet.rs"
 | 
			
		||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
unstable = []
 | 
			
		||||
ipv6 = []
 | 
			
		||||
bpf_c = []
 | 
			
		||||
chacha = []
 | 
			
		||||
cuda = []
 | 
			
		||||
erasure = []
 | 
			
		||||
ipv6 = []
 | 
			
		||||
test = []
 | 
			
		||||
chacha = []
 | 
			
		||||
unstable = []
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
atty = "0.2"
 | 
			
		||||
@@ -82,6 +83,7 @@ bytes = "0.4"
 | 
			
		||||
chrono = { version = "0.4.0", features = ["serde"] }
 | 
			
		||||
clap = "2.31"
 | 
			
		||||
dirs = "1.0.2"
 | 
			
		||||
elf = "0.0.10"
 | 
			
		||||
env_logger = "0.5.12"
 | 
			
		||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
 | 
			
		||||
getopts = "0.2"
 | 
			
		||||
@@ -98,6 +100,7 @@ matches = "0.1.6"
 | 
			
		||||
nix = "0.11.0"
 | 
			
		||||
pnet_datalink = "0.21.0"
 | 
			
		||||
rand = "0.5.1"
 | 
			
		||||
rbpf = { git = "https://github.com/solana-labs/rbpf" }
 | 
			
		||||
rayon = "1.0.0"
 | 
			
		||||
reqwest = "0.9.0"
 | 
			
		||||
ring = "0.13.2"
 | 
			
		||||
@@ -114,9 +117,8 @@ tokio-codec = "0.1"
 | 
			
		||||
untrusted = "0.6.2"
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
noop = { path = "programs/noop" }
 | 
			
		||||
print = { path = "programs/print" }
 | 
			
		||||
move_funds = { path = "programs/move_funds" }
 | 
			
		||||
move_funds = { path = "programs/native/move_funds" }
 | 
			
		||||
noop = { path = "programs/native/noop" }
 | 
			
		||||
 | 
			
		||||
[[bench]]
 | 
			
		||||
name = "bank"
 | 
			
		||||
@@ -141,8 +143,7 @@ name = "chacha"
 | 
			
		||||
members = [
 | 
			
		||||
    ".",
 | 
			
		||||
    "common",
 | 
			
		||||
    "programs/noop",
 | 
			
		||||
    "programs/print",
 | 
			
		||||
    "programs/move_funds",
 | 
			
		||||
    "programs/native/move_funds",
 | 
			
		||||
    "programs/native/noop",
 | 
			
		||||
    "programs/bpf/noop_rust",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								build.rs
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    println!("cargo:rerun-if-changed=target/perf-libs");
 | 
			
		||||
@@ -14,11 +15,33 @@ fn main() {
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let bpf_c = !env::var("CARGO_FEATURE_BPF_C").is_err();
 | 
			
		||||
    let chacha = !env::var("CARGO_FEATURE_CHACHA").is_err();
 | 
			
		||||
    let cuda = !env::var("CARGO_FEATURE_CUDA").is_err();
 | 
			
		||||
    let erasure = !env::var("CARGO_FEATURE_ERASURE").is_err();
 | 
			
		||||
    let chacha = !env::var("CARGO_FEATURE_CHACHA").is_err();
 | 
			
		||||
 | 
			
		||||
    if cuda || erasure || chacha {
 | 
			
		||||
    if bpf_c {
 | 
			
		||||
        let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap();
 | 
			
		||||
 | 
			
		||||
        println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/build.sh");
 | 
			
		||||
        println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/src/move_funds.c");
 | 
			
		||||
        println!("cargo:warning=(not a warning) Compiling move_funds_c");
 | 
			
		||||
        let status = Command::new("programs/bpf/move_funds_c/build.sh")
 | 
			
		||||
            .arg(&out_dir)
 | 
			
		||||
            .status()
 | 
			
		||||
            .expect("Failed to call move_funds_c build script");
 | 
			
		||||
        assert!(status.success());
 | 
			
		||||
 | 
			
		||||
        println!("cargo:rerun-if-changed=programs/bpf/tictactoe_c/build.sh");
 | 
			
		||||
        println!("cargo:rerun-if-changed=programs/bpf/tictactoe_c/src/tictactoe.c");
 | 
			
		||||
        println!("cargo:warning=(not a warning) Compiling tictactoe_c");
 | 
			
		||||
        let status = Command::new("programs/bpf/tictactoe_c/build.sh")
 | 
			
		||||
            .arg(&out_dir)
 | 
			
		||||
            .status()
 | 
			
		||||
            .expect("Failed to call tictactoe_c build script");
 | 
			
		||||
        assert!(status.success());
 | 
			
		||||
    }
 | 
			
		||||
    if chacha || cuda || erasure {
 | 
			
		||||
        println!("cargo:rustc-link-search=native=target/perf-libs");
 | 
			
		||||
    }
 | 
			
		||||
    if cuda {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
use pubkey::Pubkey;
 | 
			
		||||
 | 
			
		||||
/// An Account with userdata that is stored on chain
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
 | 
			
		||||
pub struct Account {
 | 
			
		||||
    /// tokens in the account
 | 
			
		||||
@@ -22,6 +23,7 @@ impl Account {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct KeyedAccount<'a> {
 | 
			
		||||
    pub key: &'a Pubkey,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ use generic_array::typenum::U32;
 | 
			
		||||
use generic_array::GenericArray;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
 | 
			
		||||
pub struct Pubkey(GenericArray<u8, U32>);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								programs/bpf/move_funds_c/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								programs/bpf/move_funds_c/build.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#!/bin/bash -ex
 | 
			
		||||
 | 
			
		||||
OUTDIR="${1:-../../../target/release/}"
 | 
			
		||||
THISDIR=$(dirname "$0")
 | 
			
		||||
mkdir -p "$OUTDIR"
 | 
			
		||||
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/move_funds_c.bc -c "$THISDIR"/src/move_funds.c
 | 
			
		||||
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/move_funds_c.o "$OUTDIR"/move_funds_c.bc
 | 
			
		||||
 | 
			
		||||
#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/move_funds_c.o
 | 
			
		||||
							
								
								
									
										3
									
								
								programs/bpf/move_funds_c/dump.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								programs/bpf/move_funds_c/dump.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/move_funds_c.o
 | 
			
		||||
							
								
								
									
										130
									
								
								programs/bpf/move_funds_c/src/move_funds.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								programs/bpf/move_funds_c/src/move_funds.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
 | 
			
		||||
//#include <stdint.h>
 | 
			
		||||
//#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
#if 1
 | 
			
		||||
// one way to define a helper function is with index as a fixed value
 | 
			
		||||
#define BPF_TRACE_PRINTK_IDX 6
 | 
			
		||||
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
 | 
			
		||||
#else
 | 
			
		||||
// relocation is another option
 | 
			
		||||
extern int sol_print(int, int, int, int, int);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef long long unsigned int uint64_t;
 | 
			
		||||
typedef long long int int64_t;
 | 
			
		||||
typedef unsigned char uint8_t;
 | 
			
		||||
 | 
			
		||||
typedef enum { false = 0, true } bool;
 | 
			
		||||
 | 
			
		||||
#define SIZE_PUBKEY 32
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t x[SIZE_PUBKEY];
 | 
			
		||||
} SolPubkey;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    SolPubkey *key;
 | 
			
		||||
    int64_t tokens;
 | 
			
		||||
    uint64_t userdata_len;
 | 
			
		||||
    uint8_t *userdata;
 | 
			
		||||
    SolPubkey *program_id;
 | 
			
		||||
} SolKeyedAccounts;
 | 
			
		||||
 | 
			
		||||
// TODO support BPF function calls rather then forcing everything to be inlined
 | 
			
		||||
#define SOL_FN_PREFIX __attribute__((always_inline)) static
 | 
			
		||||
 | 
			
		||||
// TODO move this to a registered helper
 | 
			
		||||
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
 | 
			
		||||
    for (int i = 0; i < len; i++) {
 | 
			
		||||
        *((uint8_t *)dst + i) = *((uint8_t *)src + i);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define sol_panic() _sol_panic(__LINE__)
 | 
			
		||||
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
 | 
			
		||||
    sol_print(0, 0, 0xFF, 0xFF, line);
 | 
			
		||||
    char *pv = (char *)1;
 | 
			
		||||
    *pv = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
 | 
			
		||||
                              uint8_t **userdata, uint64_t *userdata_len) {
 | 
			
		||||
    if (num_ka != *(uint64_t *)src) {
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
    src += sizeof(uint64_t);
 | 
			
		||||
 | 
			
		||||
    // TODO fixed iteration loops ok? unrolled?
 | 
			
		||||
    for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
 | 
			
		||||
        // key
 | 
			
		||||
        ka[i].key = (SolPubkey *)src;
 | 
			
		||||
        src += SIZE_PUBKEY;
 | 
			
		||||
 | 
			
		||||
        // tokens
 | 
			
		||||
        ka[i].tokens = *(uint64_t *)src;
 | 
			
		||||
        src += sizeof(uint64_t);
 | 
			
		||||
 | 
			
		||||
        // account userdata
 | 
			
		||||
        ka[i].userdata_len = *(uint64_t *)src;
 | 
			
		||||
        src += sizeof(uint64_t);
 | 
			
		||||
        ka[i].userdata = src;
 | 
			
		||||
        src += ka[i].userdata_len;
 | 
			
		||||
 | 
			
		||||
        // program_id
 | 
			
		||||
        ka[i].program_id = (SolPubkey *)src;
 | 
			
		||||
        src += SIZE_PUBKEY;
 | 
			
		||||
    }
 | 
			
		||||
    // tx userdata
 | 
			
		||||
    *userdata_len = *(uint64_t *)src;
 | 
			
		||||
    src += sizeof(uint64_t);
 | 
			
		||||
    *userdata = src;
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// -- Debug --
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX void print_key(SolPubkey *key) {
 | 
			
		||||
    for (int j = 0; j < SIZE_PUBKEY; j++) {
 | 
			
		||||
        sol_print(0, 0, 0, j, key->x[j]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
 | 
			
		||||
    for (int j = 0; j < len; j++) {
 | 
			
		||||
        sol_print(0, 0, 0, j, data[j]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
 | 
			
		||||
                                uint8_t *userdata, uint64_t userdata_len) {
 | 
			
		||||
    sol_print(0, 0, 0, 0, num_ka);
 | 
			
		||||
    for (int i = 0; i < num_ka; i++) {
 | 
			
		||||
        // key
 | 
			
		||||
        print_key(ka[i].key);
 | 
			
		||||
 | 
			
		||||
        // tokens
 | 
			
		||||
        sol_print(0, 0, 0, 0, ka[i].tokens);
 | 
			
		||||
 | 
			
		||||
        // account userdata
 | 
			
		||||
        print_userdata(ka[i].userdata, ka[i].userdata_len);
 | 
			
		||||
 | 
			
		||||
        // program_id
 | 
			
		||||
        print_key(ka[i].program_id);
 | 
			
		||||
    }
 | 
			
		||||
    // tx userdata
 | 
			
		||||
    print_userdata(userdata, userdata_len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void entrypoint(char *buf) {
 | 
			
		||||
    SolKeyedAccounts ka[3];
 | 
			
		||||
    uint64_t userdata_len;
 | 
			
		||||
    uint8_t *userdata;
 | 
			
		||||
 | 
			
		||||
    if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    print_params(3, ka, userdata, userdata_len);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								programs/bpf/noop_rust/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								programs/bpf/noop_rust/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "noop_rust"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
authors = ["Jack May <jack@solana.com>"]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
rbpf = { git = "https://github.com/solana-labs/rbpf" }
 | 
			
		||||
solana = { path = "../../.." }
 | 
			
		||||
							
								
								
									
										10
									
								
								programs/bpf/noop_rust/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								programs/bpf/noop_rust/build.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
#!/bin/bash -ex
 | 
			
		||||
 | 
			
		||||
# TODO building release flavor with rust produces a bunch of output .bc files
 | 
			
		||||
INTERDIR=../../../target/release
 | 
			
		||||
OUTDIR="${1:-../../../target/debug/}"
 | 
			
		||||
mkdir -p "$OUTDIR"
 | 
			
		||||
# cargo +nightly rustc --release -- -C panic=abort --emit=llvm-ir
 | 
			
		||||
cargo +nightly rustc --release -- -C panic=abort --emit=llvm-bc
 | 
			
		||||
cp "$INTERDIR"/deps/noop_rust-*.bc "$OUTDIR"/noop_rust.bc 
 | 
			
		||||
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -o "$OUTDIR"/noop_rust.o "$OUTDIR"/noop_rust.bc
 | 
			
		||||
							
								
								
									
										3
									
								
								programs/bpf/noop_rust/dump.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								programs/bpf/noop_rust/dump.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble target/release/noop_rust.o
 | 
			
		||||
							
								
								
									
										15
									
								
								programs/bpf/noop_rust/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								programs/bpf/noop_rust/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
extern crate rbpf;
 | 
			
		||||
 | 
			
		||||
use std::mem::transmute;
 | 
			
		||||
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
#[link_section = ".text,entrypoint"] // TODO platform independent needed
 | 
			
		||||
pub extern "C" fn entrypoint(_raw: *mut u8) {
 | 
			
		||||
    let bpf_func_trace_printk = unsafe {
 | 
			
		||||
        transmute::<u64, extern "C" fn(u64, u64, u64, u64, u64)>(
 | 
			
		||||
            rbpf::helpers::BPF_TRACE_PRINTK_IDX as u64,
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    bpf_func_trace_printk(0, 0, 1, 2, 3);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								programs/bpf/tictactoe_c/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								programs/bpf/tictactoe_c/build.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#!/bin/bash -ex
 | 
			
		||||
 | 
			
		||||
OUTDIR="${1:-../../../target/release/}"
 | 
			
		||||
THISDIR=$(dirname "$0")
 | 
			
		||||
mkdir -p "$OUTDIR"
 | 
			
		||||
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/tictactoe_c.bc -c "$THISDIR"/src/tictactoe.c
 | 
			
		||||
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/tictactoe_c.o "$OUTDIR"/tictactoe_c.bc
 | 
			
		||||
 | 
			
		||||
# /usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/tictactoe_c.o
 | 
			
		||||
							
								
								
									
										3
									
								
								programs/bpf/tictactoe_c/dump.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								programs/bpf/tictactoe_c/dump.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/tictactoe_c.o
 | 
			
		||||
							
								
								
									
										57
									
								
								programs/bpf/tictactoe_c/makefile
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										57
									
								
								programs/bpf/tictactoe_c/makefile
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
# Notes:
 | 
			
		||||
 | 
			
		||||
#  TODO needs more investigation into what r10 is used for
 | 
			
		||||
#    -O2 adds changes the generated code from:
 | 
			
		||||
#      main:
 | 
			
		||||
#       0:	b7 01 00 00 00 00 00 00 	r1 = 0
 | 
			
		||||
#       1:	63 1a fc ff 00 00 00 00 	*(u32 *)(r10 - 4) = r1
 | 
			
		||||
#       2:	bf 10 00 00 00 00 00 00 	r0 = r1
 | 
			
		||||
#       3:	95 00 00 00 00 00 00 00 	exit
 | 
			
		||||
#    To:
 | 
			
		||||
#      main:
 | 
			
		||||
#       0:	b7 00 00 00 00 00 00 00 	r0 = 0
 | 
			
		||||
#       1:	95 00 00 00 00 00 00 00 	exit
 | 
			
		||||
 | 
			
		||||
# - building bpf module that includes stdint.h with clang only (no w/ llc) results in an error
 | 
			
		||||
#  $(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
 | 
			
		||||
#  => n file included from /usr/include/sys/_types/_intptr_t.h:30:
 | 
			
		||||
#  => /usr/include/machine/types.h:37:2: error: architecture not supported
 | 
			
		||||
#  => error architecture not supported
 | 
			
		||||
 | 
			
		||||
TOOLS_DIR = /usr/local/opt/llvm/bin
 | 
			
		||||
 | 
			
		||||
test_x86.o: test.c
 | 
			
		||||
	$(TOOLS_DIR)/clang -O2 -fpie -target x86_64 -o $@ -c $<
 | 
			
		||||
 | 
			
		||||
test_x86.so: test_x86.o
 | 
			
		||||
	$(TOOLS_DIR)/ld.lld --shared -o $@ $<
 | 
			
		||||
 | 
			
		||||
test_x86_dylib.o: test.c
 | 
			
		||||
	$(TOOLS_DIR)/clang -O2 -fpie -target x86_64-apple-darwin13.0.0 -o $@ -c $<
 | 
			
		||||
 | 
			
		||||
test_x86.dylib: test_x86_dylib.o
 | 
			
		||||
	/usr/local/opt/llvm/bin/ld64.lld -dylib -lc -arch x86_64 -o $@ $<
 | 
			
		||||
 | 
			
		||||
# TODO does not work if pulling in stdlib, claims unsupported architecture
 | 
			
		||||
bpf_clang.o: bpf.c
 | 
			
		||||
	$(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
 | 
			
		||||
 | 
			
		||||
bpf.o: bpf.c
 | 
			
		||||
	$(TOOLS_DIR)/clang -O2 -emit-llvm -c $< -o - | $(TOOLS_DIR)/llc -march=bpf -filetype=obj -o $@
 | 
			
		||||
 | 
			
		||||
bpf_rust.o: bpf_rust.rs
 | 
			
		||||
	rustc +nightly -C opt-level=2 -C panic=abort --emit llvm-bc $< 
 | 
			
		||||
	$(TOOLS_DIR)/llc -march=bpf -filetype=obj -function-sections -o $@ bpf_rust.bc
 | 
			
		||||
 | 
			
		||||
dumpall:
 | 
			
		||||
	$(TOOLS_DIR)/llvm-objdump -color -source -disassemble *.o
 | 
			
		||||
 | 
			
		||||
cleanall:
 | 
			
		||||
	rm -f *.o
 | 
			
		||||
	rm -f *.so
 | 
			
		||||
	rm -f *.dylib
 | 
			
		||||
	rm -f *.ll
 | 
			
		||||
	rm -rf *.bc
 | 
			
		||||
 | 
			
		||||
all:  bpf_clang.o bpf.o
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										370
									
								
								programs/bpf/tictactoe_c/src/tictactoe.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								programs/bpf/tictactoe_c/src/tictactoe.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,370 @@
 | 
			
		||||
 | 
			
		||||
//#include <stdint.h>
 | 
			
		||||
//#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
#if 1
 | 
			
		||||
#define BPF_TRACE_PRINTK_IDX 6
 | 
			
		||||
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
 | 
			
		||||
#else
 | 
			
		||||
// relocation is another option
 | 
			
		||||
extern int sol_print(int, int, int, int, int);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef long long unsigned int uint64_t;
 | 
			
		||||
typedef long long int int64_t;
 | 
			
		||||
typedef unsigned char uint8_t;
 | 
			
		||||
 | 
			
		||||
typedef enum { false = 0, true } bool;
 | 
			
		||||
 | 
			
		||||
#define SIZE_PUBKEY 32
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t x[SIZE_PUBKEY];
 | 
			
		||||
} SolPubkey;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    SolPubkey *key;
 | 
			
		||||
    int64_t tokens;
 | 
			
		||||
    uint64_t userdata_len;
 | 
			
		||||
    uint8_t *userdata;
 | 
			
		||||
    SolPubkey *program_id;
 | 
			
		||||
} SolKeyedAccounts;
 | 
			
		||||
 | 
			
		||||
// TODO support BPF function calls rather then forcing everything to be inlined
 | 
			
		||||
#define SOL_FN_PREFIX __attribute__((always_inline)) static
 | 
			
		||||
 | 
			
		||||
// TODO move this to a registered helper
 | 
			
		||||
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
 | 
			
		||||
    for (int i = 0; i < len; i++) {
 | 
			
		||||
        *((uint8_t *)dst + i) = *((uint8_t *)src + i);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define sol_panic() _sol_panic(__LINE__)
 | 
			
		||||
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
 | 
			
		||||
    sol_print(0, 0, 0xFF, 0xFF, line);
 | 
			
		||||
    char *pv = (char *)1;
 | 
			
		||||
    *pv = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
 | 
			
		||||
                              uint8_t **userdata, uint64_t *userdata_len) {
 | 
			
		||||
    if (num_ka != *(uint64_t *)src) {
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
    src += sizeof(uint64_t);
 | 
			
		||||
 | 
			
		||||
    // TODO fixed iteration loops ok? unrolled?
 | 
			
		||||
    for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
 | 
			
		||||
        // key
 | 
			
		||||
        ka[i].key = (SolPubkey *)src;
 | 
			
		||||
        src += SIZE_PUBKEY;
 | 
			
		||||
 | 
			
		||||
        // tokens
 | 
			
		||||
        ka[i].tokens = *(uint64_t *)src;
 | 
			
		||||
        src += sizeof(uint64_t);
 | 
			
		||||
 | 
			
		||||
        // account userdata
 | 
			
		||||
        ka[i].userdata_len = *(uint64_t *)src;
 | 
			
		||||
        src += sizeof(uint64_t);
 | 
			
		||||
        ka[i].userdata = src;
 | 
			
		||||
        src += ka[i].userdata_len;
 | 
			
		||||
 | 
			
		||||
        // program_id
 | 
			
		||||
        ka[i].program_id = (SolPubkey *)src;
 | 
			
		||||
        src += SIZE_PUBKEY;
 | 
			
		||||
    }
 | 
			
		||||
    // tx userdata
 | 
			
		||||
    *userdata_len = *(uint64_t *)src;
 | 
			
		||||
    src += sizeof(uint64_t);
 | 
			
		||||
    *userdata = src;
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -- TicTacToe --
 | 
			
		||||
 | 
			
		||||
//  Board Coodinates
 | 
			
		||||
// | 0,0 | 1,0 | 2,0 |
 | 
			
		||||
// | 0,1 | 1,1 | 2,1 |
 | 
			
		||||
// | 0,2 | 1,2 | 2,2 |
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    Result_Ok,
 | 
			
		||||
    Result_Panic,
 | 
			
		||||
    Result_GameInProgress,
 | 
			
		||||
    Result_InvalidArguments,
 | 
			
		||||
    Result_InvalidMove,
 | 
			
		||||
    Result_InvalidUserdata,
 | 
			
		||||
    Result_InvalidTimestamp,
 | 
			
		||||
    Result_NoGame,
 | 
			
		||||
    Result_NotYourTurn,
 | 
			
		||||
    Result_PlayerNotFound,
 | 
			
		||||
    Result_UserdataTooSmall,
 | 
			
		||||
} Result;
 | 
			
		||||
 | 
			
		||||
typedef enum { BoardItem_F, BoardItem_X, BoardItem_O } BoardItem;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    State_Waiting,
 | 
			
		||||
    State_XMove,
 | 
			
		||||
    State_OMove,
 | 
			
		||||
    State_XWon,
 | 
			
		||||
    State_OWon,
 | 
			
		||||
    State_Draw,
 | 
			
		||||
} State;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    SolPubkey player_x;
 | 
			
		||||
    SolPubkey player_o;
 | 
			
		||||
    State state;
 | 
			
		||||
    BoardItem board[9];
 | 
			
		||||
    int64_t keep_alive[2];
 | 
			
		||||
} Game;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    Command_Init = 0,
 | 
			
		||||
    Command_Join,
 | 
			
		||||
    Command_KeepAlive,
 | 
			
		||||
    Command_Move,
 | 
			
		||||
} Command;
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX void game_dump_board(Game *self) {
 | 
			
		||||
    sol_print(0, 0, 0x9, 0x9, 0x9);
 | 
			
		||||
    sol_print(0, 0, self->board[0], self->board[1], self->board[2]);
 | 
			
		||||
    sol_print(0, 0, self->board[3], self->board[4], self->board[5]);
 | 
			
		||||
    sol_print(0, 0, self->board[6], self->board[7], self->board[8]);
 | 
			
		||||
    sol_print(0, 0, 0x9, 0x9, 0x9);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX void game_create(Game *self, SolPubkey *player_x) {
 | 
			
		||||
    sol_memcpy(self->player_x.x, player_x, SIZE_PUBKEY);
 | 
			
		||||
    // TODO self->player_o = 0;
 | 
			
		||||
    self->state = State_Waiting;
 | 
			
		||||
    self->keep_alive[0] = 0;
 | 
			
		||||
    self->keep_alive[1] = 0;
 | 
			
		||||
 | 
			
		||||
    // TODO fixed iteration loops ok? unrolled?
 | 
			
		||||
    for (int i = 0; i < 9; i++) {
 | 
			
		||||
        self->board[i] = BoardItem_F;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX Result game_join(Game *self, SolPubkey *player_o,
 | 
			
		||||
                               int64_t timestamp) {
 | 
			
		||||
    if (self->state == State_Waiting) {
 | 
			
		||||
        sol_memcpy(self->player_o.x, player_o, SIZE_PUBKEY);
 | 
			
		||||
        self->state = State_XMove;
 | 
			
		||||
 | 
			
		||||
        if (timestamp <= self->keep_alive[1]) {
 | 
			
		||||
            return Result_InvalidTimestamp;
 | 
			
		||||
        } else {
 | 
			
		||||
            self->keep_alive[1] = timestamp;
 | 
			
		||||
            return Result_Ok;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return Result_GameInProgress;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX bool game_same(BoardItem x_or_o, BoardItem one, BoardItem two,
 | 
			
		||||
                             BoardItem three) {
 | 
			
		||||
    if (x_or_o == one && x_or_o == two && x_or_o == three) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX bool game_same_player(SolPubkey *one, SolPubkey *two) {
 | 
			
		||||
    // TODO fixed iteration loops ok? unrolled?
 | 
			
		||||
    for (int i = 0; i < SIZE_PUBKEY; i++) {
 | 
			
		||||
        if (one->x[i] != two->x[i]) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX Result game_next_move(Game *self, SolPubkey *player, int x, int y) {
 | 
			
		||||
    int board_index = y * 3 + x;
 | 
			
		||||
    if (board_index >= 9 || self->board[board_index] != BoardItem_F) {
 | 
			
		||||
        return Result_InvalidMove;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BoardItem x_or_o;
 | 
			
		||||
    State won_state;
 | 
			
		||||
 | 
			
		||||
    switch (self->state) {
 | 
			
		||||
        case State_XMove:
 | 
			
		||||
            if (!game_same_player(player, &self->player_x)) {
 | 
			
		||||
                return Result_PlayerNotFound;
 | 
			
		||||
            }
 | 
			
		||||
            self->state = State_OMove;
 | 
			
		||||
            x_or_o = BoardItem_X;
 | 
			
		||||
            won_state = State_XWon;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case State_OMove:
 | 
			
		||||
            if (!game_same_player(player, &self->player_o)) {
 | 
			
		||||
                return Result_PlayerNotFound;
 | 
			
		||||
            }
 | 
			
		||||
            self->state = State_XMove;
 | 
			
		||||
            x_or_o = BoardItem_O;
 | 
			
		||||
            won_state = State_OWon;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            return Result_NotYourTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    self->board[board_index] = x_or_o;
 | 
			
		||||
 | 
			
		||||
    // game_dump_board(self);
 | 
			
		||||
 | 
			
		||||
    bool winner =
 | 
			
		||||
        // Check rows
 | 
			
		||||
        game_same(x_or_o, self->board[0], self->board[1], self->board[2]) ||
 | 
			
		||||
        game_same(x_or_o, self->board[3], self->board[4], self->board[5]) ||
 | 
			
		||||
        game_same(x_or_o, self->board[6], self->board[7], self->board[8]) ||
 | 
			
		||||
        // Check columns
 | 
			
		||||
        game_same(x_or_o, self->board[0], self->board[3], self->board[6]) ||
 | 
			
		||||
        game_same(x_or_o, self->board[1], self->board[4], self->board[7]) ||
 | 
			
		||||
        game_same(x_or_o, self->board[2], self->board[5], self->board[8]) ||
 | 
			
		||||
        // Check both diagonals
 | 
			
		||||
        game_same(x_or_o, self->board[0], self->board[4], self->board[8]) ||
 | 
			
		||||
        game_same(x_or_o, self->board[2], self->board[4], self->board[6]);
 | 
			
		||||
 | 
			
		||||
    if (winner) {
 | 
			
		||||
        self->state = won_state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        int draw = true;
 | 
			
		||||
        // TODO fixed iteration loops ok? unrolled?
 | 
			
		||||
        for (int i = 0; i < 9; i++) {
 | 
			
		||||
            if (BoardItem_F == self->board[i]) {
 | 
			
		||||
                draw = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (draw) {
 | 
			
		||||
            self->state = State_Draw;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return Result_Ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player,
 | 
			
		||||
                                     int64_t timestamp) {
 | 
			
		||||
    switch (self->state) {
 | 
			
		||||
        case State_Waiting:
 | 
			
		||||
        case State_XMove:
 | 
			
		||||
        case State_OMove:
 | 
			
		||||
            if (game_same_player(player, &self->player_x)) {
 | 
			
		||||
                if (timestamp <= self->keep_alive[0]) {
 | 
			
		||||
                    return Result_InvalidTimestamp;
 | 
			
		||||
                }
 | 
			
		||||
                self->keep_alive[0] = timestamp;
 | 
			
		||||
            } else if (game_same_player(player, &self->player_o)) {
 | 
			
		||||
                if (timestamp <= self->keep_alive[1]) {
 | 
			
		||||
                    return Result_InvalidTimestamp;
 | 
			
		||||
                }
 | 
			
		||||
                self->keep_alive[1] = timestamp;
 | 
			
		||||
            } else {
 | 
			
		||||
                return Result_PlayerNotFound;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    return Result_Ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void entrypoint(uint8_t *buf) {
 | 
			
		||||
    SolKeyedAccounts ka[3];
 | 
			
		||||
    uint64_t userdata_len;
 | 
			
		||||
    uint8_t *userdata;
 | 
			
		||||
    int err = 0;
 | 
			
		||||
 | 
			
		||||
    if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) {
 | 
			
		||||
        sol_panic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (sizeof(Game) > ka[1].userdata_len) {
 | 
			
		||||
        sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len);
 | 
			
		||||
        sol_panic();
 | 
			
		||||
    }
 | 
			
		||||
    Game game;
 | 
			
		||||
    sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len);
 | 
			
		||||
 | 
			
		||||
    Command command = *userdata;
 | 
			
		||||
    sol_print(0, 0, 0, 0, command);
 | 
			
		||||
    switch (command) {
 | 
			
		||||
        case Command_Init:
 | 
			
		||||
            game_create(&game, ka[2].key);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case Command_Join:
 | 
			
		||||
            err = game_join(&game, ka[0].key, userdata[8]);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case Command_KeepAlive:
 | 
			
		||||
            err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case Command_Move:
 | 
			
		||||
            err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            sol_panic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len);
 | 
			
		||||
    sol_print(0, 0, 0, err, game.state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// // -- Debug --
 | 
			
		||||
 | 
			
		||||
// SOL_FN_PREFIX void print_key(SolPubkey *key) {
 | 
			
		||||
//     for (int j = 0; j < SIZE_PUBKEY; j++) {
 | 
			
		||||
//         sol_print(0, 0, 0, j, key->x[j]);
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
 | 
			
		||||
//     for (int j = 0; j < len; j++) {
 | 
			
		||||
//         sol_print(0, 0, 0, j, data[j]);
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
 | 
			
		||||
//                                 uint8_t *userdata, uint64_t userdata_len) {
 | 
			
		||||
//     sol_print(0, 0, 0, 0, num_ka);
 | 
			
		||||
//     for (int i = 0; i < num_ka; i++) {
 | 
			
		||||
//         // key
 | 
			
		||||
//         print_key(ka[i].key);
 | 
			
		||||
 | 
			
		||||
//         // tokens
 | 
			
		||||
//         sol_print(0, 0, 0, 0, ka[i].tokens);
 | 
			
		||||
 | 
			
		||||
//         // account userdata
 | 
			
		||||
//         print_userdata(ka[i].userdata, ka[i].userdata_len);
 | 
			
		||||
 | 
			
		||||
//         // program_id
 | 
			
		||||
//         print_key(ka[i].program_id);
 | 
			
		||||
//     }
 | 
			
		||||
//     // tx userdata
 | 
			
		||||
//     print_userdata(userdata, userdata_len);
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// void entrypoint(char *buf) {
 | 
			
		||||
//     SolKeyedAccounts ka[3];
 | 
			
		||||
//     uint64_t userdata_len;
 | 
			
		||||
//     uint8_t *userdata;
 | 
			
		||||
 | 
			
		||||
//     if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata_len, &userdata)) {
 | 
			
		||||
//         return;
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     print_params(3, ka, userdata, userdata_len);
 | 
			
		||||
// }
 | 
			
		||||
@@ -15,7 +15,7 @@ authors = [
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.0.0"
 | 
			
		||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
 | 
			
		||||
solana_program_interface = { path = "../../common" }
 | 
			
		||||
solana_program_interface = { path = "../../../common" }
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
name = "move_funds"
 | 
			
		||||
@@ -13,7 +13,7 @@ authors = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
solana_program_interface = { path = "../../common" }
 | 
			
		||||
solana_program_interface = { path = "../../../common" }
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
name = "noop"
 | 
			
		||||
@@ -3,4 +3,7 @@ extern crate solana_program_interface;
 | 
			
		||||
use solana_program_interface::account::KeyedAccount;
 | 
			
		||||
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {}
 | 
			
		||||
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
 | 
			
		||||
    //println!("AccountInfos: {:#?}", _infos);
 | 
			
		||||
    //println!("data: {:#?}", data);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "print"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
authors = [
 | 
			
		||||
    "Anatoly Yakovenko <anatoly@solana.com>",
 | 
			
		||||
    "Greg Fitzgerald <greg@solana.com>",
 | 
			
		||||
    "Stephen Akridge <stephen@solana.com>",
 | 
			
		||||
    "Michael Vines <mvines@solana.com>",
 | 
			
		||||
    "Rob Walker <rob@solana.com>",
 | 
			
		||||
    "Pankaj Garg <pankaj@solana.com>",
 | 
			
		||||
    "Tyera Eulberg <tyera@solana.com>",
 | 
			
		||||
    "Jack May <jack@solana.com>",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
solana_program_interface = { path = "../../common" }
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
name = "print"
 | 
			
		||||
crate-type = ["dylib"]
 | 
			
		||||
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
extern crate solana_program_interface;
 | 
			
		||||
 | 
			
		||||
use solana_program_interface::account::KeyedAccount;
 | 
			
		||||
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
 | 
			
		||||
    println!("AccountInfos: {:#?}", infos);
 | 
			
		||||
    //println!("data: {:#?}", data);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										314
									
								
								src/bpf_verifier.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								src/bpf_verifier.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,314 @@
 | 
			
		||||
use rbpf::ebpf;
 | 
			
		||||
 | 
			
		||||
// This “verifier” performs simple checks when the eBPF program is loaded into the VM (before it is
 | 
			
		||||
// interpreted or JIT-compiled).
 | 
			
		||||
 | 
			
		||||
fn verify_prog_len(prog: &[u8]) {
 | 
			
		||||
    if prog.len() % ebpf::INSN_SIZE != 0 {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: eBPF program length must be a multiple of {:?} octets",
 | 
			
		||||
            ebpf::INSN_SIZE
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if prog.len() > ebpf::PROG_MAX_SIZE {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: eBPF program length limited to {:?}, here {:?}",
 | 
			
		||||
            ebpf::PROG_MAX_INSNS,
 | 
			
		||||
            prog.len() / ebpf::INSN_SIZE
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if prog.is_empty() {
 | 
			
		||||
        panic!("[Verifier] Error: program is empty");
 | 
			
		||||
    }
 | 
			
		||||
    // TODO BPF program may deterministically exit even if the last
 | 
			
		||||
    // instruction in the block is not an exit (might be earlier and jumped to)
 | 
			
		||||
    // TODO need to validate more intelligently
 | 
			
		||||
    // let last_insn = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1);
 | 
			
		||||
    // if last_insn.opc != ebpf::EXIT {
 | 
			
		||||
    //     panic!("[Verifier] Error: program does not end with “EXIT” instruction");
 | 
			
		||||
    // }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn verify_imm_nonzero(insn: &ebpf::Insn, insn_ptr: usize) {
 | 
			
		||||
    if insn.imm == 0 {
 | 
			
		||||
        panic!("[Verifier] Error: division by 0 (insn #{:?})", insn_ptr);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn verify_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) {
 | 
			
		||||
    match insn.imm {
 | 
			
		||||
        16 | 32 | 64 => return,
 | 
			
		||||
        _ => panic!(
 | 
			
		||||
            "[Verifier] Error: unsupported argument for LE/BE (insn #{:?})",
 | 
			
		||||
            insn_ptr
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn verify_load_dw(prog: &[u8], insn_ptr: usize) {
 | 
			
		||||
    // We know we can reach next insn since we enforce an EXIT insn at the end of program, while
 | 
			
		||||
    // this function should be called only for LD_DW insn, that cannot be last in program.
 | 
			
		||||
    let next_insn = ebpf::get_insn(prog, insn_ptr + 1);
 | 
			
		||||
    if next_insn.opc != 0 {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: incomplete LD_DW instruction (insn #{:?})",
 | 
			
		||||
            insn_ptr
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn verify_jmp_offset(prog: &[u8], insn_ptr: usize) {
 | 
			
		||||
    let insn = ebpf::get_insn(prog, insn_ptr);
 | 
			
		||||
    if insn.off == -1 {
 | 
			
		||||
        panic!("[Verifier] Error: infinite loop (insn #{:?})", insn_ptr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let dst_insn_ptr = insn_ptr as isize + 1 + insn.off as isize;
 | 
			
		||||
    if dst_insn_ptr < 0 || dst_insn_ptr as usize >= (prog.len() / ebpf::INSN_SIZE) {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: jump out of code to #{:?} (insn #{:?})",
 | 
			
		||||
            dst_insn_ptr, insn_ptr
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize);
 | 
			
		||||
    if dst_insn.opc == 0 {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: jump to middle of LD_DW at #{:?} (insn #{:?})",
 | 
			
		||||
            dst_insn_ptr, insn_ptr
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn verify_registers(insn: &ebpf::Insn, store: bool, insn_ptr: usize) {
 | 
			
		||||
    if insn.src > 10 {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "[Verifier] Error: invalid source register (insn #{:?})",
 | 
			
		||||
            insn_ptr
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    match (insn.dst, store) {
 | 
			
		||||
        (0...9, _) | (10, true) => {}
 | 
			
		||||
        (10, false) => panic!(
 | 
			
		||||
            "[Verifier] Error: cannot write into register r10 (insn #{:?})",
 | 
			
		||||
            insn_ptr
 | 
			
		||||
        ),
 | 
			
		||||
        (_, _) => panic!(
 | 
			
		||||
            "[Verifier] Error: invalid destination register (insn #{:?})",
 | 
			
		||||
            insn_ptr
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn verifier(prog: &[u8]) -> bool {
 | 
			
		||||
    verify_prog_len(prog);
 | 
			
		||||
 | 
			
		||||
    let mut insn_ptr: usize = 0;
 | 
			
		||||
    while insn_ptr * ebpf::INSN_SIZE < prog.len() {
 | 
			
		||||
        let insn = ebpf::get_insn(prog, insn_ptr);
 | 
			
		||||
        let mut store = false;
 | 
			
		||||
 | 
			
		||||
        match insn.opc {
 | 
			
		||||
            // BPF_LD class
 | 
			
		||||
            ebpf::LD_ABS_B => {}
 | 
			
		||||
            ebpf::LD_ABS_H => {}
 | 
			
		||||
            ebpf::LD_ABS_W => {}
 | 
			
		||||
            ebpf::LD_ABS_DW => {}
 | 
			
		||||
            ebpf::LD_IND_B => {}
 | 
			
		||||
            ebpf::LD_IND_H => {}
 | 
			
		||||
            ebpf::LD_IND_W => {}
 | 
			
		||||
            ebpf::LD_IND_DW => {}
 | 
			
		||||
 | 
			
		||||
            ebpf::LD_DW_IMM => {
 | 
			
		||||
                store = true;
 | 
			
		||||
                verify_load_dw(prog, insn_ptr);
 | 
			
		||||
                insn_ptr += 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // BPF_LDX class
 | 
			
		||||
            ebpf::LD_B_REG => {}
 | 
			
		||||
            ebpf::LD_H_REG => {}
 | 
			
		||||
            ebpf::LD_W_REG => {}
 | 
			
		||||
            ebpf::LD_DW_REG => {}
 | 
			
		||||
 | 
			
		||||
            // BPF_ST class
 | 
			
		||||
            ebpf::ST_B_IMM => store = true,
 | 
			
		||||
            ebpf::ST_H_IMM => store = true,
 | 
			
		||||
            ebpf::ST_W_IMM => store = true,
 | 
			
		||||
            ebpf::ST_DW_IMM => store = true,
 | 
			
		||||
 | 
			
		||||
            // BPF_STX class
 | 
			
		||||
            ebpf::ST_B_REG => store = true,
 | 
			
		||||
            ebpf::ST_H_REG => store = true,
 | 
			
		||||
            ebpf::ST_W_REG => store = true,
 | 
			
		||||
            ebpf::ST_DW_REG => store = true,
 | 
			
		||||
            ebpf::ST_W_XADD => {
 | 
			
		||||
                unimplemented!();
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::ST_DW_XADD => {
 | 
			
		||||
                unimplemented!();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // BPF_ALU class
 | 
			
		||||
            ebpf::ADD32_IMM => {}
 | 
			
		||||
            ebpf::ADD32_REG => {}
 | 
			
		||||
            ebpf::SUB32_IMM => {}
 | 
			
		||||
            ebpf::SUB32_REG => {}
 | 
			
		||||
            ebpf::MUL32_IMM => {}
 | 
			
		||||
            ebpf::MUL32_REG => {}
 | 
			
		||||
            ebpf::DIV32_IMM => {
 | 
			
		||||
                verify_imm_nonzero(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::DIV32_REG => {}
 | 
			
		||||
            ebpf::OR32_IMM => {}
 | 
			
		||||
            ebpf::OR32_REG => {}
 | 
			
		||||
            ebpf::AND32_IMM => {}
 | 
			
		||||
            ebpf::AND32_REG => {}
 | 
			
		||||
            ebpf::LSH32_IMM => {}
 | 
			
		||||
            ebpf::LSH32_REG => {}
 | 
			
		||||
            ebpf::RSH32_IMM => {}
 | 
			
		||||
            ebpf::RSH32_REG => {}
 | 
			
		||||
            ebpf::NEG32 => {}
 | 
			
		||||
            ebpf::MOD32_IMM => {
 | 
			
		||||
                verify_imm_nonzero(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::MOD32_REG => {}
 | 
			
		||||
            ebpf::XOR32_IMM => {}
 | 
			
		||||
            ebpf::XOR32_REG => {}
 | 
			
		||||
            ebpf::MOV32_IMM => {}
 | 
			
		||||
            ebpf::MOV32_REG => {}
 | 
			
		||||
            ebpf::ARSH32_IMM => {}
 | 
			
		||||
            ebpf::ARSH32_REG => {}
 | 
			
		||||
            ebpf::LE => {
 | 
			
		||||
                verify_imm_endian(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::BE => {
 | 
			
		||||
                verify_imm_endian(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // BPF_ALU64 class
 | 
			
		||||
            ebpf::ADD64_IMM => {}
 | 
			
		||||
            ebpf::ADD64_REG => {}
 | 
			
		||||
            ebpf::SUB64_IMM => {}
 | 
			
		||||
            ebpf::SUB64_REG => {}
 | 
			
		||||
            ebpf::MUL64_IMM => {
 | 
			
		||||
                verify_imm_nonzero(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::MUL64_REG => {}
 | 
			
		||||
            ebpf::DIV64_IMM => {
 | 
			
		||||
                verify_imm_nonzero(&insn, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::DIV64_REG => {}
 | 
			
		||||
            ebpf::OR64_IMM => {}
 | 
			
		||||
            ebpf::OR64_REG => {}
 | 
			
		||||
            ebpf::AND64_IMM => {}
 | 
			
		||||
            ebpf::AND64_REG => {}
 | 
			
		||||
            ebpf::LSH64_IMM => {}
 | 
			
		||||
            ebpf::LSH64_REG => {}
 | 
			
		||||
            ebpf::RSH64_IMM => {}
 | 
			
		||||
            ebpf::RSH64_REG => {}
 | 
			
		||||
            ebpf::NEG64 => {}
 | 
			
		||||
            ebpf::MOD64_IMM => {}
 | 
			
		||||
            ebpf::MOD64_REG => {}
 | 
			
		||||
            ebpf::XOR64_IMM => {}
 | 
			
		||||
            ebpf::XOR64_REG => {}
 | 
			
		||||
            ebpf::MOV64_IMM => {}
 | 
			
		||||
            ebpf::MOV64_REG => {}
 | 
			
		||||
            ebpf::ARSH64_IMM => {}
 | 
			
		||||
            ebpf::ARSH64_REG => {}
 | 
			
		||||
 | 
			
		||||
            // BPF_JMP class
 | 
			
		||||
            ebpf::JA => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JEQ_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JEQ_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JGT_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JGT_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JGE_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JGE_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JLT_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JLT_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JLE_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JLE_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSET_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSET_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JNE_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JNE_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSGT_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSGT_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSGE_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSGE_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSLT_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSLT_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSLE_IMM => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::JSLE_REG => {
 | 
			
		||||
                verify_jmp_offset(prog, insn_ptr);
 | 
			
		||||
            }
 | 
			
		||||
            ebpf::CALL => {}
 | 
			
		||||
            ebpf::TAIL_CALL => unimplemented!(),
 | 
			
		||||
            ebpf::EXIT => {}
 | 
			
		||||
 | 
			
		||||
            _ => {
 | 
			
		||||
                panic!(
 | 
			
		||||
                    "[Verifier] Error: unknown eBPF opcode {:#2x} (insn #{:?})",
 | 
			
		||||
                    insn.opc, insn_ptr
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        verify_registers(&insn, store, insn_ptr);
 | 
			
		||||
 | 
			
		||||
        insn_ptr += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // insn_ptr should now be equal to number of instructions.
 | 
			
		||||
    if insn_ptr != prog.len() / ebpf::INSN_SIZE {
 | 
			
		||||
        panic!("[Verifier] Error: jumped out of code to #{:?}", insn_ptr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    true
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +1,75 @@
 | 
			
		||||
extern crate bincode;
 | 
			
		||||
extern crate generic_array;
 | 
			
		||||
extern crate elf;
 | 
			
		||||
extern crate rbpf;
 | 
			
		||||
 | 
			
		||||
use libc;
 | 
			
		||||
use libloading;
 | 
			
		||||
use solana_program_interface::account::KeyedAccount;
 | 
			
		||||
use std::io::prelude::*;
 | 
			
		||||
use std::mem;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
/// Dynamic link library prefix
 | 
			
		||||
use bpf_verifier;
 | 
			
		||||
use byteorder::{LittleEndian, WriteBytesExt};
 | 
			
		||||
use libc;
 | 
			
		||||
#[cfg(unix)]
 | 
			
		||||
const PLATFORM_FILE_PREFIX: &str = "lib";
 | 
			
		||||
/// Dynamic link library prefix
 | 
			
		||||
use libloading::os::unix::*;
 | 
			
		||||
#[cfg(windows)]
 | 
			
		||||
const PLATFORM_FILE_PREFIX: &str = "";
 | 
			
		||||
use libloading::os::windows::*;
 | 
			
		||||
 | 
			
		||||
use solana_program_interface::account::KeyedAccount;
 | 
			
		||||
use solana_program_interface::pubkey::Pubkey;
 | 
			
		||||
 | 
			
		||||
/// Dynamic link library prefixs
 | 
			
		||||
const PLATFORM_FILE_PREFIX_BPF: &str = "";
 | 
			
		||||
#[cfg(unix)]
 | 
			
		||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib";
 | 
			
		||||
#[cfg(windows)]
 | 
			
		||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "";
 | 
			
		||||
 | 
			
		||||
/// Dynamic link library file extension specific to the platform
 | 
			
		||||
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
 | 
			
		||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
 | 
			
		||||
const PLATFORM_FILE_EXTENSION: &str = "dylib";
 | 
			
		||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib";
 | 
			
		||||
/// Dynamic link library file extension specific to the platform
 | 
			
		||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
 | 
			
		||||
const PLATFORM_FILE_EXTENSION: &str = "so";
 | 
			
		||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so";
 | 
			
		||||
/// Dynamic link library file extension specific to the platform
 | 
			
		||||
#[cfg(windows)]
 | 
			
		||||
const PLATFORM_FILE_EXTENSION: &str = "dll";
 | 
			
		||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll";
 | 
			
		||||
 | 
			
		||||
/// Section name
 | 
			
		||||
const PLATFORM_SECTION_RS: &str = ".text,entrypoint";
 | 
			
		||||
const PLATFORM_SECTION_C: &str = ".text.entrypoint";
 | 
			
		||||
 | 
			
		||||
pub enum ProgramPath {
 | 
			
		||||
    Bpf,
 | 
			
		||||
    Native,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProgramPath {
 | 
			
		||||
    /// Creates a platform-specific file path
 | 
			
		||||
fn create_library_path(name: &str) -> PathBuf {
 | 
			
		||||
    pub fn create(&self, name: &str) -> PathBuf {
 | 
			
		||||
        let mut path = PathBuf::from(env!("OUT_DIR"));
 | 
			
		||||
        match self {
 | 
			
		||||
            ProgramPath::Bpf => {
 | 
			
		||||
                //println!("Bpf");
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.push(PLATFORM_FILE_PREFIX_BPF.to_string() + name);
 | 
			
		||||
                path.set_extension(PLATFORM_FILE_EXTENSION_BPF);
 | 
			
		||||
            }
 | 
			
		||||
            ProgramPath::Native => {
 | 
			
		||||
                //println!("Native");
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.pop();
 | 
			
		||||
                path.push("deps");
 | 
			
		||||
    path.push(PLATFORM_FILE_PREFIX.to_string() + name);
 | 
			
		||||
    path.set_extension(PLATFORM_FILE_EXTENSION);
 | 
			
		||||
                path.push(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name);
 | 
			
		||||
                path.set_extension(PLATFORM_FILE_EXTENSION_NATIVE);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //println!("Path: {:?}", path);
 | 
			
		||||
        path
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// All programs export a symbol named process()
 | 
			
		||||
const ENTRYPOINT: &str = "process";
 | 
			
		||||
@@ -44,47 +81,129 @@ pub enum DynamicProgram {
 | 
			
		||||
    /// * Transaction::keys[0..] - program dependent
 | 
			
		||||
    /// * name - name of the program, translated to a file path of the program module
 | 
			
		||||
    /// * userdata - program specific user data
 | 
			
		||||
    Native {
 | 
			
		||||
        name: String,
 | 
			
		||||
        library: libloading::Library,
 | 
			
		||||
    },
 | 
			
		||||
    Native { name: String, library: Library },
 | 
			
		||||
    /// Bpf program
 | 
			
		||||
    /// * Transaction::keys[0..] - program dependent
 | 
			
		||||
    /// * TODO BPF specific stuff
 | 
			
		||||
    /// * userdata - program specific user data
 | 
			
		||||
    Bpf { userdata: Vec<u8> },
 | 
			
		||||
    Bpf { name: String, prog: Vec<u8> },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DynamicProgram {
 | 
			
		||||
    pub fn new(name: String) -> Self {
 | 
			
		||||
        // TODO determine what kind of module to load
 | 
			
		||||
 | 
			
		||||
    pub fn new_native(name: String) -> Self {
 | 
			
		||||
        // create native program
 | 
			
		||||
        let path = create_library_path(&name);
 | 
			
		||||
        let path = ProgramPath::Native {}.create(&name);
 | 
			
		||||
        // TODO linux tls bug can cause crash on dlclose, workaround by never unloading
 | 
			
		||||
        let os_lib =
 | 
			
		||||
            libloading::os::unix::Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        let library = libloading::Library::from(os_lib);
 | 
			
		||||
        let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW).unwrap();
 | 
			
		||||
        DynamicProgram::Native { name, library }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_bpf_from_file(name: String) -> Self {
 | 
			
		||||
        // create native program
 | 
			
		||||
        let path = ProgramPath::Bpf {}.create(&name);
 | 
			
		||||
        let file = match elf::File::open_path(&path) {
 | 
			
		||||
            Ok(f) => f,
 | 
			
		||||
            Err(e) => panic!("Error opening ELF {:?}: {:?}", path, e),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let text_section = match file.get_section(PLATFORM_SECTION_RS) {
 | 
			
		||||
            Some(s) => s,
 | 
			
		||||
            None => match file.get_section(PLATFORM_SECTION_C) {
 | 
			
		||||
                Some(s) => s,
 | 
			
		||||
                None => panic!("Failed to find text section"),
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        let prog = text_section.data.clone();
 | 
			
		||||
 | 
			
		||||
        DynamicProgram::Bpf { name, prog }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_bpf_from_buffer(prog: Vec<u8>) -> Self {
 | 
			
		||||
        DynamicProgram::Bpf {
 | 
			
		||||
            name: "from_buffer".to_string(),
 | 
			
		||||
            prog,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    fn dump_prog(name: &str, prog: &[u8]) {
 | 
			
		||||
        let mut eight_bytes: Vec<u8> = Vec::new();
 | 
			
		||||
        println!("BPF Program: {}", name);
 | 
			
		||||
        for i in prog.iter() {
 | 
			
		||||
            if eight_bytes.len() >= 7 {
 | 
			
		||||
                println!("{:02X?}", eight_bytes);
 | 
			
		||||
                eight_bytes.clear();
 | 
			
		||||
            } else {
 | 
			
		||||
                eight_bytes.push(i.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn serialize(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> Vec<u8> {
 | 
			
		||||
        assert_eq!(32, mem::size_of::<Pubkey>());
 | 
			
		||||
 | 
			
		||||
        let mut v: Vec<u8> = Vec::new();
 | 
			
		||||
        v.write_u64::<LittleEndian>(infos.len() as u64).unwrap();
 | 
			
		||||
        for info in infos.iter_mut() {
 | 
			
		||||
            v.write_all(info.key.as_ref()).unwrap();
 | 
			
		||||
            v.write_i64::<LittleEndian>(info.account.tokens).unwrap();
 | 
			
		||||
            v.write_u64::<LittleEndian>(info.account.userdata.len() as u64)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            v.write_all(&info.account.userdata).unwrap();
 | 
			
		||||
            v.write_all(info.account.program_id.as_ref()).unwrap();
 | 
			
		||||
            //println!("userdata: {:?}", infos[i].account.userdata);
 | 
			
		||||
        }
 | 
			
		||||
        v.write_u64::<LittleEndian>(data.len() as u64).unwrap();
 | 
			
		||||
        v.write_all(data).unwrap();
 | 
			
		||||
        v
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn deserialize(infos: &mut Vec<KeyedAccount>, buffer: &[u8]) {
 | 
			
		||||
        assert_eq!(32, mem::size_of::<Pubkey>());
 | 
			
		||||
 | 
			
		||||
        let mut start = mem::size_of::<u64>();
 | 
			
		||||
        for info in infos.iter_mut() {
 | 
			
		||||
            start += mem::size_of::<Pubkey>() // pubkey
 | 
			
		||||
                  + mem::size_of::<u64>() // tokens
 | 
			
		||||
                  + mem::size_of::<u64>(); // length tag
 | 
			
		||||
 | 
			
		||||
            let end = start + info.account.userdata.len();
 | 
			
		||||
 | 
			
		||||
            info.account.userdata.clone_from_slice(&buffer[start..end]);
 | 
			
		||||
 | 
			
		||||
            start += info.account.userdata.len() // userdata
 | 
			
		||||
                  + mem::size_of::<Pubkey>(); // program_id
 | 
			
		||||
                                              //println!("userdata: {:?}", infos[i].account.userdata);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn call(&self, infos: &mut Vec<KeyedAccount>, data: &[u8]) {
 | 
			
		||||
        match self {
 | 
			
		||||
            DynamicProgram::Native { name, library } => unsafe {
 | 
			
		||||
                let entrypoint: libloading::Symbol<Entrypoint> =
 | 
			
		||||
                    match library.get(ENTRYPOINT.as_bytes()) {
 | 
			
		||||
                let entrypoint: Symbol<Entrypoint> = match library.get(ENTRYPOINT.as_bytes()) {
 | 
			
		||||
                    Ok(s) => s,
 | 
			
		||||
                    Err(e) => panic!(
 | 
			
		||||
                            "{:?} Unable to find {:?} in program {}",
 | 
			
		||||
                        "Unable to find {:?} in program {}: {:?} ",
 | 
			
		||||
                        e, ENTRYPOINT, name
 | 
			
		||||
                    ),
 | 
			
		||||
                };
 | 
			
		||||
                entrypoint(infos, data);
 | 
			
		||||
            },
 | 
			
		||||
            DynamicProgram::Bpf { .. } => {
 | 
			
		||||
                // TODO BPF
 | 
			
		||||
                println!{"Bpf program not supported"}
 | 
			
		||||
            DynamicProgram::Bpf { prog, .. } => {
 | 
			
		||||
                println!("Instructions: {}", prog.len() / 8);
 | 
			
		||||
                //DynamicProgram::dump_prog(name, prog);
 | 
			
		||||
 | 
			
		||||
                let mut vm = rbpf::EbpfVmRaw::new(prog, Some(bpf_verifier::verifier));
 | 
			
		||||
 | 
			
		||||
                // TODO register more handlers (memcpy for example)
 | 
			
		||||
                vm.register_helper(
 | 
			
		||||
                    rbpf::helpers::BPF_TRACE_PRINTK_IDX,
 | 
			
		||||
                    rbpf::helpers::bpf_trace_printf,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                let mut v = DynamicProgram::serialize(infos, data);
 | 
			
		||||
                vm.prog_exec(v.as_mut_slice());
 | 
			
		||||
                DynamicProgram::deserialize(infos, &v);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -95,17 +214,71 @@ mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use std::path::Path;
 | 
			
		||||
 | 
			
		||||
    use solana_program_interface::account::Account;
 | 
			
		||||
    use solana_program_interface::pubkey::Pubkey;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_create_library_path() {
 | 
			
		||||
        let path = create_library_path("noop");
 | 
			
		||||
    fn test_path_create_native() {
 | 
			
		||||
        let path = ProgramPath::Native {}.create("noop");
 | 
			
		||||
        assert_eq!(true, Path::new(&path).exists());
 | 
			
		||||
        let path = create_library_path("print");
 | 
			
		||||
        assert_eq!(true, Path::new(&path).exists());
 | 
			
		||||
        let path = create_library_path("move_funds");
 | 
			
		||||
        let path = ProgramPath::Native {}.create("move_funds");
 | 
			
		||||
        assert_eq!(true, Path::new(&path).exists());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_bpf_buf_noop() {
 | 
			
		||||
        let prog = vec![
 | 
			
		||||
            0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 	exit
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let data: Vec<u8> = vec![0];
 | 
			
		||||
        let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
        let mut accounts = vec![Account::default(), Account::default()];
 | 
			
		||||
        accounts[0].tokens = 100;
 | 
			
		||||
        accounts[1].tokens = 1;
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let mut infos: Vec<_> = (&keys)
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .zip(&mut accounts)
 | 
			
		||||
                .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
            let dp = DynamicProgram::new_bpf_from_buffer(prog);
 | 
			
		||||
            dp.call(&mut infos, &data);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_bpf_buf_print() {
 | 
			
		||||
        let prog = vec![
 | 
			
		||||
            0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 	r1 = 0
 | 
			
		||||
            0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 	r2 = 0
 | 
			
		||||
            0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 	r3 = 1
 | 
			
		||||
            0xb7, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // 	r4 = 2
 | 
			
		||||
            0xb7, 0x05, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // 	r5 = 3
 | 
			
		||||
            0x85, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // 	call 6
 | 
			
		||||
            0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 	exit
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let data: Vec<u8> = vec![0];
 | 
			
		||||
        let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
        let mut accounts = vec![Account::default(), Account::default()];
 | 
			
		||||
        accounts[0].tokens = 100;
 | 
			
		||||
        accounts[1].tokens = 1;
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let mut infos: Vec<_> = (&keys)
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .zip(&mut accounts)
 | 
			
		||||
                .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
            let dp = DynamicProgram::new_bpf_from_buffer(prog);
 | 
			
		||||
            dp.call(&mut infos, &data);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO add more tests to validate the Userdata and Account data is
 | 
			
		||||
    // moving across the boundary correctly
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ pub mod choose_gossip_peer_strategy;
 | 
			
		||||
pub mod client;
 | 
			
		||||
#[macro_use]
 | 
			
		||||
pub mod crdt;
 | 
			
		||||
pub mod bpf_verifier;
 | 
			
		||||
pub mod budget_program;
 | 
			
		||||
pub mod drone;
 | 
			
		||||
pub mod dynamic_program;
 | 
			
		||||
@@ -91,6 +92,7 @@ extern crate log;
 | 
			
		||||
extern crate nix;
 | 
			
		||||
extern crate pnet_datalink;
 | 
			
		||||
extern crate rayon;
 | 
			
		||||
extern crate rbpf;
 | 
			
		||||
extern crate reqwest;
 | 
			
		||||
extern crate ring;
 | 
			
		||||
extern crate serde;
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ impl SystemProgram {
 | 
			
		||||
                }
 | 
			
		||||
                SystemProgram::Load { program_id, name } => {
 | 
			
		||||
                    let mut hashmap = loaded_programs.write().unwrap();
 | 
			
		||||
                    hashmap.insert(program_id, DynamicProgram::new(name));
 | 
			
		||||
                    hashmap.insert(program_id, DynamicProgram::new_native(name));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,7 @@ impl Default for State {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub struct Game {
 | 
			
		||||
    player_x: Pubkey,
 | 
			
		||||
@@ -173,7 +174,8 @@ impl Game {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
enum Command {
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub enum Command {
 | 
			
		||||
    Init,           // player X initializes a new game
 | 
			
		||||
    Join(i64),      // player O wants to join (seconds since UNIX epoch)
 | 
			
		||||
    KeepAlive(i64), // player X/O keep alive (seconds since UNIX epoch)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,16 @@ extern crate solana;
 | 
			
		||||
extern crate solana_program_interface;
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::sync::RwLock;
 | 
			
		||||
use std::thread;
 | 
			
		||||
 | 
			
		||||
use bincode::serialize;
 | 
			
		||||
 | 
			
		||||
use solana::dynamic_program::DynamicProgram;
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
use solana::dynamic_program::ProgramPath;
 | 
			
		||||
use solana::hash::Hash;
 | 
			
		||||
use solana::signature::{Keypair, KeypairUtil};
 | 
			
		||||
use solana::system_program::SystemProgram;
 | 
			
		||||
@@ -17,29 +21,22 @@ use solana::transaction::Transaction;
 | 
			
		||||
use solana_program_interface::account::{Account, KeyedAccount};
 | 
			
		||||
use solana_program_interface::pubkey::Pubkey;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
use solana::tictactoe_program::Command;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_native_noop() {
 | 
			
		||||
    let data: Vec<u8> = vec![0];
 | 
			
		||||
    let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
    let mut accounts = vec![Account::default(), Account::default()];
 | 
			
		||||
    accounts[0].tokens = 100;
 | 
			
		||||
    accounts[1].tokens = 1;
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        let mut infos: Vec<_> = (&keys)
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .zip(&mut accounts)
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new("noop".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
fn test_path_create_bpf() {
 | 
			
		||||
    let path = ProgramPath::Bpf {}.create("move_funds_c");
 | 
			
		||||
    assert_eq!(true, Path::new(&path).exists());
 | 
			
		||||
    let path = ProgramPath::Bpf {}.create("tictactoe_c");
 | 
			
		||||
    assert_eq!(true, Path::new(&path).exists());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
#[test]
 | 
			
		||||
#[ignore]
 | 
			
		||||
fn test_native_print() {
 | 
			
		||||
fn test_bpf_file_noop_rust() {
 | 
			
		||||
    let data: Vec<u8> = vec![0];
 | 
			
		||||
    let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
    let mut accounts = vec![Account::default(), Account::default()];
 | 
			
		||||
@@ -53,13 +50,105 @@ fn test_native_print() {
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new("print".to_string());
 | 
			
		||||
        let dp = DynamicProgram::new_bpf_from_file("noop_rust".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_bpf_file_move_funds_c() {
 | 
			
		||||
    let data: Vec<u8> = vec![0xa, 0xb, 0xc, 0xd, 0xe, 0xf];
 | 
			
		||||
    let keys = vec![Pubkey::new(&[0xAA; 32]), Pubkey::new(&[0xBB; 32])];
 | 
			
		||||
    let mut accounts = vec![
 | 
			
		||||
        Account::new(0x0123456789abcdef, 4, Pubkey::default()),
 | 
			
		||||
        Account::new(1, 8, Pubkey::default()),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        let mut infos: Vec<_> = (&keys)
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .zip(&mut accounts)
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new_bpf_from_file("move_funds_c".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
fn tictactoe_command(command: Command, accounts: &mut Vec<Account>, player: Pubkey) {
 | 
			
		||||
    let p = &command as *const Command as *const u8;
 | 
			
		||||
    let data: &[u8] = unsafe { std::slice::from_raw_parts(p, std::mem::size_of::<Command>()) };
 | 
			
		||||
 | 
			
		||||
    // Init
 | 
			
		||||
    // player_x pub key in keys[2]
 | 
			
		||||
    // accounts[0].program_id must be tictactoe
 | 
			
		||||
    // accounts[1].userdata must be tictactoe game state
 | 
			
		||||
 | 
			
		||||
    let keys = vec![player, Pubkey::default(), player];
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        let mut infos: Vec<_> = (&keys)
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .zip(&mut *accounts)
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new_bpf_from_file("tictactoe_c".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bpf_c")]
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_bpf_file_tictactoe_c() {
 | 
			
		||||
    let game_size = 0x78; // corresponds to the C structure size
 | 
			
		||||
    let mut accounts = vec![
 | 
			
		||||
        Account::new(0, 0, Pubkey::default()),
 | 
			
		||||
        Account::new(0, game_size, Pubkey::default()),
 | 
			
		||||
        Account::new(0, 0, Pubkey::default()),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    tictactoe_command(Command::Init, &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(
 | 
			
		||||
        Command::Join(0xAABBCCDD),
 | 
			
		||||
        &mut accounts,
 | 
			
		||||
        Pubkey::new(&[0xA; 32]),
 | 
			
		||||
    );
 | 
			
		||||
    tictactoe_command(Command::Move(1, 1), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(Command::Move(0, 0), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(Command::Move(2, 0), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(Command::Move(0, 2), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(Command::Move(2, 2), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
    tictactoe_command(Command::Move(0, 1), &mut accounts, Pubkey::new(&[0xA; 32]));
 | 
			
		||||
 | 
			
		||||
    // validate test
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_native_file_noop() {
 | 
			
		||||
    let data: Vec<u8> = vec![0];
 | 
			
		||||
    let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
    let mut accounts = vec![Account::default(), Account::default()];
 | 
			
		||||
    accounts[0].tokens = 100;
 | 
			
		||||
    accounts[1].tokens = 1;
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        let mut infos: Vec<_> = (&keys)
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .zip(&mut accounts)
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new_native("noop".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_native_move_funds_success() {
 | 
			
		||||
fn test_native_file_move_funds_success() {
 | 
			
		||||
    let tokens: i64 = 100;
 | 
			
		||||
    let data: Vec<u8> = serialize(&tokens).unwrap();
 | 
			
		||||
    let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
@@ -74,7 +163,7 @@ fn test_native_move_funds_success() {
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new("move_funds".to_string());
 | 
			
		||||
        let dp = DynamicProgram::new_native("move_funds".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
    assert_eq!(0, accounts[0].tokens);
 | 
			
		||||
@@ -82,7 +171,7 @@ fn test_native_move_funds_success() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_native_move_funds_insufficient_funds() {
 | 
			
		||||
fn test_native_file_move_funds_insufficient_funds() {
 | 
			
		||||
    let tokens: i64 = 100;
 | 
			
		||||
    let data: Vec<u8> = serialize(&tokens).unwrap();
 | 
			
		||||
    let keys = vec![Pubkey::default(); 2];
 | 
			
		||||
@@ -97,7 +186,7 @@ fn test_native_move_funds_insufficient_funds() {
 | 
			
		||||
            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        let dp = DynamicProgram::new("move_funds".to_string());
 | 
			
		||||
        let dp = DynamicProgram::new_native("move_funds".to_string());
 | 
			
		||||
        dp.call(&mut infos, &data);
 | 
			
		||||
    }
 | 
			
		||||
    assert_eq!(10, accounts[0].tokens);
 | 
			
		||||
@@ -105,7 +194,7 @@ fn test_native_move_funds_insufficient_funds() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_native_move_funds_succes_many_threads() {
 | 
			
		||||
fn test_program_native_move_funds_succes_many_threads() {
 | 
			
		||||
    let num_threads = 42; // number of threads to spawn
 | 
			
		||||
    let num_iters = 100; // number of iterations of test in each thread
 | 
			
		||||
    let mut threads = Vec::new();
 | 
			
		||||
@@ -127,7 +216,7 @@ fn test_native_move_funds_succes_many_threads() {
 | 
			
		||||
                            .map(|(key, account)| KeyedAccount { key, account })
 | 
			
		||||
                            .collect();
 | 
			
		||||
 | 
			
		||||
                        let dp = DynamicProgram::new("move_funds".to_string());
 | 
			
		||||
                        let dp = DynamicProgram::new_native("move_funds".to_string());
 | 
			
		||||
                        dp.call(&mut infos, &data);
 | 
			
		||||
                    }
 | 
			
		||||
                    assert_eq!(0, accounts[0].tokens);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user