From 04032997285fa5a2e63abe9231e226c2d29c3d3a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 9 Oct 2018 06:29:09 -0600 Subject: [PATCH] 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(). --- Cargo.toml | 2 + common/src/account.rs | 16 +++++ programs/native/solua/Cargo.toml | 14 ++++ programs/native/solua/src/lib.rs | 106 +++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 programs/native/solua/Cargo.toml create mode 100644 programs/native/solua/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e65a52114..513c733c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/common/src/account.rs b/common/src/account.rs index a818e85a6..5abd64200 100644 --- a/common/src/account.rs +++ b/common/src/account.rs @@ -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 { + accounts.iter_mut().map(Into::into).collect() +} diff --git a/programs/native/solua/Cargo.toml b/programs/native/solua/Cargo.toml new file mode 100644 index 000000000..806940209 --- /dev/null +++ b/programs/native/solua/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "solua" +version = "0.1.0" +authors = ["Solana Maintainers "] + +[dependencies] +bincode = "1.0.0" +rlua = "0.15.2" +solana_program_interface = { path = "../../../common" } + +[lib] +name = "solua" +crate-type = ["dylib"] + diff --git a/programs/native/solua/src/lib.rs b/programs/native/solua/src/lib.rs new file mode 100644 index 000000000..dc62bacb3 --- /dev/null +++ b/programs/native/solua/src/lib.rs @@ -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) -> 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, 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, 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 = 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); + } +}