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); + } +}