rpc: Support token-2022 in token-specific calls (#25150)
* rpc: Support token-2022 in token-specific calls * Address feedback
This commit is contained in:
parent
dff089e0dd
commit
0820065c98
|
@ -5,7 +5,7 @@ use {
|
|||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
parse_sysvar::parse_sysvar,
|
||||
parse_token::{parse_token, spl_token_ids},
|
||||
parse_token::{parse_token, spl_token_2022_id, spl_token_id},
|
||||
parse_vote::parse_vote,
|
||||
},
|
||||
inflector::Inflector,
|
||||
|
@ -30,9 +30,8 @@ lazy_static! {
|
|||
);
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
for spl_token_id in spl_token_ids() {
|
||||
m.insert(spl_token_id, ParsableAccount::SplToken);
|
||||
}
|
||||
m.insert(spl_token_id(), ParsableAccount::SplToken);
|
||||
m.insert(spl_token_2022_id(), ParsableAccount::SplToken2022);
|
||||
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
|
||||
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
|
||||
|
@ -73,6 +72,7 @@ pub enum ParsableAccount {
|
|||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
SplToken2022,
|
||||
Stake,
|
||||
Sysvar,
|
||||
Vote,
|
||||
|
@ -99,7 +99,7 @@ pub fn parse_account_data(
|
|||
}
|
||||
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::SplToken => {
|
||||
ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
|
||||
serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
|
||||
}
|
||||
ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
|
||||
|
|
|
@ -18,13 +18,13 @@ use {
|
|||
|
||||
// A helper function to convert spl_token::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
fn spl_token_id() -> Pubkey {
|
||||
pub(crate) fn spl_token_id() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token::id().to_bytes())
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token_2022::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
fn spl_token_2022_id() -> Pubkey {
|
||||
pub(crate) fn spl_token_2022_id() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token_2022::id().to_bytes())
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,7 @@ mod test {
|
|||
delegate: COption::None,
|
||||
delegated_amount: 0,
|
||||
};
|
||||
let account_size = ExtensionType::get_account_len::<Mint>(&[
|
||||
let account_size = ExtensionType::get_account_len::<Account>(&[
|
||||
ExtensionType::ImmutableOwner,
|
||||
ExtensionType::MemoTransfer,
|
||||
]);
|
||||
|
|
581
rpc/src/rpc.rs
581
rpc/src/rpc.rs
|
@ -47,6 +47,7 @@ use {
|
|||
bank_forks::BankForks,
|
||||
commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots},
|
||||
inline_spl_token::{SPL_TOKEN_ACCOUNT_MINT_OFFSET, SPL_TOKEN_ACCOUNT_OWNER_OFFSET},
|
||||
inline_spl_token_2022::{self, ACCOUNTTYPE_ACCOUNT},
|
||||
non_circulating_supply::calculate_non_circulating_supply,
|
||||
snapshot_config::SnapshotConfig,
|
||||
snapshot_utils,
|
||||
|
@ -87,7 +88,8 @@ use {
|
|||
UiConfirmedBlock, UiTransactionEncoding,
|
||||
},
|
||||
solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
|
||||
spl_token::{
|
||||
spl_token_2022::{
|
||||
extension::StateWithExtensions,
|
||||
solana_program::program_pack::Pack,
|
||||
state::{Account as TokenAccount, Mint},
|
||||
},
|
||||
|
@ -1791,12 +1793,12 @@ impl JsonRpcRequestProcessor {
|
|||
"Invalid param: not a Token account".to_string(),
|
||||
));
|
||||
}
|
||||
let token_account = TokenAccount::unpack(account.data())
|
||||
let token_account = StateWithExtensions::<TokenAccount>::unpack(account.data())
|
||||
.map_err(|_| Error::invalid_params("Invalid param: not a Token account".to_string()))?;
|
||||
let mint = &Pubkey::from_str(&token_account.mint.to_string())
|
||||
let mint = &Pubkey::from_str(&token_account.base.mint.to_string())
|
||||
.expect("Token account mint should be convertible to Pubkey");
|
||||
let (_, decimals) = get_mint_owner_and_decimals(&bank, mint)?;
|
||||
let balance = token_amount_to_ui_amount(token_account.amount, decimals);
|
||||
let balance = token_amount_to_ui_amount(token_account.base.amount, decimals);
|
||||
Ok(new_response(&bank, balance))
|
||||
}
|
||||
|
||||
|
@ -1814,11 +1816,11 @@ impl JsonRpcRequestProcessor {
|
|||
"Invalid param: not a Token mint".to_string(),
|
||||
));
|
||||
}
|
||||
let mint = Mint::unpack(mint_account.data()).map_err(|_| {
|
||||
let mint = StateWithExtensions::<Mint>::unpack(mint_account.data()).map_err(|_| {
|
||||
Error::invalid_params("Invalid param: mint could not be unpacked".to_string())
|
||||
})?;
|
||||
|
||||
let supply = token_amount_to_ui_amount(mint.supply, mint.decimals);
|
||||
let supply = token_amount_to_ui_amount(mint.base.supply, mint.base.decimals);
|
||||
Ok(new_response(&bank, supply))
|
||||
}
|
||||
|
||||
|
@ -1838,8 +1840,8 @@ impl JsonRpcRequestProcessor {
|
|||
.get_filtered_spl_token_accounts_by_mint(&bank, &mint_owner, mint, vec![])?
|
||||
.into_iter()
|
||||
.map(|(address, account)| {
|
||||
let amount = TokenAccount::unpack(account.data())
|
||||
.map(|account| account.amount)
|
||||
let amount = StateWithExtensions::<TokenAccount>::unpack(account.data())
|
||||
.map(|account| account.base.amount)
|
||||
.unwrap_or(0);
|
||||
let amount = token_amount_to_ui_amount(amount, decimals);
|
||||
RpcTokenAccountBalance {
|
||||
|
@ -1951,9 +1953,7 @@ impl JsonRpcRequestProcessor {
|
|||
self.get_filtered_spl_token_accounts_by_mint(&bank, &token_program_id, &mint, filters)?
|
||||
} else {
|
||||
// Filter on Token Account state
|
||||
filters.push(RpcFilterType::DataSize(
|
||||
TokenAccount::get_packed_len() as u64
|
||||
));
|
||||
filters.push(RpcFilterType::TokenAccountState);
|
||||
self.get_filtered_program_accounts(&bank, &token_program_id, filters)?
|
||||
};
|
||||
let accounts = if encoding == UiAccountEncoding::JsonParsed {
|
||||
|
@ -2340,26 +2340,40 @@ fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) ->
|
|||
return None;
|
||||
}
|
||||
let mut data_size_filter: Option<u64> = None;
|
||||
let mut memcmp_filter: Option<&[u8]> = None;
|
||||
let mut owner_key: Option<Pubkey> = None;
|
||||
let mut incorrect_owner_len: Option<usize> = None;
|
||||
let mut token_account_state_filter = false;
|
||||
let account_packed_len = TokenAccount::get_packed_len();
|
||||
for filter in filters {
|
||||
match filter {
|
||||
RpcFilterType::DataSize(size) => data_size_filter = Some(*size),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: SPL_TOKEN_ACCOUNT_OWNER_OFFSET,
|
||||
offset,
|
||||
bytes: MemcmpEncodedBytes::Bytes(bytes),
|
||||
..
|
||||
}) => {
|
||||
}) if *offset == account_packed_len && *program_id == inline_spl_token_2022::id() => {
|
||||
memcmp_filter = Some(bytes)
|
||||
}
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset,
|
||||
bytes: MemcmpEncodedBytes::Bytes(bytes),
|
||||
..
|
||||
}) if *offset == SPL_TOKEN_ACCOUNT_OWNER_OFFSET => {
|
||||
if bytes.len() == PUBKEY_BYTES {
|
||||
owner_key = Some(Pubkey::new(bytes));
|
||||
} else {
|
||||
incorrect_owner_len = Some(bytes.len());
|
||||
}
|
||||
}
|
||||
RpcFilterType::TokenAccountState => token_account_state_filter = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if data_size_filter == Some(TokenAccount::get_packed_len() as u64) {
|
||||
if data_size_filter == Some(account_packed_len as u64)
|
||||
|| memcmp_filter == Some(&[ACCOUNTTYPE_ACCOUNT])
|
||||
|| token_account_state_filter
|
||||
{
|
||||
if let Some(incorrect_owner_len) = incorrect_owner_len {
|
||||
info!(
|
||||
"Incorrect num bytes ({:?}) provided for spl_token_owner_filter",
|
||||
|
@ -2382,26 +2396,40 @@ fn get_spl_token_mint_filter(program_id: &Pubkey, filters: &[RpcFilterType]) ->
|
|||
return None;
|
||||
}
|
||||
let mut data_size_filter: Option<u64> = None;
|
||||
let mut memcmp_filter: Option<&[u8]> = None;
|
||||
let mut mint: Option<Pubkey> = None;
|
||||
let mut incorrect_mint_len: Option<usize> = None;
|
||||
let mut token_account_state_filter = false;
|
||||
let account_packed_len = TokenAccount::get_packed_len();
|
||||
for filter in filters {
|
||||
match filter {
|
||||
RpcFilterType::DataSize(size) => data_size_filter = Some(*size),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: SPL_TOKEN_ACCOUNT_MINT_OFFSET,
|
||||
offset,
|
||||
bytes: MemcmpEncodedBytes::Bytes(bytes),
|
||||
..
|
||||
}) => {
|
||||
}) if *offset == account_packed_len && *program_id == inline_spl_token_2022::id() => {
|
||||
memcmp_filter = Some(bytes)
|
||||
}
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset,
|
||||
bytes: MemcmpEncodedBytes::Bytes(bytes),
|
||||
..
|
||||
}) if *offset == SPL_TOKEN_ACCOUNT_MINT_OFFSET => {
|
||||
if bytes.len() == PUBKEY_BYTES {
|
||||
mint = Some(Pubkey::new(bytes));
|
||||
} else {
|
||||
incorrect_mint_len = Some(bytes.len());
|
||||
}
|
||||
}
|
||||
RpcFilterType::TokenAccountState => token_account_state_filter = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if data_size_filter == Some(TokenAccount::get_packed_len() as u64) {
|
||||
if data_size_filter == Some(account_packed_len as u64)
|
||||
|| memcmp_filter == Some(&[ACCOUNTTYPE_ACCOUNT])
|
||||
|| token_account_state_filter
|
||||
{
|
||||
if let Some(incorrect_mint_len) = incorrect_mint_len {
|
||||
info!(
|
||||
"Incorrect num bytes ({:?}) provided for spl_token_mint_filter",
|
||||
|
@ -4533,7 +4561,7 @@ pub mod tests {
|
|||
},
|
||||
solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender, commitment::BlockCommitment,
|
||||
non_circulating_supply::non_circulating_accounts,
|
||||
inline_spl_token, non_circulating_supply::non_circulating_accounts,
|
||||
},
|
||||
solana_sdk::{
|
||||
account::{Account, WritableAccount},
|
||||
|
@ -4559,17 +4587,18 @@ pub mod tests {
|
|||
vote_instruction,
|
||||
vote_state::{Vote, VoteInit, VoteStateVersions, MAX_LOCKOUT_HISTORY},
|
||||
},
|
||||
spl_token::{
|
||||
spl_token_2022::{
|
||||
extension::{
|
||||
immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
|
||||
mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut,
|
||||
},
|
||||
pod::OptionalNonZeroPubkey,
|
||||
solana_program::{program_option::COption, pubkey::Pubkey as SplTokenPubkey},
|
||||
state::{AccountState as TokenAccountState, Mint},
|
||||
},
|
||||
std::{borrow::Cow, collections::HashMap},
|
||||
};
|
||||
|
||||
fn spl_token_id() -> Pubkey {
|
||||
solana_account_decoder::parse_token::spl_token_ids()[0]
|
||||
}
|
||||
|
||||
const TEST_MINT_LAMPORTS: u64 = 1_000_000;
|
||||
const TEST_SLOTS_PER_EPOCH: u64 = DELINQUENT_VALIDATOR_SLOT_DISTANCE + 1;
|
||||
|
||||
|
@ -7182,14 +7211,109 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_token_rpcs() {
|
||||
for program_id in solana_account_decoder::parse_token::spl_token_ids() {
|
||||
let rpc = RpcHandler::start();
|
||||
let bank = rpc.working_bank();
|
||||
let RpcHandler { io, meta, .. } = rpc;
|
||||
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let mint = SplTokenPubkey::new(&[2; 32]);
|
||||
let owner = SplTokenPubkey::new(&[3; 32]);
|
||||
let delegate = SplTokenPubkey::new(&[4; 32]);
|
||||
let token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let token_with_different_mint_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let new_mint = SplTokenPubkey::new(&[5; 32]);
|
||||
if program_id == inline_spl_token_2022::id() {
|
||||
// Add the token account
|
||||
let account_base = TokenAccount {
|
||||
mint,
|
||||
owner,
|
||||
delegate: COption::Some(delegate),
|
||||
amount: 420,
|
||||
state: TokenAccountState::Initialized,
|
||||
is_native: COption::None,
|
||||
delegated_amount: 30,
|
||||
close_authority: COption::Some(owner),
|
||||
};
|
||||
let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
|
||||
ExtensionType::ImmutableOwner,
|
||||
ExtensionType::MemoTransfer,
|
||||
]);
|
||||
let mut account_data = vec![0; account_size];
|
||||
let mut account_state =
|
||||
StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)
|
||||
.unwrap();
|
||||
|
||||
account_state.base = account_base;
|
||||
account_state.pack_base();
|
||||
account_state.init_account_type().unwrap();
|
||||
account_state.init_extension::<ImmutableOwner>().unwrap();
|
||||
let mut memo_transfer = account_state.init_extension::<MemoTransfer>().unwrap();
|
||||
memo_transfer.require_incoming_transfer_memos = true.into();
|
||||
|
||||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&token_account_pubkey, &token_account);
|
||||
|
||||
// Add the mint
|
||||
let mint_size =
|
||||
ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
|
||||
let mint_base = Mint {
|
||||
mint_authority: COption::Some(owner),
|
||||
supply: 500,
|
||||
decimals: 2,
|
||||
is_initialized: true,
|
||||
freeze_authority: COption::Some(owner),
|
||||
};
|
||||
let mut mint_data = vec![0; mint_size];
|
||||
let mut mint_state =
|
||||
StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
|
||||
|
||||
mint_state.base = mint_base;
|
||||
mint_state.pack_base();
|
||||
mint_state.init_account_type().unwrap();
|
||||
let mut mint_close_authority =
|
||||
mint_state.init_extension::<MintCloseAuthority>().unwrap();
|
||||
mint_close_authority.close_authority =
|
||||
OptionalNonZeroPubkey::try_from(Some(owner)).unwrap();
|
||||
|
||||
let mint_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: mint_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
|
||||
|
||||
// Add another token account with the same owner, delegate, and mint
|
||||
let other_token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&other_token_account_pubkey, &token_account);
|
||||
|
||||
// Add another token account with the same owner and delegate but different mint
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let token_account = TokenAccount {
|
||||
mint: new_mint,
|
||||
owner,
|
||||
delegate: COption::Some(delegate),
|
||||
amount: 42,
|
||||
state: TokenAccountState::Initialized,
|
||||
is_native: COption::None,
|
||||
delegated_amount: 30,
|
||||
close_authority: COption::Some(owner),
|
||||
};
|
||||
TokenAccount::pack(token_account, &mut account_data).unwrap();
|
||||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&token_with_different_mint_pubkey, &token_account);
|
||||
} else {
|
||||
// Add the token account
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let token_account = TokenAccount {
|
||||
mint,
|
||||
owner,
|
||||
|
@ -7204,10 +7328,9 @@ pub mod tests {
|
|||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
let token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&token_account_pubkey, &token_account);
|
||||
|
||||
// Add the mint
|
||||
|
@ -7223,11 +7346,37 @@ pub mod tests {
|
|||
let mint_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: mint_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
|
||||
|
||||
// Add another token account with the same owner, delegate, and mint
|
||||
let other_token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&other_token_account_pubkey, &token_account);
|
||||
|
||||
// Add another token account with the same owner and delegate but different mint
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let token_account = TokenAccount {
|
||||
mint: new_mint,
|
||||
owner,
|
||||
delegate: COption::Some(delegate),
|
||||
amount: 42,
|
||||
state: TokenAccountState::Initialized,
|
||||
is_native: COption::None,
|
||||
delegated_amount: 30,
|
||||
close_authority: COption::Some(owner),
|
||||
};
|
||||
TokenAccount::pack(token_account, &mut account_data).unwrap();
|
||||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&token_with_different_mint_pubkey, &token_account);
|
||||
}
|
||||
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenAccountBalance","params":["{}"]}}"#,
|
||||
token_account_pubkey,
|
||||
|
@ -7279,33 +7428,6 @@ pub mod tests {
|
|||
.expect("actual response deserialization");
|
||||
assert!(result.get("error").is_some());
|
||||
|
||||
// Add another token account with the same owner, delegate, and mint
|
||||
let other_token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&other_token_account_pubkey, &token_account);
|
||||
|
||||
// Add another token account with the same owner and delegate but different mint
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let new_mint = SplTokenPubkey::new(&[5; 32]);
|
||||
let token_account = TokenAccount {
|
||||
mint: new_mint,
|
||||
owner,
|
||||
delegate: COption::Some(delegate),
|
||||
amount: 42,
|
||||
state: TokenAccountState::Initialized,
|
||||
is_native: COption::None,
|
||||
delegated_amount: 30,
|
||||
close_authority: COption::Some(owner),
|
||||
};
|
||||
TokenAccount::pack(token_account, &mut account_data).unwrap();
|
||||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
..Account::default()
|
||||
});
|
||||
let token_with_different_mint_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&token_with_different_mint_pubkey, &token_account);
|
||||
|
||||
// Test getTokenAccountsByOwner with Token program id returns all accounts, regardless of Mint address
|
||||
let req = format!(
|
||||
r#"{{
|
||||
|
@ -7314,8 +7436,7 @@ pub mod tests {
|
|||
"method":"getTokenAccountsByOwner",
|
||||
"params":["{}", {{"programId": "{}"}}, {{"encoding":"base64"}}]
|
||||
}}"#,
|
||||
owner,
|
||||
spl_token_id(),
|
||||
owner, program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -7332,8 +7453,7 @@ pub mod tests {
|
|||
"method":"getTokenAccountsByOwner",
|
||||
"params":["{}", {{"programId": "{}"}}, {{"encoding": "jsonParsed"}}]
|
||||
}}"#,
|
||||
owner,
|
||||
spl_token_id(),
|
||||
owner, program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -7350,14 +7470,19 @@ pub mod tests {
|
|||
"method":"getProgramAccounts",
|
||||
"params":["{}", {{"encoding": "jsonParsed"}}]
|
||||
}}"#,
|
||||
spl_token_id(),
|
||||
program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
let accounts: Vec<RpcKeyedAccount> =
|
||||
serde_json::from_value(result["result"].clone()).unwrap();
|
||||
if program_id == inline_spl_token::id() {
|
||||
// native mint is included for token-v3
|
||||
assert_eq!(accounts.len(), 4);
|
||||
} else {
|
||||
assert_eq!(accounts.len(), 3);
|
||||
}
|
||||
|
||||
// Test returns only mint accounts
|
||||
let req = format!(
|
||||
|
@ -7414,7 +7539,7 @@ pub mod tests {
|
|||
"params":["{}", {{"programId": "{}"}}]
|
||||
}}"#,
|
||||
solana_sdk::pubkey::new_rand(),
|
||||
spl_token_id(),
|
||||
program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -7431,8 +7556,7 @@ pub mod tests {
|
|||
"method":"getTokenAccountsByDelegate",
|
||||
"params":["{}", {{"programId": "{}"}}, {{"encoding":"base64"}}]
|
||||
}}"#,
|
||||
delegate,
|
||||
spl_token_id(),
|
||||
delegate, program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -7497,7 +7621,7 @@ pub mod tests {
|
|||
"params":["{}", {{"programId": "{}"}}]
|
||||
}}"#,
|
||||
solana_sdk::pubkey::new_rand(),
|
||||
spl_token_id(),
|
||||
program_id,
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -7519,7 +7643,7 @@ pub mod tests {
|
|||
let mint_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: mint_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(
|
||||
|
@ -7541,7 +7665,7 @@ pub mod tests {
|
|||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
let token_with_smaller_balance = solana_sdk::pubkey::new_rand();
|
||||
|
@ -7581,17 +7705,88 @@ pub mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_parsing() {
|
||||
for program_id in solana_account_decoder::parse_token::spl_token_ids() {
|
||||
let rpc = RpcHandler::start();
|
||||
let bank = rpc.working_bank();
|
||||
let RpcHandler { io, meta, .. } = rpc;
|
||||
|
||||
let mut account_data = vec![0; TokenAccount::get_packed_len()];
|
||||
let mint = SplTokenPubkey::new(&[2; 32]);
|
||||
let owner = SplTokenPubkey::new(&[3; 32]);
|
||||
let delegate = SplTokenPubkey::new(&[4; 32]);
|
||||
let token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let (program_name, account_size, mint_size) = if program_id
|
||||
== inline_spl_token_2022::id()
|
||||
{
|
||||
let account_base = TokenAccount {
|
||||
mint,
|
||||
owner,
|
||||
delegate: COption::Some(delegate),
|
||||
amount: 420,
|
||||
state: TokenAccountState::Initialized,
|
||||
is_native: COption::Some(10),
|
||||
delegated_amount: 30,
|
||||
close_authority: COption::Some(owner),
|
||||
};
|
||||
let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
|
||||
ExtensionType::ImmutableOwner,
|
||||
ExtensionType::MemoTransfer,
|
||||
]);
|
||||
let mut account_data = vec![0; account_size];
|
||||
let mut account_state =
|
||||
StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)
|
||||
.unwrap();
|
||||
|
||||
account_state.base = account_base;
|
||||
account_state.pack_base();
|
||||
account_state.init_account_type().unwrap();
|
||||
account_state.init_extension::<ImmutableOwner>().unwrap();
|
||||
let mut memo_transfer = account_state.init_extension::<MemoTransfer>().unwrap();
|
||||
memo_transfer.require_incoming_transfer_memos = true.into();
|
||||
|
||||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&token_account_pubkey, &token_account);
|
||||
|
||||
let mint_size =
|
||||
ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
|
||||
let mint_base = Mint {
|
||||
mint_authority: COption::Some(owner),
|
||||
supply: 500,
|
||||
decimals: 2,
|
||||
is_initialized: true,
|
||||
freeze_authority: COption::Some(owner),
|
||||
};
|
||||
let mut mint_data = vec![0; mint_size];
|
||||
let mut mint_state =
|
||||
StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
|
||||
|
||||
mint_state.base = mint_base;
|
||||
mint_state.pack_base();
|
||||
mint_state.init_account_type().unwrap();
|
||||
let mut mint_close_authority =
|
||||
mint_state.init_extension::<MintCloseAuthority>().unwrap();
|
||||
mint_close_authority.close_authority =
|
||||
OptionalNonZeroPubkey::try_from(Some(owner)).unwrap();
|
||||
|
||||
let mint_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: mint_data.to_vec(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
|
||||
("spl-token-2022", account_size, mint_size)
|
||||
} else {
|
||||
let account_size = TokenAccount::get_packed_len();
|
||||
let mut account_data = vec![0; account_size];
|
||||
let token_account = TokenAccount {
|
||||
mint,
|
||||
owner,
|
||||
|
@ -7606,14 +7801,14 @@ pub mod tests {
|
|||
let token_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: account_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
let token_account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
bank.store_account(&token_account_pubkey, &token_account);
|
||||
|
||||
// Add the mint
|
||||
let mut mint_data = vec![0; Mint::get_packed_len()];
|
||||
let mint_size = Mint::get_packed_len();
|
||||
let mut mint_data = vec![0; mint_size];
|
||||
let mint_state = Mint {
|
||||
mint_authority: COption::Some(owner),
|
||||
supply: 500,
|
||||
|
@ -7625,10 +7820,12 @@ pub mod tests {
|
|||
let mint_account = AccountSharedData::from(Account {
|
||||
lamports: 111,
|
||||
data: mint_data.to_vec(),
|
||||
owner: spl_token_id(),
|
||||
owner: program_id,
|
||||
..Account::default()
|
||||
});
|
||||
bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
|
||||
("spl-token", account_size, mint_size)
|
||||
};
|
||||
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}", {{"encoding": "jsonParsed"}}]}}"#,
|
||||
|
@ -7637,11 +7834,9 @@ pub mod tests {
|
|||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(
|
||||
result["result"]["value"]["data"],
|
||||
json!({
|
||||
"program": "spl-token",
|
||||
"space": TokenAccount::get_packed_len(),
|
||||
let mut expected_value = json!({
|
||||
"program": program_name,
|
||||
"space": account_size,
|
||||
"parsed": {
|
||||
"type": "account",
|
||||
"info": {
|
||||
|
@ -7671,8 +7866,21 @@ pub mod tests {
|
|||
"closeAuthority": owner.to_string(),
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
if program_id == inline_spl_token_2022::id() {
|
||||
expected_value["parsed"]["info"]["extensions"] = json!([
|
||||
{
|
||||
"extension": "immutableOwner"
|
||||
},
|
||||
{
|
||||
"extension": "memoTransfer",
|
||||
"state": {
|
||||
"requireIncomingTransferMemos": true
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
assert_eq!(result["result"]["value"]["data"], expected_value);
|
||||
|
||||
// Test Mint
|
||||
let req = format!(
|
||||
|
@ -7682,11 +7890,9 @@ pub mod tests {
|
|||
let res = io.handle_request_sync(&req, meta);
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(
|
||||
result["result"]["value"]["data"],
|
||||
json!({
|
||||
"program": "spl-token",
|
||||
"space": Mint::get_packed_len(),
|
||||
let mut expected_value = json!({
|
||||
"program": program_name,
|
||||
"space": mint_size,
|
||||
"parsed": {
|
||||
"type": "mint",
|
||||
"info": {
|
||||
|
@ -7697,12 +7903,24 @@ pub mod tests {
|
|||
"freezeAuthority": owner.to_string(),
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
if program_id == inline_spl_token_2022::id() {
|
||||
expected_value["parsed"]["info"]["extensions"] = json!([
|
||||
{
|
||||
"extension": "mintCloseAuthority",
|
||||
"state": {
|
||||
"closeAuthority": owner.to_string(),
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
assert_eq!(result["result"]["value"]["data"], expected_value,);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_spl_token_owner_filter() {
|
||||
// Filtering on token-v3 length
|
||||
let owner = Pubkey::new_unique();
|
||||
assert_eq!(
|
||||
get_spl_token_owner_filter(
|
||||
|
@ -7720,6 +7938,62 @@ pub mod tests {
|
|||
owner
|
||||
);
|
||||
|
||||
// Filtering on token-2022 account type
|
||||
assert_eq!(
|
||||
get_spl_token_owner_filter(
|
||||
&Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 32,
|
||||
bytes: MemcmpEncodedBytes::Bytes(owner.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
owner
|
||||
);
|
||||
|
||||
// Filtering on token account state
|
||||
assert_eq!(
|
||||
get_spl_token_owner_filter(
|
||||
&Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 32,
|
||||
bytes: MemcmpEncodedBytes::Bytes(owner.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::TokenAccountState,
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
owner
|
||||
);
|
||||
|
||||
// Can't filter on account type for token-v3
|
||||
assert!(get_spl_token_owner_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 32,
|
||||
bytes: MemcmpEncodedBytes::Bytes(owner.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
|
||||
// Filtering on mint instead of owner
|
||||
assert!(get_spl_token_owner_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
|
@ -7747,6 +8021,143 @@ pub mod tests {
|
|||
],
|
||||
)
|
||||
.is_none());
|
||||
assert!(get_spl_token_owner_filter(
|
||||
&Pubkey::new_unique(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 32,
|
||||
bytes: MemcmpEncodedBytes::Bytes(owner.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_spl_token_mint_filter() {
|
||||
// Filtering on token-v3 length
|
||||
let mint = Pubkey::new_unique();
|
||||
assert_eq!(
|
||||
get_spl_token_mint_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::DataSize(165)
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
mint
|
||||
);
|
||||
|
||||
// Filtering on token-2022 account type
|
||||
assert_eq!(
|
||||
get_spl_token_mint_filter(
|
||||
&Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
mint
|
||||
);
|
||||
|
||||
// Filtering on token account state
|
||||
assert_eq!(
|
||||
get_spl_token_mint_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::TokenAccountState,
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
mint
|
||||
);
|
||||
|
||||
// Can't filter on account type for token-v3
|
||||
assert!(get_spl_token_mint_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
|
||||
// Filtering on owner instead of mint
|
||||
assert!(get_spl_token_mint_filter(
|
||||
&Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 32,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::DataSize(165)
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
|
||||
// Wrong program id
|
||||
assert!(get_spl_token_mint_filter(
|
||||
&Pubkey::new_unique(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::DataSize(165)
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
assert!(get_spl_token_mint_filter(
|
||||
&Pubkey::new_unique(),
|
||||
&[
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(mint.to_bytes().to_vec()),
|
||||
encoding: None
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 165,
|
||||
bytes: MemcmpEncodedBytes::Bytes(vec![ACCOUNTTYPE_ACCOUNT]),
|
||||
encoding: None
|
||||
})
|
||||
],
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::inline_spl_token::{self, GenericTokenAccount};
|
|||
solana_sdk::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
|
||||
|
||||
// `spl_token_program_2022::extension::AccountType::Account` ordinal value
|
||||
const ACCOUNTTYPE_ACCOUNT: u8 = 2;
|
||||
pub const ACCOUNTTYPE_ACCOUNT: u8 = 2;
|
||||
|
||||
pub struct Account;
|
||||
impl GenericTokenAccount for Account {
|
||||
|
|
Loading…
Reference in New Issue