diff --git a/Cargo.toml b/Cargo.toml index 95ea3631ea..98be142503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,9 +109,10 @@ sys-info = "0.5.6" tokio = "0.1" tokio-codec = "0.1" untrusted = "0.6.2" -solana-noop = { path = "programs/native/noop", version = "0.11.0" } solana-bpfloader = { path = "programs/native/bpf_loader", version = "0.11.0" } +solana-erc20 = { path = "programs/native/erc20", version = "0.11.0" } solana-lualoader = { path = "programs/native/lua_loader", version = "0.11.0" } +solana-noop = { path = "programs/native/noop", version = "0.11.0" } [[bench]] name = "bank" @@ -136,8 +137,9 @@ name = "chacha" members = [ ".", "sdk", - "programs/native/noop", - "programs/native/bpf_loader", - "programs/native/lua_loader", "programs/bpf/rust/noop", + "programs/native/bpf_loader", + "programs/native/erc20", + "programs/native/lua_loader", + "programs/native/noop", ] diff --git a/programs/native/erc20/Cargo.toml b/programs/native/erc20/Cargo.toml new file mode 100644 index 0000000000..aaa950a186 --- /dev/null +++ b/programs/native/erc20/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "solana-erc20" +version = "0.11.0" +description = "Solana reference erc20 program" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" + +[dependencies] +bincode = "1.0.0" +env_logger = "0.5.12" +log = "0.4.2" +serde = "1.0.27" +serde_derive = "1.0.27" +solana-sdk = { path = "../../../sdk", version = "0.11.0" } + +[lib] +name = "solana_erc20" +crate-type = ["cdylib"] + diff --git a/programs/native/erc20/src/lib.rs b/programs/native/erc20/src/lib.rs new file mode 100644 index 0000000000..cca6d689a1 --- /dev/null +++ b/programs/native/erc20/src/lib.rs @@ -0,0 +1,30 @@ +//! The `erc20` library implements a generic erc20-like token + +extern crate bincode; +extern crate env_logger; +#[macro_use] +extern crate log; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate solana_sdk; + +use solana_sdk::account::KeyedAccount; +use std::sync::{Once, ONCE_INIT}; + +mod token_program; + +#[no_mangle] +pub extern "C" fn process(info: &mut [KeyedAccount], input: &[u8]) -> bool { + // env_logger can only be initialized once + static INIT: Once = ONCE_INIT; + INIT.call_once(env_logger::init); + + match token_program::TokenProgram::process(info, input) { + Err(err) => { + error!("error: {:?}", err); + false + } + Ok(_) => true, + } +} diff --git a/programs/native/erc20/src/token_program.rs b/programs/native/erc20/src/token_program.rs new file mode 100644 index 0000000000..fd7bb1ca39 --- /dev/null +++ b/programs/native/erc20/src/token_program.rs @@ -0,0 +1,540 @@ +//! ERC20-like Token + +use bincode; + +use solana_sdk::account::KeyedAccount; +use solana_sdk::pubkey::Pubkey; +use std; + +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidArgument, + InsufficentFunds, + NotOwner, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error") + } +} +impl std::error::Error for Error {} + +pub type Result = std::result::Result; + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct TokenInfo { + /** + * Total supply of tokens + */ + supply: u64, + + /** + * Number of base 10 digits to the right of the decimal place in the total supply + */ + decimals: u8, + + /** + * Descriptive name of this token + */ + name: String, + + /** + * Symbol for this token + */ + symbol: String, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct TokenAccountDelegateInfo { + /** + * The source account for the tokens + */ + source: Pubkey, + + /** + * The original amount that this delegate account was authorized to spend up to + */ + original_amount: u64, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct TokenAccountInfo { + /** + * The kind of token this account holds + */ + token: Pubkey, + + /** + * Owner of this account + */ + owner: Pubkey, + + /** + * Amount of tokens this account holds + */ + amount: u64, + + /** + * If `delegate` None, `amount` belongs to this account. + * If `delegate` is Option<_>, `amount` represents the remaining allowance + * of tokens that may be transferred from the `source` account. + */ + delegate: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +enum Command { + NewToken(TokenInfo), + NewTokenAccount, + Transfer(u64), + Approve(u64), + SetOwner, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub enum TokenProgram { + Unallocated, + Token(TokenInfo), + Account(TokenAccountInfo), + Invalid, +} +impl Default for TokenProgram { + fn default() -> TokenProgram { + TokenProgram::Unallocated + } +} + +impl TokenProgram { + #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + fn map_to_invalid_args(err: std::boxed::Box) -> Error { + warn!("invalid argument: {:?}", err); + Error::InvalidArgument + } + + pub fn deserialize(input: &[u8]) -> Result { + if input.is_empty() { + Err(Error::InvalidArgument)?; + } + match input[0] { + 0 => Ok(TokenProgram::Unallocated), + 1 => Ok(TokenProgram::Token( + bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, + )), + 2 => Ok(TokenProgram::Account( + bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, + )), + _ => Err(Error::InvalidArgument), + } + } + + fn serialize(self: &TokenProgram, output: &mut [u8]) -> Result<()> { + if output.is_empty() { + warn!("serialize fail: ouput.len is 0"); + Err(Error::InvalidArgument)?; + } + match self { + TokenProgram::Unallocated | TokenProgram::Invalid => Err(Error::InvalidArgument), + TokenProgram::Token(token_info) => { + output[0] = 1; + let writer = std::io::BufWriter::new(&mut output[1..]); + bincode::serialize_into(writer, &token_info).map_err(Self::map_to_invalid_args) + } + TokenProgram::Account(account_info) => { + output[0] = 2; + let writer = std::io::BufWriter::new(&mut output[1..]); + bincode::serialize_into(writer, &account_info).map_err(Self::map_to_invalid_args) + } + } + } + + #[allow(dead_code)] + pub fn amount(&self) -> Result { + if let TokenProgram::Account(account_info) = self { + Ok(account_info.amount) + } else { + Err(Error::InvalidArgument) + } + } + + #[allow(dead_code)] + pub fn only_owner(&self, key: &Pubkey) -> Result<()> { + if *key != Pubkey::default() { + if let TokenProgram::Account(account_info) = self { + if account_info.owner == *key { + return Ok(()); + } + } + } + warn!("TokenProgram: non-owner rejected"); + Err(Error::NotOwner) + } + + pub fn process_command_newtoken( + info: &mut [KeyedAccount], + token_info: TokenInfo, + input_program_accounts: &[TokenProgram], + output_program_accounts: &mut Vec<(usize, TokenProgram)>, + ) -> Result<()> { + if input_program_accounts.len() != 2 { + error!("Expected 2 accounts"); + Err(Error::InvalidArgument)?; + } + + if let TokenProgram::Account(dest_account) = &input_program_accounts[1] { + if info[0].key != &dest_account.token { + error!("account 1 token mismatch"); + Err(Error::InvalidArgument)?; + } + + if dest_account.delegate.is_some() { + error!("account 1 is a delegate and cannot accept tokens"); + Err(Error::InvalidArgument)?; + } + + let mut output_dest_account = dest_account.clone(); + output_dest_account.amount = token_info.supply; + output_program_accounts.push((1, TokenProgram::Account(output_dest_account))); + } else { + error!("account 1 invalid"); + Err(Error::InvalidArgument)?; + } + + if input_program_accounts[0] != TokenProgram::Unallocated { + error!("account 0 not available"); + Err(Error::InvalidArgument)?; + } + output_program_accounts.push((0, TokenProgram::Token(token_info))); + Ok(()) + } + + pub fn process_command_newaccount( + info: &mut [KeyedAccount], + input_program_accounts: &[TokenProgram], + output_program_accounts: &mut Vec<(usize, TokenProgram)>, + ) -> Result<()> { + // key 0 - Destination new token account + // key 1 - Owner of the account + // key 2 - Token this account is associated with + // key 3 - Source account that this account is a delegate for (optional) + if input_program_accounts.len() < 3 { + error!("Expected 3 accounts"); + Err(Error::InvalidArgument)?; + } + if input_program_accounts[0] != TokenProgram::Unallocated { + error!("account 0 is already allocated"); + Err(Error::InvalidArgument)?; + } + + let mut token_account_info = TokenAccountInfo { + token: *info[2].key, + owner: *info[1].key, + amount: 0, + delegate: None, + }; + if input_program_accounts.len() >= 4 { + token_account_info.delegate = Some(TokenAccountDelegateInfo { + source: *info[3].key, + original_amount: 0, + }); + } + output_program_accounts.push((0, TokenProgram::Account(token_account_info))); + Ok(()) + } + + pub fn process_command_transfer( + info: &mut [KeyedAccount], + amount: u64, + input_program_accounts: &[TokenProgram], + output_program_accounts: &mut Vec<(usize, TokenProgram)>, + ) -> Result<()> { + if input_program_accounts.len() < 3 { + error!("Expected 3 accounts"); + Err(Error::InvalidArgument)?; + } + + if let (TokenProgram::Account(source_account), TokenProgram::Account(dest_account)) = + (&input_program_accounts[1], &input_program_accounts[2]) + { + if source_account.token != dest_account.token { + error!("account 1/2 token mismatch"); + Err(Error::InvalidArgument)?; + } + + if dest_account.delegate.is_some() { + error!("account 2 is a delegate and cannot accept tokens"); + Err(Error::InvalidArgument)?; + } + + if info[0].key != &source_account.owner { + error!("owner of account 1 not present"); + Err(Error::InvalidArgument)?; + } + + if source_account.amount < amount { + Err(Error::InsufficentFunds)?; + } + + let mut output_source_account = source_account.clone(); + output_source_account.amount -= amount; + output_program_accounts.push((1, TokenProgram::Account(output_source_account))); + + if let Some(ref delegate_info) = source_account.delegate { + if input_program_accounts.len() != 4 { + error!("Expected 4 accounts"); + Err(Error::InvalidArgument)?; + } + + let delegate_account = source_account; + if let TokenProgram::Account(source_account) = &input_program_accounts[3] { + if source_account.token != delegate_account.token { + error!("account 1/3 token mismatch"); + Err(Error::InvalidArgument)?; + } + if info[3].key != &delegate_info.source { + error!("Account 1 is not a delegate of account 3"); + Err(Error::InvalidArgument)?; + } + + if source_account.amount < amount { + Err(Error::InsufficentFunds)?; + } + + let mut output_source_account = source_account.clone(); + output_source_account.amount -= amount; + output_program_accounts.push((3, TokenProgram::Account(output_source_account))); + } else { + error!("account 3 is an invalid account"); + Err(Error::InvalidArgument)?; + } + } + + let mut output_dest_account = dest_account.clone(); + output_dest_account.amount += amount; + output_program_accounts.push((2, TokenProgram::Account(output_dest_account))); + } else { + error!("account 1 and/or 2 are invalid accounts"); + Err(Error::InvalidArgument)?; + } + Ok(()) + } + + pub fn process_command_approve( + info: &mut [KeyedAccount], + amount: u64, + input_program_accounts: &[TokenProgram], + output_program_accounts: &mut Vec<(usize, TokenProgram)>, + ) -> Result<()> { + if input_program_accounts.len() != 3 { + error!("Expected 3 accounts"); + Err(Error::InvalidArgument)?; + } + + if let (TokenProgram::Account(source_account), TokenProgram::Account(delegate_account)) = + (&input_program_accounts[1], &input_program_accounts[2]) + { + if source_account.token != delegate_account.token { + error!("account 1/2 token mismatch"); + Err(Error::InvalidArgument)?; + } + + if info[0].key != &source_account.owner { + error!("owner of account 1 not present"); + Err(Error::InvalidArgument)?; + } + + if source_account.delegate.is_some() { + error!("account 1 is a delegate"); + Err(Error::InvalidArgument)?; + } + + match &delegate_account.delegate { + None => { + error!("account 2 is not a delegate"); + Err(Error::InvalidArgument)?; + } + Some(delegate_info) => { + if info[1].key != &delegate_info.source { + error!("account 2 is not a delegate of account 1"); + Err(Error::InvalidArgument)?; + } + + let mut output_delegate_account = delegate_account.clone(); + output_delegate_account.amount = amount; + output_delegate_account.delegate = Some(TokenAccountDelegateInfo { + source: delegate_info.source, + original_amount: amount, + }); + output_program_accounts + .push((2, TokenProgram::Account(output_delegate_account))); + } + } + } else { + error!("account 1 and/or 2 are invalid accounts"); + Err(Error::InvalidArgument)?; + } + Ok(()) + } + + pub fn process_command_setowner( + info: &mut [KeyedAccount], + input_program_accounts: &[TokenProgram], + output_program_accounts: &mut Vec<(usize, TokenProgram)>, + ) -> Result<()> { + if input_program_accounts.len() < 3 { + error!("Expected 3 accounts"); + Err(Error::InvalidArgument)?; + } + + if let TokenProgram::Account(source_account) = &input_program_accounts[1] { + if info[0].key != &source_account.owner { + info!("owner of account 1 not present"); + Err(Error::InvalidArgument)?; + } + + let mut output_source_account = source_account.clone(); + output_source_account.owner = *info[2].key; + output_program_accounts.push((1, TokenProgram::Account(output_source_account))); + } else { + info!("account 1 is invalid"); + Err(Error::InvalidArgument)?; + } + Ok(()) + } + + pub fn process(info: &mut [KeyedAccount], input: &[u8]) -> Result<()> { + let command = bincode::deserialize::(input).map_err(Self::map_to_invalid_args)?; + info!("process_transaction: command={:?}", command); + + let input_program_accounts: Vec = info + .iter() + .map(|keyed_account| { + let account = &keyed_account.account; + + // + // TODO: Restore the following commented out block to valid program ids + // once https://github.com/solana-labs/solana/issues/1544 is fixed. + + /* + if account.program_id == info[0].account.program_id { + match Self::deserialize(&account.userdata) { + Ok(token_program) => token_program, + Err(err) => { + error!("deserialize failed: {:?}", err); + TokenProgram::Invalid + } + } + } else { + TokenProgram::Invalid + } + */ + match Self::deserialize(&account.userdata) { + Ok(token_program) => token_program, + Err(err) => { + error!("deserialize failed: {:?}", err); + TokenProgram::Invalid + } + } + }).collect(); + + for program_account in &input_program_accounts { + info!("input_program_account: userdata={:?}", program_account); + } + + let mut output_program_accounts: Vec<(_, _)> = vec![]; + + match command { + Command::NewToken(token_info) => Self::process_command_newtoken( + info, + token_info, + &input_program_accounts, + &mut output_program_accounts, + )?, + Command::NewTokenAccount => Self::process_command_newaccount( + info, + &input_program_accounts, + &mut output_program_accounts, + )?, + + Command::Transfer(amount) => Self::process_command_transfer( + info, + amount, + &input_program_accounts, + &mut output_program_accounts, + )?, + + Command::Approve(amount) => Self::process_command_approve( + info, + amount, + &input_program_accounts, + &mut output_program_accounts, + )?, + + Command::SetOwner => Self::process_command_setowner( + info, + &input_program_accounts, + &mut output_program_accounts, + )?, + } + + for (index, program_account) in &output_program_accounts { + info!( + "output_program_account: index={} userdata={:?}", + index, program_account + ); + Self::serialize(program_account, &mut info[*index].account.userdata)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + pub fn serde() { + assert_eq!(TokenProgram::deserialize(&[0]), Ok(TokenProgram::default())); + + let mut userdata = vec![0; 256]; + + let account = TokenProgram::Account(TokenAccountInfo { + token: Pubkey::new(&[1; 32]), + owner: Pubkey::new(&[2; 32]), + amount: 123, + delegate: None, + }); + assert!(account.serialize(&mut userdata).is_ok()); + assert_eq!(TokenProgram::deserialize(&userdata), Ok(account)); + + let account = TokenProgram::Token(TokenInfo { + supply: 12345, + decimals: 2, + name: "A test token".to_string(), + symbol: "TEST".to_string(), + }); + assert!(account.serialize(&mut userdata).is_ok()); + assert_eq!(TokenProgram::deserialize(&userdata), Ok(account)); + } + + #[test] + pub fn serde_expect_fail() { + let mut userdata = vec![0; 256]; + + // Certain TokenProgram's may not be serialized + let account = TokenProgram::default(); + assert_eq!(account, TokenProgram::Unallocated); + assert!(account.serialize(&mut userdata).is_err()); + assert!(account.serialize(&mut userdata).is_err()); + let account = TokenProgram::Invalid; + assert!(account.serialize(&mut userdata).is_err()); + + // Bad deserialize userdata + assert!(TokenProgram::deserialize(&[]).is_err()); + assert!(TokenProgram::deserialize(&[1]).is_err()); + assert!(TokenProgram::deserialize(&[1, 2]).is_err()); + assert!(TokenProgram::deserialize(&[2, 2]).is_err()); + assert!(TokenProgram::deserialize(&[3]).is_err()); + } + + // Note: business logic tests are located in the @solana/web3.js test suite +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 92808ff6e8..a51819d897 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -10,6 +10,7 @@ license = "Apache-2.0" bincode = "1.0.0" bs58 = "0.2.0" generic-array = { version = "0.12.0", default-features = false, features = ["serde"] } +log = "0.4.2" serde = "1.0.27" serde_derive = "1.0.27" diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index b5cd719c0f..d8b468ddd8 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -1,8 +1,10 @@ pub mod account; pub mod loader_instruction; pub mod pubkey; + extern crate bincode; extern crate bs58; extern crate generic_array; +extern crate log; #[macro_use] extern crate serde_derive; diff --git a/src/bank.rs b/src/bank.rs index 8d9b7cf27c..470680b4a9 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -36,7 +36,7 @@ use storage_program::StorageProgram; use system_program::SystemProgram; use system_transaction::SystemTransaction; use timing::{duration_as_us, timestamp}; -use token_program::TokenProgram; +use token_program; use tokio::prelude::Future; use transaction::Transaction; use vote_program::VoteProgram; @@ -238,12 +238,23 @@ impl Bank { } fn add_builtin_programs(&self) { - // Preload Bpf Loader account - let mut accounts = self.accounts.write().unwrap(); - let mut account = accounts - .entry(bpf_loader::id()) - .or_insert_with(Account::default); - bpf_loader::populate_account(&mut account); + // Preload Bpf Loader program + { + let mut accounts = self.accounts.write().unwrap(); + let mut account = accounts + .entry(bpf_loader::id()) + .or_insert_with(Account::default); + bpf_loader::populate_account(&mut account); + } + + // Preload Erc20 token program + { + let mut accounts = self.accounts.write().unwrap(); + let mut account = accounts + .entry(token_program::id()) + .or_insert_with(Account::default); + token_program::populate_account(&mut account); + } } /// Commit funds to the given account @@ -662,11 +673,6 @@ impl Bank { { return Err(BankError::ProgramRuntimeError(instruction_index as u8)); } - } else if TokenProgram::check_id(&tx_program_id) { - if TokenProgram::process_transaction(&tx, instruction_index, program_accounts).is_err() - { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } } else if VoteProgram::check_id(&tx_program_id) { VoteProgram::process_transaction(&tx, instruction_index, program_accounts).is_err(); } else { @@ -2143,7 +2149,7 @@ mod tests { assert_eq!(bpf_loader::id(), bpf); assert_eq!(BudgetState::id(), budget); assert_eq!(StorageProgram::id(), storage); - assert_eq!(TokenProgram::id(), token); + assert_eq!(token_program::id(), token); assert_eq!(VoteProgram::id(), vote); } @@ -2156,7 +2162,7 @@ mod tests { bpf_loader::id(), BudgetState::id(), StorageProgram::id(), - TokenProgram::id(), + token_program::id(), VoteProgram::id(), ]; assert!(ids.into_iter().all(move |id| unique.insert(id))); diff --git a/src/token_program.rs b/src/token_program.rs index 4b9e836f75..376116ce29 100644 --- a/src/token_program.rs +++ b/src/token_program.rs @@ -1,525 +1,22 @@ //! ERC20-like Token program - -use bincode; - +use native_loader; use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; -use std; -use transaction::Transaction; -#[derive(Debug, PartialEq)] -pub enum Error { - InvalidArgument, - InsufficentFunds, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "error") - } -} -impl std::error::Error for Error {} - -pub type Result = std::result::Result; - -#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct TokenInfo { - /** - * Total supply of tokens - */ - supply: u64, - - /** - * Number of base 10 digits to the right of the decimal place in the total supply - */ - decimals: u8, - - /** - * Descriptive name of this token - */ - name: String, - - /** - * Symbol for this token - */ - symbol: String, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct TokenAccountDelegateInfo { - /** - * The source account for the tokens - */ - source: Pubkey, - - /** - * The original amount that this delegate account was authorized to spend up to - */ - original_amount: u64, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct TokenAccountInfo { - /** - * The kind of token this account holds - */ - token: Pubkey, - - /** - * Owner of this account - */ - owner: Pubkey, - - /** - * Amount of tokens this account holds - */ - amount: u64, - - /** - * If `delegate` None, `amount` belongs to this account. - * If `delegate` is Option<_>, `amount` represents the remaining allowance - * of tokens that may be transferred from the `source` account. - */ - delegate: Option, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -enum Command { - NewToken(TokenInfo), - NewTokenAccount, - Transfer(u64), - Approve(u64), - SetOwner, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub enum TokenProgram { - Unallocated, - Token(TokenInfo), - Account(TokenAccountInfo), - Invalid, -} -impl Default for TokenProgram { - fn default() -> TokenProgram { - TokenProgram::Unallocated - } -} - -const TOKEN_PROGRAM_ID: [u8; 32] = [ +const ERC20_NAME: &str = "solana_erc20"; +const ERC20_PROGRAM_ID: [u8; 32] = [ 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; -impl TokenProgram { - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - fn map_to_invalid_args(err: std::boxed::Box) -> Error { - warn!("invalid argument: {:?}", err); - Error::InvalidArgument - } - - fn deserialize(input: &[u8]) -> Result { - if input.is_empty() { - Err(Error::InvalidArgument)?; - } - match input[0] { - 0 => Ok(TokenProgram::Unallocated), - 1 => Ok(TokenProgram::Token( - bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, - )), - 2 => Ok(TokenProgram::Account( - bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, - )), - _ => Err(Error::InvalidArgument), - } - } - - fn serialize(self: &TokenProgram, output: &mut [u8]) -> Result<()> { - if output.is_empty() { - warn!("serialize fail: ouput.len is 0"); - Err(Error::InvalidArgument)?; - } - match self { - TokenProgram::Unallocated | TokenProgram::Invalid => Err(Error::InvalidArgument), - TokenProgram::Token(token_info) => { - output[0] = 1; - let writer = std::io::BufWriter::new(&mut output[1..]); - bincode::serialize_into(writer, &token_info).map_err(Self::map_to_invalid_args) - } - TokenProgram::Account(account_info) => { - output[0] = 2; - let writer = std::io::BufWriter::new(&mut output[1..]); - bincode::serialize_into(writer, &account_info).map_err(Self::map_to_invalid_args) - } - } - } - - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == TOKEN_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&TOKEN_PROGRAM_ID) - } - - pub fn process_command_newtoken( - tx: &Transaction, - pix: usize, - token_info: TokenInfo, - input_program_accounts: &[TokenProgram], - output_program_accounts: &mut Vec<(usize, TokenProgram)>, - ) -> Result<()> { - if input_program_accounts.len() != 2 { - error!("Expected 2 accounts"); - Err(Error::InvalidArgument)?; - } - - if let TokenProgram::Account(dest_account) = &input_program_accounts[1] { - if tx.key(pix, 0) != Some(&dest_account.token) { - info!("account 1 token mismatch"); - Err(Error::InvalidArgument)?; - } - - if dest_account.delegate.is_some() { - info!("account 1 is a delegate and cannot accept tokens"); - Err(Error::InvalidArgument)?; - } - - let mut output_dest_account = dest_account.clone(); - output_dest_account.amount = token_info.supply; - output_program_accounts.push((1, TokenProgram::Account(output_dest_account))); - } else { - info!("account 1 invalid"); - Err(Error::InvalidArgument)?; - } - - if input_program_accounts[0] != TokenProgram::Unallocated { - info!("account 0 not available"); - Err(Error::InvalidArgument)?; - } - output_program_accounts.push((0, TokenProgram::Token(token_info))); - Ok(()) - } - - pub fn process_command_newaccount( - tx: &Transaction, - pix: usize, - input_program_accounts: &[TokenProgram], - output_program_accounts: &mut Vec<(usize, TokenProgram)>, - ) -> Result<()> { - // key 0 - Destination new token account - // key 1 - Owner of the account - // key 2 - Token this account is associated with - // key 3 - Source account that this account is a delegate for (optional) - if input_program_accounts.len() < 3 { - error!("Expected 3 accounts"); - Err(Error::InvalidArgument)?; - } - if input_program_accounts[0] != TokenProgram::Unallocated { - info!("account 0 is already allocated"); - Err(Error::InvalidArgument)?; - } - - let mut token_account_info = TokenAccountInfo { - token: *tx.key(pix, 2).unwrap(), - owner: *tx.key(pix, 1).unwrap(), - amount: 0, - delegate: None, - }; - if input_program_accounts.len() >= 4 { - token_account_info.delegate = Some(TokenAccountDelegateInfo { - source: *tx.key(pix, 3).unwrap(), - original_amount: 0, - }); - } - output_program_accounts.push((0, TokenProgram::Account(token_account_info))); - Ok(()) - } - - pub fn process_command_transfer( - tx: &Transaction, - pix: usize, - amount: u64, - input_program_accounts: &[TokenProgram], - output_program_accounts: &mut Vec<(usize, TokenProgram)>, - ) -> Result<()> { - if input_program_accounts.len() < 3 { - error!("Expected 3 accounts"); - Err(Error::InvalidArgument)?; - } - - if let (TokenProgram::Account(source_account), TokenProgram::Account(dest_account)) = - (&input_program_accounts[1], &input_program_accounts[2]) - { - if source_account.token != dest_account.token { - info!("account 1/2 token mismatch"); - Err(Error::InvalidArgument)?; - } - - if dest_account.delegate.is_some() { - info!("account 2 is a delegate and cannot accept tokens"); - Err(Error::InvalidArgument)?; - } - - if Some(&source_account.owner) != tx.key(pix, 0) { - info!("owner of account 1 not present"); - Err(Error::InvalidArgument)?; - } - - if source_account.amount < amount { - Err(Error::InsufficentFunds)?; - } - - let mut output_source_account = source_account.clone(); - output_source_account.amount -= amount; - output_program_accounts.push((1, TokenProgram::Account(output_source_account))); - - if let Some(ref delegate_info) = source_account.delegate { - if input_program_accounts.len() != 4 { - error!("Expected 4 accounts"); - Err(Error::InvalidArgument)?; - } - - let delegate_account = source_account; - if let TokenProgram::Account(source_account) = &input_program_accounts[3] { - if source_account.token != delegate_account.token { - info!("account 1/3 token mismatch"); - Err(Error::InvalidArgument)?; - } - if Some(&delegate_info.source) != tx.key(pix, 3) { - info!("Account 1 is not a delegate of account 3"); - Err(Error::InvalidArgument)?; - } - - if source_account.amount < amount { - Err(Error::InsufficentFunds)?; - } - - let mut output_source_account = source_account.clone(); - output_source_account.amount -= amount; - output_program_accounts.push((3, TokenProgram::Account(output_source_account))); - } else { - info!("account 3 is an invalid account"); - Err(Error::InvalidArgument)?; - } - } - - let mut output_dest_account = dest_account.clone(); - output_dest_account.amount += amount; - output_program_accounts.push((2, TokenProgram::Account(output_dest_account))); - } else { - info!("account 1 and/or 2 are invalid accounts"); - Err(Error::InvalidArgument)?; - } - Ok(()) - } - - pub fn process_command_approve( - tx: &Transaction, - pix: usize, - amount: u64, - input_program_accounts: &[TokenProgram], - output_program_accounts: &mut Vec<(usize, TokenProgram)>, - ) -> Result<()> { - if input_program_accounts.len() != 3 { - error!("Expected 3 accounts"); - Err(Error::InvalidArgument)?; - } - - if let (TokenProgram::Account(source_account), TokenProgram::Account(delegate_account)) = - (&input_program_accounts[1], &input_program_accounts[2]) - { - if source_account.token != delegate_account.token { - info!("account 1/2 token mismatch"); - Err(Error::InvalidArgument)?; - } - - if Some(&source_account.owner) != tx.key(pix, 0) { - info!("owner of account 1 not present"); - Err(Error::InvalidArgument)?; - } - - if source_account.delegate.is_some() { - info!("account 1 is a delegate"); - Err(Error::InvalidArgument)?; - } - - match &delegate_account.delegate { - None => { - info!("account 2 is not a delegate"); - Err(Error::InvalidArgument)?; - } - Some(delegate_info) => { - if Some(&delegate_info.source) != tx.key(pix, 1) { - info!("account 2 is not a delegate of account 1"); - Err(Error::InvalidArgument)?; - } - - let mut output_delegate_account = delegate_account.clone(); - output_delegate_account.amount = amount; - output_delegate_account.delegate = Some(TokenAccountDelegateInfo { - source: delegate_info.source, - original_amount: amount, - }); - output_program_accounts - .push((2, TokenProgram::Account(output_delegate_account))); - } - } - } else { - info!("account 1 and/or 2 are invalid accounts"); - Err(Error::InvalidArgument)?; - } - Ok(()) - } - - pub fn process_command_setowner( - tx: &Transaction, - pix: usize, - input_program_accounts: &[TokenProgram], - output_program_accounts: &mut Vec<(usize, TokenProgram)>, - ) -> Result<()> { - if input_program_accounts.len() < 3 { - error!("Expected 3 accounts"); - Err(Error::InvalidArgument)?; - } - - if let TokenProgram::Account(source_account) = &input_program_accounts[1] { - if Some(&source_account.owner) != tx.key(pix, 0) { - info!("owner of account 1 not present"); - Err(Error::InvalidArgument)?; - } - - let mut output_source_account = source_account.clone(); - output_source_account.owner = *tx.key(pix, 2).unwrap(); - output_program_accounts.push((1, TokenProgram::Account(output_source_account))); - } else { - info!("account 1 is invalid"); - Err(Error::InvalidArgument)?; - } - Ok(()) - } - - pub fn process_transaction( - tx: &Transaction, - pix: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - let command = bincode::deserialize::(&tx.userdata(pix)) - .map_err(Self::map_to_invalid_args)?; - info!("process_transaction: command={:?}", command); - - let input_program_accounts: Vec = accounts - .iter() - .map(|account| { - if Self::check_id(&account.program_id) { - Self::deserialize(&account.userdata) - .map_err(|err| { - info!("deserialize failed: {:?}", err); - TokenProgram::Invalid - }).unwrap() - } else { - TokenProgram::Invalid - } - }).collect(); - - let mut output_program_accounts: Vec<(_, _)> = vec![]; - - match command { - Command::NewToken(token_info) => Self::process_command_newtoken( - tx, - pix, - token_info, - &input_program_accounts, - &mut output_program_accounts, - )?, - Command::NewTokenAccount => Self::process_command_newaccount( - tx, - pix, - &input_program_accounts, - &mut output_program_accounts, - )?, - - Command::Transfer(amount) => Self::process_command_transfer( - tx, - pix, - amount, - &input_program_accounts, - &mut output_program_accounts, - )?, - - Command::Approve(amount) => Self::process_command_approve( - tx, - pix, - amount, - &input_program_accounts, - &mut output_program_accounts, - )?, - - Command::SetOwner => Self::process_command_setowner( - tx, - pix, - &input_program_accounts, - &mut output_program_accounts, - )?, - } - - for (index, program_account) in &output_program_accounts { - info!( - "output_program_account: index={} userdata={:?}", - index, program_account - ); - Self::serialize(program_account, &mut accounts[*index].userdata)?; - } - Ok(()) - } +pub fn id() -> Pubkey { + Pubkey::new(&ERC20_PROGRAM_ID) } -#[cfg(test)] -mod test { - use super::*; - #[test] - pub fn serde() { - assert_eq!(TokenProgram::deserialize(&[0]), Ok(TokenProgram::default())); - - let mut userdata = vec![0; 256]; - - let account = TokenProgram::Account(TokenAccountInfo { - token: Pubkey::new(&[1; 32]), - owner: Pubkey::new(&[2; 32]), - amount: 123, - delegate: None, - }); - assert!(account.serialize(&mut userdata).is_ok()); - assert_eq!(TokenProgram::deserialize(&userdata), Ok(account)); - - let account = TokenProgram::Token(TokenInfo { - supply: 12345, - decimals: 2, - name: "A test token".to_string(), - symbol: "TEST".to_string(), - }); - assert!(account.serialize(&mut userdata).is_ok()); - assert_eq!(TokenProgram::deserialize(&userdata), Ok(account)); - } - - #[test] - pub fn serde_expect_fail() { - let mut userdata = vec![0; 256]; - - // Certain TokenProgram's may not be serialized - let account = TokenProgram::default(); - assert_eq!(account, TokenProgram::Unallocated); - assert!(account.serialize(&mut userdata).is_err()); - assert!(account.serialize(&mut userdata).is_err()); - let account = TokenProgram::Invalid; - assert!(account.serialize(&mut userdata).is_err()); - - // Bad deserialize userdata - assert!(TokenProgram::deserialize(&[]).is_err()); - assert!(TokenProgram::deserialize(&[1]).is_err()); - assert!(TokenProgram::deserialize(&[1, 2]).is_err()); - assert!(TokenProgram::deserialize(&[2, 2]).is_err()); - assert!(TokenProgram::deserialize(&[3]).is_err()); - } - - // Note: business logic tests are located in the @solana/web3.js test suite +pub fn populate_account(account: &mut Account) { + account.tokens = 0; + account.program_id = id(); + account.userdata = ERC20_NAME.as_bytes().to_vec(); + account.executable = true; + account.loader_program_id = native_loader::id(); }