Program bank integration (#1462)
Native, BPF and Lua loaders integrated into the bank
This commit is contained in:
@ -24,7 +24,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
SolPubkey *key;
|
||||
int64_t tokens;
|
||||
int64_t* tokens;
|
||||
uint64_t userdata_len;
|
||||
uint8_t *userdata;
|
||||
SolPubkey *program_id;
|
||||
@ -50,7 +50,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) {
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
src += sizeof(uint64_t);
|
||||
|
||||
@ -61,8 +61,8 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||
src += SIZE_PUBKEY;
|
||||
|
||||
// tokens
|
||||
ka[i].tokens = *(uint64_t *)src;
|
||||
src += sizeof(uint64_t);
|
||||
ka[i].tokens = (int64_t *)src;
|
||||
src += sizeof(int64_t);
|
||||
|
||||
// account userdata
|
||||
ka[i].userdata_len = *(uint64_t *)src;
|
||||
@ -79,7 +79,7 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||
src += sizeof(uint64_t);
|
||||
*userdata = src;
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||
print_key(ka[i].key);
|
||||
|
||||
// tokens
|
||||
sol_print(0, 0, 0, 0, ka[i].tokens);
|
||||
sol_print(0, 0, 0, 0, *ka[i].tokens);
|
||||
|
||||
// account userdata
|
||||
print_userdata(ka[i].userdata, ka[i].userdata_len);
|
||||
@ -117,14 +117,24 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||
print_userdata(userdata, userdata_len);
|
||||
}
|
||||
|
||||
void entrypoint(char *buf) {
|
||||
uint64_t 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;
|
||||
if (1 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_params(3, ka, userdata, userdata_len);
|
||||
|
||||
int64_t tokens = *(int64_t*)userdata;
|
||||
if (*ka[0].tokens >= tokens) {
|
||||
*ka[0].tokens -= tokens;
|
||||
*ka[2].tokens += tokens;
|
||||
//sol_print(0, 0, *ka[0].tokens, *ka[2].tokens, tokens);
|
||||
} else {
|
||||
//sol_print(0, 0, 0xFF, *ka[0].tokens, tokens);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
9
programs/bpf/noop_c/build.sh
Executable file
9
programs/bpf/noop_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"/noop_c.bc -c "$THISDIR"/src/noop.c
|
||||
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/noop_c.o "$OUTDIR"/noop_c.bc
|
||||
|
||||
#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/noop_c.o
|
3
programs/bpf/noop_c/dump.sh
Executable file
3
programs/bpf/noop_c/dump.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/noop_c.o
|
133
programs/bpf/noop_c/src/noop.c
Normal file
133
programs/bpf/noop_c/src/noop.c
Normal file
@ -0,0 +1,133 @@
|
||||
|
||||
//#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 0;
|
||||
}
|
||||
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 = (int64_t *)src;
|
||||
src += sizeof(int64_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 1;
|
||||
}
|
||||
|
||||
|
||||
// -- 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);
|
||||
}
|
||||
|
||||
// -- Program entrypoint --
|
||||
|
||||
uint64_t entrypoint(char *buf) {
|
||||
SolKeyedAccounts ka[1];
|
||||
uint64_t userdata_len;
|
||||
uint8_t *userdata;
|
||||
|
||||
if (1 != sol_deserialize((uint8_t *)buf, 1, ka, &userdata, &userdata_len)) {
|
||||
return 0;
|
||||
}
|
||||
print_params(1, ka, userdata, userdata_len);
|
||||
return 1;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) {
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
src += sizeof(uint64_t);
|
||||
|
||||
@ -78,9 +78,55 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||
src += sizeof(uint64_t);
|
||||
*userdata = src;
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// // -- 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);
|
||||
// }
|
||||
|
||||
// -- TicTacToe --
|
||||
|
||||
// Board Coodinates
|
||||
@ -279,92 +325,48 @@ SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player,
|
||||
return Result_Ok;
|
||||
}
|
||||
|
||||
void entrypoint(uint8_t *buf) {
|
||||
SolKeyedAccounts ka[3];
|
||||
uint64_t entrypoint(uint8_t *buf) {
|
||||
SolKeyedAccounts ka[4];
|
||||
uint64_t userdata_len;
|
||||
uint8_t *userdata;
|
||||
int err = 0;
|
||||
|
||||
if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) {
|
||||
sol_panic();
|
||||
if (1 != sol_deserialize(buf, 4, ka, &userdata, &userdata_len)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sizeof(Game) > ka[1].userdata_len) {
|
||||
sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len);
|
||||
sol_panic();
|
||||
if (sizeof(Game) > ka[2].userdata_len) {
|
||||
sol_print(0, 0, 0xFF, sizeof(Game), ka[2].userdata_len);
|
||||
return 0;
|
||||
}
|
||||
Game game;
|
||||
sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len);
|
||||
sol_memcpy(&game, ka[2].userdata, ka[2].userdata_len);
|
||||
|
||||
Command command = *userdata;
|
||||
sol_print(0, 0, 0, 0, command);
|
||||
//sol_print(0, 0, 0, 0, command);
|
||||
switch (command) {
|
||||
case Command_Init:
|
||||
game_create(&game, ka[2].key);
|
||||
game_create(&game, ka[3].key);
|
||||
break;
|
||||
|
||||
case Command_Join:
|
||||
err = game_join(&game, ka[0].key, userdata[8]);
|
||||
err = game_join(&game, ka[3].key, userdata[8]);
|
||||
break;
|
||||
|
||||
case Command_KeepAlive:
|
||||
err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0);
|
||||
err = game_keep_alive(&game, ka[3].key, /*TODO*/ 0);
|
||||
break;
|
||||
|
||||
case Command_Move:
|
||||
err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]);
|
||||
err = game_next_move(&game, ka[3].key, userdata[8], userdata[9]);
|
||||
break;
|
||||
|
||||
default:
|
||||
sol_panic();
|
||||
return 0;
|
||||
}
|
||||
|
||||
sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len);
|
||||
sol_memcpy(ka[2].userdata, &game, ka[2].userdata_len);
|
||||
sol_print(0, 0, 0, err, game.state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// // -- 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);
|
||||
// }
|
||||
|
@ -5,16 +5,16 @@ use bincode::deserialize;
|
||||
use solana_program_interface::account::KeyedAccount;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||
pub extern "C" fn process(keyed_accounts: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||
let tokens: i64 = deserialize(data).unwrap();
|
||||
if infos[0].account.tokens >= tokens {
|
||||
infos[0].account.tokens -= tokens;
|
||||
infos[1].account.tokens += tokens;
|
||||
if keyed_accounts[0].account.tokens >= tokens {
|
||||
keyed_accounts[0].account.tokens -= tokens;
|
||||
keyed_accounts[1].account.tokens += tokens;
|
||||
true
|
||||
} else {
|
||||
println!(
|
||||
"Insufficient funds, asked {}, only had {}",
|
||||
tokens, infos[0].account.tokens
|
||||
tokens, keyed_accounts[0].account.tokens
|
||||
);
|
||||
false
|
||||
}
|
||||
@ -37,12 +37,12 @@ mod tests {
|
||||
accounts[1].tokens = 1;
|
||||
|
||||
{
|
||||
let mut infos: Vec<KeyedAccount> = Vec::new();
|
||||
let mut keyed_accounts: Vec<KeyedAccount> = Vec::new();
|
||||
for (key, account) in keys.iter().zip(&mut accounts).collect::<Vec<_>>() {
|
||||
infos.push(KeyedAccount { key, account });
|
||||
}
|
||||
|
||||
process(&mut infos, &data);
|
||||
process(&mut keyed_accounts, &data);
|
||||
}
|
||||
assert_eq!(0, accounts[0].tokens);
|
||||
assert_eq!(101, accounts[1].tokens);
|
||||
|
@ -3,8 +3,8 @@ extern crate solana_program_interface;
|
||||
use solana_program_interface::account::KeyedAccount;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||
println!("noop: AccountInfos: {:#?}", infos);
|
||||
println!("noop: data: {:#?}", data);
|
||||
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> bool {
|
||||
println!("noop: keyed_accounts: {:#?}", keyed_accounts);
|
||||
println!("noop: data: {:?}", data);
|
||||
true
|
||||
}
|
||||
|
20
programs/native/sobpf/Cargo.toml
Normal file
20
programs/native/sobpf/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "sobpf"
|
||||
version = "0.1.0"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.0.0"
|
||||
byteorder = "1.2.1"
|
||||
elf = "0.0.10"
|
||||
libc = "0.2.43"
|
||||
log = "0.4.2"
|
||||
rbpf = { git = "https://github.com/solana-labs/rbpf" }
|
||||
serde = "1.0.27"
|
||||
serde_derive = "1.0.27"
|
||||
solana_program_interface = { path = "../../../common" }
|
||||
|
||||
[lib]
|
||||
name = "sobpf"
|
||||
crate-type = ["cdylib"]
|
||||
|
314
programs/native/sobpf/src/bpf_verifier.rs
Normal file
314
programs/native/sobpf/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
|
||||
}
|
195
programs/native/sobpf/src/lib.rs
Normal file
195
programs/native/sobpf/src/lib.rs
Normal file
@ -0,0 +1,195 @@
|
||||
extern crate bincode;
|
||||
extern crate byteorder;
|
||||
extern crate elf;
|
||||
extern crate libc;
|
||||
extern crate rbpf;
|
||||
extern crate solana_program_interface;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod bpf_verifier;
|
||||
|
||||
use bincode::{deserialize, serialize};
|
||||
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
||||
use solana_program_interface::account::KeyedAccount;
|
||||
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std::env;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
/// Dynamic link library prefixs
|
||||
const PLATFORM_FILE_PREFIX_BPF: &str = "";
|
||||
|
||||
/// Dynamic link library file extension specific to the platform
|
||||
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
|
||||
|
||||
/// Section name
|
||||
pub const PLATFORM_SECTION_RS: &str = ".text,entrypoint";
|
||||
pub const PLATFORM_SECTION_C: &str = ".text.entrypoint";
|
||||
|
||||
fn create_path(name: &str) -> PathBuf {
|
||||
let pathbuf = {
|
||||
let current_exe = env::current_exe().unwrap();
|
||||
PathBuf::from(current_exe.parent().unwrap().parent().unwrap())
|
||||
};
|
||||
|
||||
pathbuf.join(
|
||||
PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name)
|
||||
.with_extension(PLATFORM_FILE_EXTENSION_BPF),
|
||||
)
|
||||
}
|
||||
|
||||
#[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_state(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> Vec<u8> {
|
||||
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||
|
||||
let mut v: Vec<u8> = Vec::new();
|
||||
v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
|
||||
.unwrap();
|
||||
for info in keyed_accounts.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();
|
||||
}
|
||||
v.write_u64::<LittleEndian>(data.len() as u64).unwrap();
|
||||
v.write_all(data).unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
fn deserialize_state(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) {
|
||||
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||
|
||||
let mut start = mem::size_of::<u64>();
|
||||
for info in keyed_accounts.iter_mut() {
|
||||
start += mem::size_of::<Pubkey>(); // skip pubkey
|
||||
info.account.tokens = LittleEndian::read_i64(&buffer[start..]);
|
||||
|
||||
start += mem::size_of::<u64>() // skip tokens
|
||||
+ mem::size_of::<u64>(); // skip length tag
|
||||
let end = start + info.account.userdata.len();
|
||||
info.account.userdata.clone_from_slice(&buffer[start..end]);
|
||||
|
||||
start += info.account.userdata.len() // skip userdata
|
||||
+ mem::size_of::<Pubkey>(); // skip program_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum BpfLoader {
|
||||
File { name: String },
|
||||
Bytes { bytes: Vec<u8> },
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||
if keyed_accounts[0].account.executable {
|
||||
let prog: Vec<u8>;
|
||||
if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) {
|
||||
match program {
|
||||
BpfLoader::File { name } => {
|
||||
trace!("Call Bpf with file {:?}", name);
|
||||
let path = create_path(&name);
|
||||
let file = match elf::File::open_path(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
warn!("Error opening ELF {:?}: {:?}", path, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
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 => {
|
||||
warn!("Failed to find elf section {:?}", PLATFORM_SECTION_C);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
prog = text_section.data.clone();
|
||||
}
|
||||
BpfLoader::Bytes { bytes } => {
|
||||
trace!("Call Bpf with bytes");
|
||||
prog = bytes;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("deserialize failed: {:?}", tx_data);
|
||||
return false;
|
||||
}
|
||||
trace!("Call BPF, {} Instructions", prog.len() / 8);
|
||||
|
||||
let mut vm = rbpf::EbpfVmRaw::new(&prog, Some(bpf_verifier::verifier));
|
||||
vm.register_helper(
|
||||
rbpf::helpers::BPF_TRACE_PRINTK_IDX,
|
||||
rbpf::helpers::bpf_trace_printf,
|
||||
);
|
||||
|
||||
let mut v = serialize_state(&mut keyed_accounts[1..], &tx_data);
|
||||
if 0 == vm.prog_exec(v.as_mut_slice()) {
|
||||
warn!("BPF program failed");
|
||||
return false;
|
||||
}
|
||||
deserialize_state(&mut keyed_accounts[1..], &v);
|
||||
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||
match instruction {
|
||||
LoaderInstruction::Write { offset, bytes } => {
|
||||
trace!("BPFLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||
let offset = offset as usize;
|
||||
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||
return false;
|
||||
}
|
||||
let name = match str::from_utf8(&bytes) {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(e) => {
|
||||
println!("Invalid UTF-8 sequence: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
trace!("name: {:?}", name);
|
||||
let s = serialize(&BpfLoader::File { name }).unwrap();
|
||||
keyed_accounts[0]
|
||||
.account
|
||||
.userdata
|
||||
.splice(0..s.len(), s.iter().cloned());
|
||||
}
|
||||
|
||||
LoaderInstruction::Finalize => {
|
||||
keyed_accounts[0].account.executable = true;
|
||||
keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id;
|
||||
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||
trace!(
|
||||
"BPFLoader::Finalize prog: {:?} loader {:?}",
|
||||
keyed_accounts[0].account.program_id,
|
||||
keyed_accounts[0].account.loader_program_id
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Invalid program transaction: {:?}", tx_data);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
@ -4,7 +4,11 @@ version = "0.1.0"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.0.0"
|
||||
log = "0.4.2"
|
||||
rlua = "0.15.2"
|
||||
serde = "1.0.27"
|
||||
serde_derive = "1.0.27"
|
||||
solana_program_interface = { path = "../../../common" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -1,10 +1,23 @@
|
||||
extern crate bincode;
|
||||
extern crate rlua;
|
||||
extern crate solana_program_interface;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use bincode::{deserialize, serialize};
|
||||
use rlua::{Lua, Result, Table};
|
||||
use solana_program_interface::account::KeyedAccount;
|
||||
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||
use std::str;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum LuaLoader {
|
||||
File { name: String },
|
||||
Bytes { bytes: Vec<u8> },
|
||||
}
|
||||
|
||||
/// Make KeyAccount values available to Lua.
|
||||
fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
let accounts = lua.create_table()?;
|
||||
@ -21,7 +34,7 @@ fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Resul
|
||||
}
|
||||
|
||||
/// Commit the new KeyedAccount values.
|
||||
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec<KeyedAccount>) -> Result<()> {
|
||||
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut [KeyedAccount]) -> Result<()> {
|
||||
let globals = lua.globals();
|
||||
let accounts: Table = globals.get(name)?;
|
||||
for (i, keyed_account) in keyed_accounts.into_iter().enumerate() {
|
||||
@ -33,7 +46,7 @@ fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec<KeyedAccount>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_lua(keyed_accounts: &mut Vec<KeyedAccount>, code: &str, data: &[u8]) -> Result<()> {
|
||||
fn run_lua(keyed_accounts: &mut [KeyedAccount], code: &str, data: &[u8]) -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
let data_str = lua.create_string(data)?;
|
||||
@ -45,10 +58,67 @@ fn run_lua(keyed_accounts: &mut Vec<KeyedAccount>, code: &str, data: &[u8]) -> R
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process(keyed_accounts: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||
let code_data = keyed_accounts[0].account.userdata.clone();
|
||||
let code = str::from_utf8(&code_data).unwrap();
|
||||
run_lua(keyed_accounts, &code, data).unwrap();
|
||||
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||
if keyed_accounts[0].account.executable {
|
||||
let prog: Vec<u8>;
|
||||
if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) {
|
||||
match program {
|
||||
LuaLoader::File { name } => {
|
||||
trace!("Call Lua with file {:?}", name);
|
||||
panic!("Not supported");
|
||||
}
|
||||
LuaLoader::Bytes { bytes } => {
|
||||
trace!("Call Lua with bytes, code size {}", bytes.len());
|
||||
prog = bytes;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("deserialize failed: {:?}", tx_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
let code = str::from_utf8(&prog).unwrap();
|
||||
match run_lua(&mut keyed_accounts[1..], &code, tx_data) {
|
||||
Ok(()) => {
|
||||
trace!("Lua success");
|
||||
return true;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Lua Error: {:#?}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||
match instruction {
|
||||
LoaderInstruction::Write { offset, bytes } => {
|
||||
trace!("LuaLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||
let offset = offset as usize;
|
||||
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||
warn!("program overflow offset {} len {}", offset, bytes.len());
|
||||
return false;
|
||||
}
|
||||
let s = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||
keyed_accounts[0]
|
||||
.account
|
||||
.userdata
|
||||
.splice(0..s.len(), s.iter().cloned());
|
||||
}
|
||||
|
||||
LoaderInstruction::Finalize => {
|
||||
keyed_accounts[0].account.executable = true;
|
||||
keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id;
|
||||
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||
trace!(
|
||||
"LuaLoader::Finalize prog: {:?} loader {:?}",
|
||||
keyed_accounts[0].account.program_id,
|
||||
keyed_accounts[0].account.loader_program_id
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Invalid program transaction: {:?}", tx_data);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@ -98,12 +168,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_move_funds_with_lua_via_process() {
|
||||
let userdata = r#"
|
||||
let bytes = r#"
|
||||
local tokens, _ = string.unpack("I", data)
|
||||
accounts[1].tokens = accounts[1].tokens - tokens
|
||||
accounts[2].tokens = accounts[2].tokens + tokens
|
||||
"#.as_bytes()
|
||||
.to_vec();
|
||||
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||
|
||||
let alice_pubkey = Pubkey::default();
|
||||
let bob_pubkey = Pubkey::default();
|
||||
@ -111,89 +182,26 @@ mod tests {
|
||||
|
||||
let mut accounts = [
|
||||
(
|
||||
alice_pubkey,
|
||||
Pubkey::default(),
|
||||
Account {
|
||||
tokens: 100,
|
||||
tokens: 1,
|
||||
userdata,
|
||||
program_id,
|
||||
executable: true,
|
||||
loader_program_id: Pubkey::default(),
|
||||
},
|
||||
),
|
||||
(alice_pubkey, Account::new(100, 0, program_id)),
|
||||
(bob_pubkey, Account::new(1, 0, program_id)),
|
||||
];
|
||||
let data = serialize(&10u64).unwrap();
|
||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||
assert_eq!(accounts[0].1.tokens, 90);
|
||||
assert_eq!(accounts[1].1.tokens, 11);
|
||||
assert_eq!(accounts[1].1.tokens, 90);
|
||||
assert_eq!(accounts[2].1.tokens, 11);
|
||||
|
||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||
assert_eq!(accounts[0].1.tokens, 80);
|
||||
assert_eq!(accounts[1].1.tokens, 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_funds_with_lua_via_process_and_terminate() {
|
||||
let userdata = r#"
|
||||
local tokens, _ = string.unpack("I", data)
|
||||
accounts[1].tokens = accounts[1].tokens - tokens
|
||||
accounts[2].tokens = accounts[2].tokens + tokens
|
||||
accounts[1].userdata = ""
|
||||
"#.as_bytes()
|
||||
.to_vec();
|
||||
|
||||
let alice_pubkey = Pubkey::default();
|
||||
let bob_pubkey = Pubkey::default();
|
||||
let program_id = Pubkey::default();
|
||||
|
||||
let mut accounts = [
|
||||
(
|
||||
alice_pubkey,
|
||||
Account {
|
||||
tokens: 100,
|
||||
userdata,
|
||||
program_id,
|
||||
},
|
||||
),
|
||||
(bob_pubkey, Account::new(1, 0, program_id)),
|
||||
];
|
||||
let data = serialize(&10).unwrap();
|
||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||
assert_eq!(accounts[0].1.tokens, 90);
|
||||
assert_eq!(accounts[1].1.tokens, 11);
|
||||
|
||||
// Verify the program modified itself to a no-op.
|
||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||
assert_eq!(accounts[0].1.tokens, 90);
|
||||
assert_eq!(accounts[1].1.tokens, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_abort_tx_with_lua() {
|
||||
let userdata = r#"
|
||||
if data == accounts[1].key then
|
||||
accounts[1].userdata = ""
|
||||
end
|
||||
"#.as_bytes()
|
||||
.to_vec();
|
||||
|
||||
let alice_pubkey = Pubkey::default();
|
||||
let program_id = Pubkey::default();
|
||||
|
||||
let mut accounts = [(
|
||||
alice_pubkey,
|
||||
Account {
|
||||
tokens: 100,
|
||||
userdata,
|
||||
program_id,
|
||||
},
|
||||
)];
|
||||
|
||||
// Abort
|
||||
let data = alice_pubkey.to_string().as_bytes().to_vec();
|
||||
|
||||
assert_ne!(accounts[0].1.userdata, vec![]);
|
||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||
assert_eq!(accounts[0].1.tokens, 100);
|
||||
assert_eq!(accounts[0].1.userdata, vec![]);
|
||||
assert_eq!(accounts[1].1.tokens, 80);
|
||||
assert_eq!(accounts[2].1.tokens, 21);
|
||||
}
|
||||
|
||||
fn read_test_file(name: &str) -> Vec<u8> {
|
||||
@ -207,27 +215,35 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_load_lua_library() {
|
||||
let userdata = r#"
|
||||
let bytes = r#"
|
||||
local serialize = load(accounts[2].userdata)().serialize
|
||||
accounts[3].userdata = serialize({a=1, b=2, c=3}, nil, "s")
|
||||
"#.as_bytes()
|
||||
.to_vec();
|
||||
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||
|
||||
let program_id = Pubkey::default();
|
||||
|
||||
let alice_account = Account {
|
||||
tokens: 100,
|
||||
let program_account = Account {
|
||||
tokens: 1,
|
||||
userdata,
|
||||
program_id,
|
||||
executable: true,
|
||||
loader_program_id: Pubkey::default(),
|
||||
};
|
||||
|
||||
let alice_account = Account::new(100, 0, program_id);
|
||||
|
||||
let serialize_account = Account {
|
||||
tokens: 100,
|
||||
userdata: read_test_file("serialize.lua"),
|
||||
program_id,
|
||||
executable: false,
|
||||
loader_program_id: Pubkey::default(),
|
||||
};
|
||||
|
||||
let mut accounts = [
|
||||
(Pubkey::default(), program_account),
|
||||
(Pubkey::default(), alice_account),
|
||||
(Pubkey::default(), serialize_account),
|
||||
(Pubkey::default(), Account::new(1, 0, program_id)),
|
||||
@ -238,7 +254,7 @@ mod tests {
|
||||
|
||||
// Verify deterministic ordering of a serialized Lua table.
|
||||
assert_eq!(
|
||||
str::from_utf8(&keyed_accounts[2].account.userdata).unwrap(),
|
||||
str::from_utf8(&keyed_accounts[3].account.userdata).unwrap(),
|
||||
"{a=1,b=2,c=3}"
|
||||
);
|
||||
}
|
||||
@ -255,20 +271,36 @@ mod tests {
|
||||
let dan_pubkey = Pubkey::new(&[5; 32]);
|
||||
let erin_pubkey = Pubkey::new(&[6; 32]);
|
||||
|
||||
let userdata = serialize(&LuaLoader::Bytes {
|
||||
bytes: read_test_file("multisig.lua"),
|
||||
}).unwrap();
|
||||
let program_account = Account {
|
||||
tokens: 1,
|
||||
userdata,
|
||||
program_id,
|
||||
executable: true,
|
||||
loader_program_id: Pubkey::default(),
|
||||
};
|
||||
|
||||
let alice_account = Account {
|
||||
tokens: 100,
|
||||
userdata: read_test_file("multisig.lua"),
|
||||
userdata: Vec::new(),
|
||||
program_id,
|
||||
executable: true,
|
||||
loader_program_id: Pubkey::default(),
|
||||
};
|
||||
|
||||
let serialize_account = Account {
|
||||
tokens: 100,
|
||||
userdata: read_test_file("serialize.lua"),
|
||||
program_id,
|
||||
executable: true,
|
||||
loader_program_id: Pubkey::default(),
|
||||
};
|
||||
|
||||
let mut accounts = [
|
||||
(alice_pubkey, alice_account), // The payer and where the program is stored.
|
||||
(Pubkey::default(), program_account), // Account holding the program
|
||||
(alice_pubkey, alice_account), // The payer
|
||||
(serialize_pubkey, serialize_account), // Where the serialize library is stored.
|
||||
(state_pubkey, Account::new(1, 0, program_id)), // Where program state is stored.
|
||||
(bob_pubkey, Account::new(1, 0, program_id)), // The payee once M signatures are collected.
|
||||
@ -282,18 +314,18 @@ mod tests {
|
||||
.to_vec();
|
||||
|
||||
process(&mut keyed_accounts, &data);
|
||||
assert_eq!(keyed_accounts[3].account.tokens, 1);
|
||||
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
||||
|
||||
let data = format!(r#""{}""#, carol_pubkey).into_bytes();
|
||||
process(&mut keyed_accounts, &data);
|
||||
assert_eq!(keyed_accounts[3].account.tokens, 1);
|
||||
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
||||
|
||||
let data = format!(r#""{}""#, dan_pubkey).into_bytes();
|
||||
process(&mut keyed_accounts, &data);
|
||||
assert_eq!(keyed_accounts[3].account.tokens, 101); // Pay day!
|
||||
assert_eq!(keyed_accounts[4].account.tokens, 101); // Pay day!
|
||||
|
||||
let data = format!(r#""{}""#, erin_pubkey).into_bytes();
|
||||
process(&mut keyed_accounts, &data);
|
||||
assert_eq!(keyed_accounts[3].account.tokens, 101); // No change!
|
||||
assert_eq!(keyed_accounts[4].account.tokens, 101); // No change!
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user