Add context-free Lua smart contracts

lua_State is not preserved across runs and account userdata is not converted into
Lua values. All this allows us to do is manipulate the number of tokens
in each account and DoS the Fullnode with those three little words,
"repeat until false".

Why bother? Research. rlua's project goals are well-aligned with the LAMPORT runtime.

What's next:
* rlua to add security limits, such as number of instructions executed
* Add a way to deserialize Account::userdata OR use Account::program_id
  to look up a metatable for lua_newuserdata().
This commit is contained in:
Greg Fitzgerald 2018-10-09 06:29:09 -06:00
parent 95701114e3
commit 0403299728
4 changed files with 138 additions and 0 deletions

View File

@ -109,6 +109,7 @@ tokio-codec = "0.1"
untrusted = "0.6.2"
[dev-dependencies]
solua = { path = "programs/native/solua" }
move_funds = { path = "programs/native/move_funds" }
noop = { path = "programs/native/noop" }
@ -135,6 +136,7 @@ name = "chacha"
members = [
".",
"common",
"programs/native/solua",
"programs/native/move_funds",
"programs/native/noop",
"programs/bpf/noop_rust",

View File

@ -29,3 +29,19 @@ pub struct KeyedAccount<'a> {
pub key: &'a Pubkey,
pub account: &'a mut Account,
}
impl<'a> From<(&'a Pubkey, &'a mut Account)> for KeyedAccount<'a> {
fn from((key, account): (&'a Pubkey, &'a mut Account)) -> Self {
KeyedAccount { key, account }
}
}
impl<'a> From<&'a mut (Pubkey, Account)> for KeyedAccount<'a> {
fn from((key, account): &'a mut (Pubkey, Account)) -> Self {
KeyedAccount { key, account }
}
}
pub fn create_keyed_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAccount> {
accounts.iter_mut().map(Into::into).collect()
}

View File

@ -0,0 +1,14 @@
[package]
name = "solua"
version = "0.1.0"
authors = ["Solana Maintainers <maintainers@solana.com>"]
[dependencies]
bincode = "1.0.0"
rlua = "0.15.2"
solana_program_interface = { path = "../../../common" }
[lib]
name = "solua"
crate-type = ["dylib"]

View File

@ -0,0 +1,106 @@
extern crate bincode;
extern crate rlua;
extern crate solana_program_interface;
use bincode::deserialize;
use rlua::{Lua, Result, Table};
use solana_program_interface::account::KeyedAccount;
/// 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()?;
account.set("tokens", keyed_account.account.tokens)?;
accounts.set(i + 1, account)?;
}
let globals = lua.globals();
globals.set(name, accounts)
}
/// Commit the new KeyedAccount values.
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec<KeyedAccount>) -> Result<()> {
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")?;
}
Ok(())
}
fn run_lua(keyed_accounts: &mut Vec<KeyedAccount>, code: &str) -> Result<()> {
let lua = Lua::new();
set_accounts(&lua, "accounts", keyed_accounts)?;
lua.exec::<_, ()>(code, None)?;
update_accounts(&lua, "accounts", keyed_accounts)
}
#[no_mangle]
pub extern "C" fn process(keyed_accounts: &mut Vec<KeyedAccount>, data: &[u8]) {
let code: String = deserialize(&data).unwrap();
run_lua(keyed_accounts, &code).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::serialize;
use solana_program_interface::account::{create_keyed_accounts, Account};
use solana_program_interface::pubkey::Pubkey;
#[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;
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())];
run_lua(&mut create_keyed_accounts(&mut accounts), code)?;
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())];
assert!(run_lua(&mut create_keyed_accounts(&mut accounts), code).is_err());
}
#[test]
fn test_move_funds_with_lua_via_process() {
let data: Vec<u8> = serialize(
r#"
local tokens = 100
accounts[1].tokens = accounts[1].tokens - tokens
accounts[2].tokens = accounts[2].tokens + tokens
"#,
).unwrap();
let alice_pubkey = Pubkey::default();
let bob_pubkey = Pubkey::default();
let program_id = Pubkey::default();
let mut accounts = [
(alice_pubkey, Account::new(100, 0, program_id)),
(bob_pubkey, Account::new(1, 0, program_id)),
];
process(&mut create_keyed_accounts(&mut accounts), &data);
assert_eq!(accounts[0].1.tokens, 0);
assert_eq!(accounts[1].1.tokens, 101);
}
}