Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit c878c44

Browse files
authored
Avoid unwrap in get_token_account for solana (#1138)
1 parent 1862e8a commit c878c44

9 files changed

Lines changed: 104 additions & 28 deletions

File tree

crates/gem_solana/src/address.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ mod tests {
1111
#[test]
1212
fn test_solana_address() {
1313
let address = "GvhwZwtV32kYUXUw965CUM3KGPdtBsDwPVpi92brY5R2";
14-
let parsed = Pubkey::from_base58(address).unwrap();
14+
let pubkey = Pubkey::from_base58(address).unwrap();
1515

1616
assert!(validate_address(address));
17-
assert_eq!(parsed.as_bytes().len(), 32);
18-
assert_eq!(parsed.to_base58(), address);
17+
assert_eq!(pubkey.as_bytes().len(), 32);
18+
assert_eq!(pubkey.to_base58(), address);
1919
assert!(!validate_address("invalid"));
2020
}
2121
}

crates/gem_solana/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub mod signer;
1919

2020
pub use address::validate_address;
2121
pub use jsonrpc::SolanaRpc;
22-
pub use solana_primitives::{Pubkey, find_program_address};
22+
pub use solana_primitives::{Pubkey, SolanaError, find_program_address};
2323
pub use transaction::{decode_transaction, try_decode_transaction};
2424

2525
#[cfg(all(feature = "reqwest", not(feature = "rpc")))]

crates/gem_solana/src/signer/instructions/stake_account.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ pub(super) fn from_blockhash(sender: &Pubkey, input: &SignerInput) -> Result<Pub
9494

9595
pub(super) fn seed_from_blockhash(input: &SignerInput) -> Result<String, SignerError> {
9696
let block_hash = input.metadata.get_block_hash()?;
97-
Ok(block_hash[..block_hash.len().min(32)].to_string())
97+
block_hash
98+
.get(..block_hash.len().min(32))
99+
.map(String::from)
100+
.ok_or_else(|| SignerError::invalid_input("invalid Solana block hash"))
98101
}
99102

100103
fn create_with_seed_instruction(sender: Pubkey, stake_account: Pubkey, seed: String, lamports: u64) -> Result<Instruction, SignerError> {
@@ -201,3 +204,21 @@ fn delegate_instruction(stake_account: Pubkey, validator: Pubkey, authority: Pub
201204
fn program() -> Result<Pubkey, SignerError> {
202205
Pubkey::from_base58(STAKE_PROGRAM_ID).map_err(SignerError::from_display)
203206
}
207+
208+
#[cfg(test)]
209+
mod tests {
210+
use super::*;
211+
use primitives::SignerInput;
212+
213+
#[test]
214+
fn test_seed_from_blockhash() {
215+
let valid_block_hash = "1".repeat(44);
216+
assert_eq!(seed_from_blockhash(&SignerInput::mock_solana(&valid_block_hash)).unwrap(), "1".repeat(32));
217+
218+
let invalid_block_hash = format!("{}é", "1".repeat(31));
219+
assert_eq!(
220+
seed_from_blockhash(&SignerInput::mock_solana(&invalid_block_hash)).unwrap_err().to_string(),
221+
"Invalid input: invalid Solana block hash"
222+
);
223+
}
224+
}

crates/gem_solana/src/signer/testkit.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use primitives::testkit::signer_mock::TEST_PRIVATE_KEY;
33
use primitives::{SolanaTokenProgramId, TransactionLoadMetadata};
44
use solana_primitives::{Pubkey, VersionedTransaction, get_address};
55

6-
pub const TEST_BLOCK_HASH: &str = "11111111111111111111111111111111";
76
pub const TEST_RECIPIENT: &str = "EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd";
87
pub const TEST_SENDER_TOKEN_ADDRESS: &str = "HEeranxp3y7kVQKVSLdZW1rUmnbs7bAtUTMu8o88Jash";
98

@@ -16,12 +15,7 @@ pub fn sender_address_for_key(private_key: &[u8]) -> String {
1615
}
1716

1817
pub fn solana_metadata(sender_token_address: Option<&str>, recipient_token_address: Option<&str>, token_program: Option<SolanaTokenProgramId>) -> TransactionLoadMetadata {
19-
TransactionLoadMetadata::Solana {
20-
sender_token_address: sender_token_address.map(String::from),
21-
recipient_token_address: recipient_token_address.map(String::from),
22-
token_program,
23-
block_hash: TEST_BLOCK_HASH.to_string(),
24-
}
18+
TransactionLoadMetadata::mock_solana_token(sender_token_address, recipient_token_address, token_program)
2519
}
2620

2721
pub fn private_key_base58(value: &str) -> Vec<u8> {

crates/gem_solana/src/token_account.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use crate::{ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, Pubkey, find_program_address};
1+
use std::sync::LazyLock;
22

3-
pub fn get_token_account(wallet: &str, token_mint: &str, token_program: &str) -> String {
4-
let owner = Pubkey::from_base58(wallet).unwrap();
5-
let token_program = Pubkey::from_base58(token_program).unwrap();
6-
let mint = Pubkey::from_base58(token_mint).unwrap();
7-
let associated_token_program = Pubkey::from_base58(ASSOCIATED_TOKEN_ACCOUNT_PROGRAM).unwrap();
3+
use crate::{ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, Pubkey, SolanaError, find_program_address};
4+
5+
static ASSOCIATED_TOKEN_PROGRAM: LazyLock<Pubkey> = LazyLock::new(|| Pubkey::from_base58(ASSOCIATED_TOKEN_ACCOUNT_PROGRAM).unwrap());
6+
7+
pub fn get_token_account(wallet: &str, token_mint: &str, token_program: &str) -> Result<String, SolanaError> {
8+
let owner = Pubkey::from_base58(wallet)?;
9+
let token_program = Pubkey::from_base58(token_program)?;
10+
let mint = Pubkey::from_base58(token_mint)?;
811
let seeds = [owner.as_bytes().as_ref(), token_program.as_bytes().as_ref(), mint.as_bytes().as_ref()];
912

10-
find_program_address(&associated_token_program, &seeds).unwrap().0.to_string()
13+
Ok(find_program_address(&ASSOCIATED_TOKEN_PROGRAM, &seeds)?.0.to_string())
1114
}
1215

1316
#[cfg(test)]
@@ -16,7 +19,7 @@ mod tests {
1619
use crate::{PYUSD_TOKEN_MINT, TOKEN_PROGRAM, TOKEN_PROGRAM_2022, USDC_TOKEN_MINT, USDS_TOKEN_MINT, USDT_TOKEN_MINT, WSOL_TOKEN_ADDRESS};
1720

1821
#[test]
19-
fn test_get_token_account() {
22+
fn test_get_token_account() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
2023
let test_cases = [
2124
(
2225
"CzVqG98YbFNiMREwgTswSML59CNrfobsNX4N9j6K8fbC",
@@ -51,8 +54,12 @@ mod tests {
5154
];
5255

5356
for (wallet, token_mint, token_program, expected_token_account) in test_cases.iter() {
54-
let fee_token_account = get_token_account(wallet, token_mint, token_program);
57+
let fee_token_account = get_token_account(wallet, token_mint, token_program)?;
5558
assert_eq!(fee_token_account, *expected_token_account);
5659
}
60+
61+
assert!(get_token_account("invalid", USDC_TOKEN_MINT, TOKEN_PROGRAM).is_err());
62+
63+
Ok(())
5764
}
5865
}

crates/primitives/src/testkit/transaction_load_input_mock.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ impl SignerInput {
131131
TransactionFee::default(),
132132
)
133133
}
134+
135+
pub fn mock_solana(block_hash: &str) -> Self {
136+
SignerInput::new(TransactionLoadInput::mock_solana(block_hash), TransactionFee::default())
137+
}
134138
}
135139

136140
impl TransactionLoadInput {
@@ -150,6 +154,19 @@ impl TransactionLoadInput {
150154
}
151155
}
152156

157+
pub fn mock_solana(block_hash: &str) -> Self {
158+
TransactionLoadInput {
159+
input_type: TransactionInputType::Transfer(Asset::mock_sol()),
160+
sender_address: String::new(),
161+
destination_address: String::new(),
162+
value: "0".to_string(),
163+
gas_price: GasPriceType::regular(0),
164+
memo: None,
165+
is_max_value: false,
166+
metadata: TransactionLoadMetadata::mock_solana(block_hash),
167+
}
168+
}
169+
153170
pub fn mock_transfer(asset: Asset, sender: &str, destination: &str, value: &str, fee: u64, memo: Option<&str>, metadata: TransactionLoadMetadata) -> Self {
154171
TransactionLoadInput {
155172
input_type: TransactionInputType::Transfer(asset),

crates/primitives/src/testkit/transaction_load_metadata_mock.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{TransactionLoadMetadata, stake_type::TronStakeData};
1+
use crate::{SolanaTokenProgramId, TransactionLoadMetadata, stake_type::TronStakeData};
22

33
impl TransactionLoadMetadata {
44
pub fn mock_aptos() -> Self {
@@ -48,4 +48,22 @@ impl TransactionLoadMetadata {
4848
sequence,
4949
}
5050
}
51+
52+
pub fn mock_solana(block_hash: &str) -> Self {
53+
TransactionLoadMetadata::Solana {
54+
sender_token_address: None,
55+
recipient_token_address: None,
56+
token_program: None,
57+
block_hash: block_hash.to_string(),
58+
}
59+
}
60+
61+
pub fn mock_solana_token(sender_token_address: Option<&str>, recipient_token_address: Option<&str>, token_program: Option<SolanaTokenProgramId>) -> Self {
62+
TransactionLoadMetadata::Solana {
63+
sender_token_address: sender_token_address.map(String::from),
64+
recipient_token_address: recipient_token_address.map(String::from),
65+
token_program,
66+
block_hash: "11111111111111111111111111111111".to_string(),
67+
}
68+
}
5169
}

crates/swapper/src/error.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ impl From<gem_ton::tvm::TvmError> for SwapperError {
110110
}
111111
}
112112

113+
impl From<gem_solana::SolanaError> for SwapperError {
114+
fn from(err: gem_solana::SolanaError) -> Self {
115+
Self::ComputeQuoteError(format!("Solana error: {err}"))
116+
}
117+
}
118+
113119
impl From<primitives::AddressError> for SwapperError {
114120
fn from(err: primitives::AddressError) -> Self {
115121
Self::ComputeQuoteError(format!("{INVALID_ADDRESS}: {err}"))
@@ -145,3 +151,16 @@ impl From<number_formatter::NumberFormatterError> for SwapperError {
145151
Self::ComputeQuoteError(format!("{}: {err}", INVALID_AMOUNT))
146152
}
147153
}
154+
155+
#[cfg(test)]
156+
mod tests {
157+
use super::*;
158+
159+
#[test]
160+
fn test_solana_error_mapping() {
161+
assert_eq!(
162+
SwapperError::from(gem_solana::SolanaError::InvalidTransaction),
163+
SwapperError::ComputeQuoteError("Solana error: Invalid transaction".to_string())
164+
);
165+
}
166+
}

crates/swapper/src/jupiter/provider.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ where
6060
input.to_string()
6161
}
6262

63-
fn get_fee_token_account(&self, options: &Options, mint: &str, token_program: &str) -> Option<String> {
63+
fn get_fee_token_account(&self, options: &Options, mint: &str, token_program: &str) -> Result<Option<String>, SwapperError> {
6464
if let Some(fee) = &options.fee {
65-
let fee_account = get_token_account(&fee.solana.address, mint, token_program);
66-
return Some(fee_account);
65+
let fee_account = get_token_account(&fee.solana.address, mint, token_program)?;
66+
return Ok(Some(fee_account));
6767
}
68-
None
68+
Ok(None)
6969
}
7070

7171
async fn fetch_token_program(&self, mint: &str) -> Result<String, SwapperError> {
@@ -83,12 +83,12 @@ where
8383
let fee_mint = self.get_fee_mint(input_mint, output_mint);
8484
// if fee_mint is in preset, no need to fetch token program
8585
let token_program = if self.fee_mints.contains(fee_mint.as_str()) {
86-
return Ok(self.get_fee_token_account(options, fee_mint.as_str(), TOKEN_PROGRAM).unwrap_or_default());
86+
return Ok(self.get_fee_token_account(options, fee_mint.as_str(), TOKEN_PROGRAM)?.unwrap_or_default());
8787
} else {
8888
self.fetch_token_program(&fee_mint).await?
8989
};
9090

91-
let mut fee_account = self.get_fee_token_account(options, &fee_mint, &token_program).unwrap_or_default();
91+
let mut fee_account = self.get_fee_token_account(options, &fee_mint, &token_program)?.unwrap_or_default();
9292
if fee_account.is_empty() {
9393
return Ok(fee_account);
9494
}

0 commit comments

Comments
 (0)