Move token_program from src/ to programs/native/
This commit is contained in:
10
Cargo.toml
10
Cargo.toml
@ -109,9 +109,10 @@ sys-info = "0.5.6"
|
|||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
tokio-codec = "0.1"
|
tokio-codec = "0.1"
|
||||||
untrusted = "0.6.2"
|
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-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-lualoader = { path = "programs/native/lua_loader", version = "0.11.0" }
|
||||||
|
solana-noop = { path = "programs/native/noop", version = "0.11.0" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bank"
|
name = "bank"
|
||||||
@ -136,8 +137,9 @@ name = "chacha"
|
|||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"sdk",
|
"sdk",
|
||||||
"programs/native/noop",
|
|
||||||
"programs/native/bpf_loader",
|
|
||||||
"programs/native/lua_loader",
|
|
||||||
"programs/bpf/rust/noop",
|
"programs/bpf/rust/noop",
|
||||||
|
"programs/native/bpf_loader",
|
||||||
|
"programs/native/erc20",
|
||||||
|
"programs/native/lua_loader",
|
||||||
|
"programs/native/noop",
|
||||||
]
|
]
|
||||||
|
20
programs/native/erc20/Cargo.toml
Normal file
20
programs/native/erc20/Cargo.toml
Normal file
@ -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"]
|
||||||
|
|
30
programs/native/erc20/src/lib.rs
Normal file
30
programs/native/erc20/src/lib.rs
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
540
programs/native/erc20/src/token_program.rs
Normal file
540
programs/native/erc20/src/token_program.rs
Normal file
@ -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"
|
bincode = "1.0.0"
|
||||||
bs58 = "0.2.0"
|
bs58 = "0.2.0"
|
||||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||||
|
log = "0.4.2"
|
||||||
serde = "1.0.27"
|
serde = "1.0.27"
|
||||||
serde_derive = "1.0.27"
|
serde_derive = "1.0.27"
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod loader_instruction;
|
pub mod loader_instruction;
|
||||||
pub mod pubkey;
|
pub mod pubkey;
|
||||||
|
|
||||||
extern crate bincode;
|
extern crate bincode;
|
||||||
extern crate bs58;
|
extern crate bs58;
|
||||||
extern crate generic_array;
|
extern crate generic_array;
|
||||||
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
24
src/bank.rs
24
src/bank.rs
@ -36,7 +36,7 @@ use storage_program::StorageProgram;
|
|||||||
use system_program::SystemProgram;
|
use system_program::SystemProgram;
|
||||||
use system_transaction::SystemTransaction;
|
use system_transaction::SystemTransaction;
|
||||||
use timing::{duration_as_us, timestamp};
|
use timing::{duration_as_us, timestamp};
|
||||||
use token_program::TokenProgram;
|
use token_program;
|
||||||
use tokio::prelude::Future;
|
use tokio::prelude::Future;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
use vote_program::VoteProgram;
|
use vote_program::VoteProgram;
|
||||||
@ -238,7 +238,8 @@ impl Bank {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_builtin_programs(&self) {
|
fn add_builtin_programs(&self) {
|
||||||
// Preload Bpf Loader account
|
// Preload Bpf Loader program
|
||||||
|
{
|
||||||
let mut accounts = self.accounts.write().unwrap();
|
let mut accounts = self.accounts.write().unwrap();
|
||||||
let mut account = accounts
|
let mut account = accounts
|
||||||
.entry(bpf_loader::id())
|
.entry(bpf_loader::id())
|
||||||
@ -246,6 +247,16 @@ impl Bank {
|
|||||||
bpf_loader::populate_account(&mut account);
|
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
|
/// Commit funds to the given account
|
||||||
fn apply_payment(payment: &Payment, account: &mut Account) {
|
fn apply_payment(payment: &Payment, account: &mut Account) {
|
||||||
trace!("apply payments {}", payment.tokens);
|
trace!("apply payments {}", payment.tokens);
|
||||||
@ -662,11 +673,6 @@ impl Bank {
|
|||||||
{
|
{
|
||||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
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) {
|
} else if VoteProgram::check_id(&tx_program_id) {
|
||||||
VoteProgram::process_transaction(&tx, instruction_index, program_accounts).is_err();
|
VoteProgram::process_transaction(&tx, instruction_index, program_accounts).is_err();
|
||||||
} else {
|
} else {
|
||||||
@ -2143,7 +2149,7 @@ mod tests {
|
|||||||
assert_eq!(bpf_loader::id(), bpf);
|
assert_eq!(bpf_loader::id(), bpf);
|
||||||
assert_eq!(BudgetState::id(), budget);
|
assert_eq!(BudgetState::id(), budget);
|
||||||
assert_eq!(StorageProgram::id(), storage);
|
assert_eq!(StorageProgram::id(), storage);
|
||||||
assert_eq!(TokenProgram::id(), token);
|
assert_eq!(token_program::id(), token);
|
||||||
assert_eq!(VoteProgram::id(), vote);
|
assert_eq!(VoteProgram::id(), vote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2156,7 +2162,7 @@ mod tests {
|
|||||||
bpf_loader::id(),
|
bpf_loader::id(),
|
||||||
BudgetState::id(),
|
BudgetState::id(),
|
||||||
StorageProgram::id(),
|
StorageProgram::id(),
|
||||||
TokenProgram::id(),
|
token_program::id(),
|
||||||
VoteProgram::id(),
|
VoteProgram::id(),
|
||||||
];
|
];
|
||||||
assert!(ids.into_iter().all(move |id| unique.insert(id)));
|
assert!(ids.into_iter().all(move |id| unique.insert(id)));
|
||||||
|
@ -1,525 +1,22 @@
|
|||||||
//! ERC20-like Token program
|
//! ERC20-like Token program
|
||||||
|
use native_loader;
|
||||||
use bincode;
|
|
||||||
|
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std;
|
|
||||||
use transaction::Transaction;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
const ERC20_NAME: &str = "solana_erc20";
|
||||||
pub enum Error {
|
const ERC20_PROGRAM_ID: [u8; 32] = [
|
||||||
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] = [
|
|
||||||
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,
|
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,
|
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 {
|
pub fn id() -> Pubkey {
|
||||||
Pubkey::new(&TOKEN_PROGRAM_ID)
|
Pubkey::new(&ERC20_PROGRAM_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command_newtoken(
|
pub fn populate_account(account: &mut Account) {
|
||||||
tx: &Transaction,
|
account.tokens = 0;
|
||||||
pix: usize,
|
account.program_id = id();
|
||||||
token_info: TokenInfo,
|
account.userdata = ERC20_NAME.as_bytes().to_vec();
|
||||||
input_program_accounts: &[TokenProgram],
|
account.executable = true;
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
account.loader_program_id = native_loader::id();
|
||||||
) -> 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user