2018-10-16 09:43:49 -07:00
|
|
|
extern crate bincode;
|
2018-10-16 16:33:31 -07:00
|
|
|
extern crate env_logger;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2018-10-09 05:29:09 -07:00
|
|
|
extern crate rlua;
|
2018-10-25 11:13:08 -07:00
|
|
|
extern crate solana_sdk;
|
2018-10-09 05:29:09 -07:00
|
|
|
|
2018-10-19 18:28:38 -07:00
|
|
|
use bincode::deserialize;
|
2018-10-09 05:29:09 -07:00
|
|
|
use rlua::{Lua, Result, Table};
|
2018-10-25 11:13:08 -07:00
|
|
|
use solana_sdk::account::KeyedAccount;
|
|
|
|
use solana_sdk::loader_instruction::LoaderInstruction;
|
2018-10-09 08:45:09 -07:00
|
|
|
use std::str;
|
2018-10-16 16:33:31 -07:00
|
|
|
use std::sync::{Once, ONCE_INIT};
|
2018-10-09 05:29:09 -07:00
|
|
|
|
|
|
|
/// Make KeyAccount values available to Lua.
|
|
|
|
fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
|
|
|
let accounts = lua.create_table()?;
|
|
|
|
for (i, keyed_account) in keyed_accounts.iter().enumerate() {
|
|
|
|
let account = lua.create_table()?;
|
2018-10-09 09:55:22 -07:00
|
|
|
account.set("key", keyed_account.key.to_string())?;
|
2018-10-09 05:29:09 -07:00
|
|
|
account.set("tokens", keyed_account.account.tokens)?;
|
2018-10-09 08:45:09 -07:00
|
|
|
let data_str = lua.create_string(&keyed_account.account.userdata)?;
|
|
|
|
account.set("userdata", data_str)?;
|
2018-10-09 05:29:09 -07:00
|
|
|
accounts.set(i + 1, account)?;
|
|
|
|
}
|
|
|
|
let globals = lua.globals();
|
|
|
|
globals.set(name, accounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Commit the new KeyedAccount values.
|
2018-10-16 09:43:49 -07:00
|
|
|
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut [KeyedAccount]) -> Result<()> {
|
2018-10-09 05:29:09 -07:00
|
|
|
let globals = lua.globals();
|
|
|
|
let accounts: Table = globals.get(name)?;
|
|
|
|
for (i, keyed_account) in keyed_accounts.into_iter().enumerate() {
|
|
|
|
let account: Table = accounts.get(i + 1)?;
|
|
|
|
keyed_account.account.tokens = account.get("tokens")?;
|
2018-10-09 08:45:09 -07:00
|
|
|
let data_str: rlua::String = account.get("userdata")?;
|
|
|
|
keyed_account.account.userdata = data_str.as_bytes().to_vec();
|
2018-10-09 05:29:09 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-10-16 09:43:49 -07:00
|
|
|
fn run_lua(keyed_accounts: &mut [KeyedAccount], code: &str, data: &[u8]) -> Result<()> {
|
2018-10-09 05:29:09 -07:00
|
|
|
let lua = Lua::new();
|
2018-10-09 07:02:00 -07:00
|
|
|
let globals = lua.globals();
|
|
|
|
let data_str = lua.create_string(data)?;
|
|
|
|
globals.set("data", data_str)?;
|
|
|
|
|
2018-10-09 05:29:09 -07:00
|
|
|
set_accounts(&lua, "accounts", keyed_accounts)?;
|
|
|
|
lua.exec::<_, ()>(code, None)?;
|
|
|
|
update_accounts(&lua, "accounts", keyed_accounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[no_mangle]
|
2018-10-16 09:43:49 -07:00
|
|
|
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
2018-10-16 16:33:31 -07:00
|
|
|
static INIT: Once = ONCE_INIT;
|
|
|
|
INIT.call_once(|| {
|
|
|
|
// env_logger can only be initialized once
|
|
|
|
env_logger::init();
|
|
|
|
});
|
|
|
|
|
2018-10-16 09:43:49 -07:00
|
|
|
if keyed_accounts[0].account.executable {
|
2018-10-19 18:28:38 -07:00
|
|
|
let code = keyed_accounts[0].account.userdata.clone();
|
|
|
|
let code = str::from_utf8(&code).unwrap();
|
2018-10-16 09:43:49 -07:00
|
|
|
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 } => {
|
|
|
|
let offset = offset as usize;
|
2018-10-19 18:28:38 -07:00
|
|
|
let len = bytes.len();
|
|
|
|
trace!("LuaLoader::Write offset {} length {:?}", offset, len);
|
|
|
|
if keyed_accounts[0].account.userdata.len() < offset + len {
|
2018-10-30 11:07:11 -07:00
|
|
|
warn!(
|
|
|
|
"Write overflow {} < {}",
|
2018-10-19 18:28:38 -07:00
|
|
|
keyed_accounts[0].account.userdata.len(),
|
|
|
|
offset + len
|
|
|
|
);
|
2018-10-16 09:43:49 -07:00
|
|
|
return false;
|
|
|
|
}
|
2018-10-19 18:28:38 -07:00
|
|
|
keyed_accounts[0].account.userdata[offset..offset + len].copy_from_slice(&bytes);
|
2018-10-16 09:43:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
LoaderInstruction::Finalize => {
|
|
|
|
keyed_accounts[0].account.executable = true;
|
2018-10-18 10:33:30 -07:00
|
|
|
trace!("LuaLoader::Finalize prog: {:?}", keyed_accounts[0].key);
|
2018-10-16 09:43:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn!("Invalid program transaction: {:?}", tx_data);
|
|
|
|
return false;
|
|
|
|
}
|
2018-10-13 19:31:16 -07:00
|
|
|
true
|
2018-10-09 05:29:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-10-09 08:45:09 -07:00
|
|
|
extern crate bincode;
|
|
|
|
|
|
|
|
use self::bincode::serialize;
|
2018-10-09 05:29:09 -07:00
|
|
|
use super::*;
|
2018-10-25 11:13:08 -07:00
|
|
|
use solana_sdk::account::{create_keyed_accounts, Account};
|
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2018-10-09 14:00:30 -07:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::path::PathBuf;
|
2018-10-09 05:29:09 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_accounts() -> Result<()> {
|
|
|
|
let mut accounts = [(Pubkey::default(), Account::default())];
|
|
|
|
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
|
|
|
let lua = Lua::new();
|
|
|
|
set_accounts(&lua, "xs", &keyed_accounts)?;
|
|
|
|
keyed_accounts[0].account.tokens = 42;
|
2018-10-09 08:45:09 -07:00
|
|
|
keyed_accounts[0].account.userdata = vec![];
|
2018-10-09 05:29:09 -07:00
|
|
|
update_accounts(&lua, "xs", &mut keyed_accounts)?;
|
|
|
|
|
|
|
|
// Ensure update_accounts() overwrites the local value 42.
|
|
|
|
assert_eq!(keyed_accounts[0].account.tokens, 0);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_credit_with_lua() -> Result<()> {
|
|
|
|
let code = r#"accounts[1].tokens = accounts[1].tokens + 1"#;
|
|
|
|
let mut accounts = [(Pubkey::default(), Account::default())];
|
2018-10-09 07:02:00 -07:00
|
|
|
run_lua(&mut create_keyed_accounts(&mut accounts), code, &[])?;
|
2018-10-09 05:29:09 -07:00
|
|
|
assert_eq!(accounts[0].1.tokens, 1);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_error_with_lua() {
|
|
|
|
let code = r#"accounts[1].tokens += 1"#;
|
|
|
|
let mut accounts = [(Pubkey::default(), Account::default())];
|
2018-10-09 07:02:00 -07:00
|
|
|
assert!(run_lua(&mut create_keyed_accounts(&mut accounts), code, &[]).is_err());
|
2018-10-09 05:29:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_move_funds_with_lua_via_process() {
|
2018-10-16 09:43:49 -07:00
|
|
|
let bytes = r#"
|
2018-10-09 07:02:00 -07:00
|
|
|
local tokens, _ = string.unpack("I", data)
|
2018-10-09 05:29:09 -07:00
|
|
|
accounts[1].tokens = accounts[1].tokens - tokens
|
|
|
|
accounts[2].tokens = accounts[2].tokens + tokens
|
2018-10-09 08:45:09 -07:00
|
|
|
"#.as_bytes()
|
|
|
|
.to_vec();
|
2018-10-16 09:43:49 -07:00
|
|
|
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
2018-10-09 05:29:09 -07:00
|
|
|
|
|
|
|
let alice_pubkey = Pubkey::default();
|
|
|
|
let bob_pubkey = Pubkey::default();
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
|
|
|
|
let mut accounts = [
|
2018-10-09 07:02:00 -07:00
|
|
|
(
|
2018-10-16 09:43:49 -07:00
|
|
|
Pubkey::default(),
|
2018-10-09 07:02:00 -07:00
|
|
|
Account {
|
2018-10-16 09:43:49 -07:00
|
|
|
tokens: 1,
|
2018-10-09 07:02:00 -07:00
|
|
|
userdata,
|
|
|
|
program_id,
|
2018-10-16 09:43:49 -07:00
|
|
|
executable: true,
|
|
|
|
loader_program_id: Pubkey::default(),
|
2018-10-09 07:02:00 -07:00
|
|
|
},
|
|
|
|
),
|
2018-10-16 09:43:49 -07:00
|
|
|
(alice_pubkey, Account::new(100, 0, program_id)),
|
2018-10-09 05:29:09 -07:00
|
|
|
(bob_pubkey, Account::new(1, 0, program_id)),
|
|
|
|
];
|
2018-10-09 08:45:09 -07:00
|
|
|
let data = serialize(&10u64).unwrap();
|
2018-10-09 05:29:09 -07:00
|
|
|
process(&mut create_keyed_accounts(&mut accounts), &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(accounts[1].1.tokens, 90);
|
|
|
|
assert_eq!(accounts[2].1.tokens, 11);
|
2018-10-09 05:29:09 -07:00
|
|
|
|
2018-10-09 07:02:00 -07:00
|
|
|
process(&mut create_keyed_accounts(&mut accounts), &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(accounts[1].1.tokens, 80);
|
|
|
|
assert_eq!(accounts[2].1.tokens, 21);
|
2018-10-09 09:55:22 -07:00
|
|
|
}
|
2018-10-09 14:00:30 -07:00
|
|
|
|
|
|
|
fn read_test_file(name: &str) -> Vec<u8> {
|
|
|
|
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
path.push(name);
|
|
|
|
let mut file = File::open(path).unwrap();
|
|
|
|
let mut contents = vec![];
|
|
|
|
file.read_to_end(&mut contents).unwrap();
|
|
|
|
contents
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_load_lua_library() {
|
2018-10-16 09:43:49 -07:00
|
|
|
let bytes = r#"
|
2018-10-09 14:00:30 -07:00
|
|
|
local serialize = load(accounts[2].userdata)().serialize
|
|
|
|
accounts[3].userdata = serialize({a=1, b=2, c=3}, nil, "s")
|
|
|
|
"#.as_bytes()
|
|
|
|
.to_vec();
|
2018-10-16 09:43:49 -07:00
|
|
|
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
2018-10-09 14:00:30 -07:00
|
|
|
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
|
2018-10-16 09:43:49 -07:00
|
|
|
let program_account = Account {
|
|
|
|
tokens: 1,
|
2018-10-09 14:00:30 -07:00
|
|
|
userdata,
|
|
|
|
program_id,
|
2018-10-16 09:43:49 -07:00
|
|
|
executable: true,
|
|
|
|
loader_program_id: Pubkey::default(),
|
2018-10-09 14:00:30 -07:00
|
|
|
};
|
|
|
|
|
2018-10-16 09:43:49 -07:00
|
|
|
let alice_account = Account::new(100, 0, program_id);
|
|
|
|
|
2018-10-09 14:00:30 -07:00
|
|
|
let serialize_account = Account {
|
|
|
|
tokens: 100,
|
|
|
|
userdata: read_test_file("serialize.lua"),
|
|
|
|
program_id,
|
2018-10-16 09:43:49 -07:00
|
|
|
executable: false,
|
|
|
|
loader_program_id: Pubkey::default(),
|
2018-10-09 14:00:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut accounts = [
|
2018-10-16 09:43:49 -07:00
|
|
|
(Pubkey::default(), program_account),
|
2018-10-09 14:00:30 -07:00
|
|
|
(Pubkey::default(), alice_account),
|
|
|
|
(Pubkey::default(), serialize_account),
|
|
|
|
(Pubkey::default(), Account::new(1, 0, program_id)),
|
|
|
|
];
|
|
|
|
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
|
|
|
|
|
|
|
process(&mut keyed_accounts, &[]);
|
|
|
|
|
|
|
|
// Verify deterministic ordering of a serialized Lua table.
|
|
|
|
assert_eq!(
|
2018-10-16 09:43:49 -07:00
|
|
|
str::from_utf8(&keyed_accounts[3].account.userdata).unwrap(),
|
2018-10-09 14:00:30 -07:00
|
|
|
"{a=1,b=2,c=3}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_lua_multisig() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
|
|
|
|
let alice_pubkey = Pubkey::new(&[0; 32]);
|
|
|
|
let serialize_pubkey = Pubkey::new(&[1; 32]);
|
|
|
|
let state_pubkey = Pubkey::new(&[2; 32]);
|
|
|
|
let bob_pubkey = Pubkey::new(&[3; 32]);
|
|
|
|
let carol_pubkey = Pubkey::new(&[4; 32]);
|
|
|
|
let dan_pubkey = Pubkey::new(&[5; 32]);
|
|
|
|
let erin_pubkey = Pubkey::new(&[6; 32]);
|
|
|
|
|
2018-10-16 09:43:49 -07:00
|
|
|
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(),
|
|
|
|
};
|
|
|
|
|
2018-10-09 14:00:30 -07:00
|
|
|
let alice_account = Account {
|
|
|
|
tokens: 100,
|
2018-10-16 09:43:49 -07:00
|
|
|
userdata: Vec::new(),
|
2018-10-09 14:00:30 -07:00
|
|
|
program_id,
|
2018-10-16 09:43:49 -07:00
|
|
|
executable: true,
|
|
|
|
loader_program_id: Pubkey::default(),
|
2018-10-09 14:00:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let serialize_account = Account {
|
|
|
|
tokens: 100,
|
|
|
|
userdata: read_test_file("serialize.lua"),
|
|
|
|
program_id,
|
2018-10-16 09:43:49 -07:00
|
|
|
executable: true,
|
|
|
|
loader_program_id: Pubkey::default(),
|
2018-10-09 14:00:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut accounts = [
|
2018-10-16 09:43:49 -07:00
|
|
|
(Pubkey::default(), program_account), // Account holding the program
|
|
|
|
(alice_pubkey, alice_account), // The payer
|
2018-10-09 14:00:30 -07:00
|
|
|
(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.
|
|
|
|
];
|
|
|
|
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
|
|
|
|
|
|
|
let data = format!(
|
|
|
|
r#"{{m=2, n={{"{}","{}","{}"}}, tokens=100}}"#,
|
|
|
|
carol_pubkey, dan_pubkey, erin_pubkey
|
|
|
|
).as_bytes()
|
|
|
|
.to_vec();
|
|
|
|
|
|
|
|
process(&mut keyed_accounts, &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
2018-10-09 14:00:30 -07:00
|
|
|
|
|
|
|
let data = format!(r#""{}""#, carol_pubkey).into_bytes();
|
|
|
|
process(&mut keyed_accounts, &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
2018-10-09 14:00:30 -07:00
|
|
|
|
|
|
|
let data = format!(r#""{}""#, dan_pubkey).into_bytes();
|
|
|
|
process(&mut keyed_accounts, &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(keyed_accounts[4].account.tokens, 101); // Pay day!
|
2018-10-09 14:00:30 -07:00
|
|
|
|
|
|
|
let data = format!(r#""{}""#, erin_pubkey).into_bytes();
|
|
|
|
process(&mut keyed_accounts, &data);
|
2018-10-16 09:43:49 -07:00
|
|
|
assert_eq!(keyed_accounts[4].account.tokens, 101); // No change!
|
2018-10-09 14:00:30 -07:00
|
|
|
}
|
2018-10-09 05:29:09 -07:00
|
|
|
}
|