Move token_program from src/ to programs/native/
This commit is contained in:
parent
f3b04894b9
commit
40e945b0c8
10
Cargo.toml
10
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",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "solana-erc20"
|
||||
version = "0.11.0"
|
||||
description = "Solana reference erc20 program"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
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"]
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[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<TokenAccountDelegateInfo>,
|
||||
}
|
||||
|
||||
#[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<bincode::ErrorKind>) -> Error {
|
||||
warn!("invalid argument: {:?}", err);
|
||||
Error::InvalidArgument
|
||||
}
|
||||
|
||||
pub fn deserialize(input: &[u8]) -> Result<TokenProgram> {
|
||||
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<u64> {
|
||||
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::<Command>(input).map_err(Self::map_to_invalid_args)?;
|
||||
info!("process_transaction: command={:?}", command);
|
||||
|
||||
let input_program_accounts: Vec<TokenProgram> = 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
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
34
src/bank.rs
34
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)));
|
||||
|
|
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[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<TokenAccountDelegateInfo>,
|
||||
}
|
||||
|
||||
#[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<bincode::ErrorKind>) -> Error {
|
||||
warn!("invalid argument: {:?}", err);
|
||||
Error::InvalidArgument
|
||||
}
|
||||
|
||||
fn deserialize(input: &[u8]) -> Result<TokenProgram> {
|
||||
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::<Command>(&tx.userdata(pix))
|
||||
.map_err(Self::map_to_invalid_args)?;
|
||||
info!("process_transaction: command={:?}", command);
|
||||
|
||||
let input_program_accounts: Vec<TokenProgram> = 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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue