Skip to content

Commit

Permalink
[Solana]: Add a function for generating token addresses for the Token…
Browse files Browse the repository at this point in the history
…-2022 Program (#4010)

* Add function TWSolanaAddressToken2022Address

* Extract common code into a separate function to reduce duplication

* Add C++ test cases for TWSolanaAddressToken2022Address
  • Loading branch information
10gic committed Sep 11, 2024
1 parent db50956 commit 992b248
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 9 deletions.
8 changes: 8 additions & 0 deletions include/TrustWalletCore/TWSolanaAddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ void TWSolanaAddressDelete(struct TWSolanaAddress* _Nonnull address);
TW_EXPORT_METHOD
TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress);

/// Derive token 2022 address for token
///
/// \param address Non-null pointer to a Solana Address
/// \param tokenMintAddress Non-null pointer to a token mint address as a string
/// \return Null pointer if the token 2022 address for a token is not found, valid pointer otherwise
TW_EXPORT_METHOD
TWString* _Nullable TWSolanaAddressToken2022Address(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress);

/// Returns the address string representation.
///
/// \param address Non-null pointer to a Solana Address
Expand Down
3 changes: 2 additions & 1 deletion rust/chains/tw_solana/src/program/stake_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ impl StakeProgram {
/// https://github.com/solana-labs/solana-program-library/blob/master/associated-token-account/program/src/lib.rs#L35
pub fn get_associated_token_address(
main_address: SolanaAddress,
token_program_id: SolanaAddress,
token_mint_address: SolanaAddress,
) -> AddressResult<SolanaAddress> {
SolanaAddress::find_program_address(
&[
main_address.bytes().as_slice(),
TOKEN_PROGRAM_ID_ADDRESS.bytes().as_slice(),
token_program_id.bytes().as_slice(),
token_mint_address.bytes().as_slice(),
],
*ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS,
Expand Down
9 changes: 7 additions & 2 deletions rust/chains/tw_solana/tests/get_default_token_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::str::FromStr;
use tw_solana::address::SolanaAddress;
use tw_solana::blockhash::Blockhash;
use tw_solana::defined_addresses::TOKEN_PROGRAM_ID_ADDRESS;
use tw_solana::program::stake_program::StakeProgram;

fn test_get_default_token_address_impl(
Expand All @@ -16,8 +17,12 @@ fn test_get_default_token_address_impl(
let token_mint_address = SolanaAddress::from_str(token_mint_address).unwrap();
let expected = SolanaAddress::from_str(expected).unwrap();

let actual = StakeProgram::get_associated_token_address(main_address, token_mint_address)
.expect("!get_associated_token_address");
let actual = StakeProgram::get_associated_token_address(
main_address,
*TOKEN_PROGRAM_ID_ADDRESS,
token_mint_address,
)
.expect("!get_associated_token_address");
assert_eq!(actual, expected);
}

Expand Down
35 changes: 30 additions & 5 deletions rust/wallet_core_rs/src/ffi/solana/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use tw_memory::ffi::tw_string::TWString;
use tw_memory::ffi::RawPtrTrait;
use tw_misc::try_or_else;
use tw_solana::address::SolanaAddress;
use tw_solana::defined_addresses::{TOKEN_2022_PROGRAM_ID_ADDRESS, TOKEN_PROGRAM_ID_ADDRESS};
use tw_solana::program::stake_program::StakeProgram;

/// Derive default token address for token
Expand All @@ -20,26 +21,50 @@ use tw_solana::program::stake_program::StakeProgram;
pub unsafe extern "C" fn tw_solana_address_default_token_address(
address: *const TWString,
token_mint_address: *const TWString,
) -> *mut TWString {
tw_solana_address_token_address_impl(address, token_mint_address, *TOKEN_PROGRAM_ID_ADDRESS)
}

/// Derive token 2022 address for token
///
/// \param address Non-null pointer to a Solana Address
/// \param token_mint_address Non-null pointer to a token mint address as a string
/// \return Null pointer if the token 2022 address for a token is not found, valid pointer otherwise
#[no_mangle]
pub unsafe extern "C" fn tw_solana_address_token_2022_address(
address: *const TWString,
token_mint_address: *const TWString,
) -> *mut TWString {
tw_solana_address_token_address_impl(
address,
token_mint_address,
*TOKEN_2022_PROGRAM_ID_ADDRESS,
)
}

unsafe fn tw_solana_address_token_address_impl(
address: *const TWString,
token_mint_address: *const TWString,
token_address: SolanaAddress,
) -> *mut TWString {
let main_address = try_or_else!(TWString::from_ptr_as_ref(address), std::ptr::null_mut);
let main_address = try_or_else!(main_address.as_str(), std::ptr::null_mut);
let main_address = try_or_else!(SolanaAddress::from_str(main_address), std::ptr::null_mut);

let token_mint_address = try_or_else!(
TWString::from_ptr_as_ref(token_mint_address),
std::ptr::null_mut
);
let token_mint_address = try_or_else!(token_mint_address.as_str(), std::ptr::null_mut);

let main_address = try_or_else!(SolanaAddress::from_str(main_address), std::ptr::null_mut);
let token_mint_address = try_or_else!(
SolanaAddress::from_str(token_mint_address),
std::ptr::null_mut
);

let token_address = try_or_else!(
StakeProgram::get_associated_token_address(main_address, token_mint_address),
let associated_token_address = try_or_else!(
StakeProgram::get_associated_token_address(main_address, token_address, token_mint_address),
std::ptr::null_mut
);

TWString::from(token_address.to_string()).into_ptr()
TWString::from(associated_token_address.to_string()).into_ptr()
}
24 changes: 23 additions & 1 deletion rust/wallet_core_rs/tests/solana/solana_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// Copyright © 2017 Trust Wallet.

use tw_memory::test_utils::tw_string_helper::TWStringHelper;
use wallet_core_rs::ffi::solana::address::tw_solana_address_default_token_address;
use wallet_core_rs::ffi::solana::address::{
tw_solana_address_default_token_address, tw_solana_address_token_2022_address,
};

#[test]
fn test_solana_address_default_token_address() {
Expand All @@ -24,3 +26,23 @@ fn test_solana_address_default_token_address() {

assert_eq!(actual, "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP");
}

#[test]
fn test_solana_address_token_2022_address() {
let main_address = "68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t";
let main_address = TWStringHelper::create(main_address);

let token_mint_address = "7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icFv1";
let token_mint_address = TWStringHelper::create(token_mint_address);

let actual = unsafe {
TWStringHelper::wrap(tw_solana_address_token_2022_address(
main_address.ptr(),
token_mint_address.ptr(),
))
}
.to_string()
.expect("!tw_solana_address_associated_token_address returned a nullptr");

assert_eq!(actual, "3PaFQnebQMHBgthRScup2B932cMxA1GBP7m9roCkomHq");
}
19 changes: 19 additions & 0 deletions src/interface/TWSolanaAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _
}
}

TWString* _Nullable TWSolanaAddressToken2022Address(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) {
try {
if (address == nullptr || tokenMintAddress == nullptr) {
return nullptr;
}
Rust::TWStringWrapper tokenMintAddressWrapper = TWStringUTF8Bytes(tokenMintAddress);
Rust::TWStringWrapper mainAddress = address->impl.string();

Rust::TWStringWrapper newTokenAddress = Rust::tw_solana_address_token_2022_address(mainAddress.get(), tokenMintAddressWrapper.get());

if (!newTokenAddress) {
return nullptr;
}
return TWStringCreateWithUTF8Bytes(newTokenAddress.c_str());
} catch (...) {
return nullptr;
}
}

TWString* _Nonnull TWSolanaAddressDescription(struct TWSolanaAddress* _Nonnull address) {
return TWStringCreateWithUTF8Bytes(address->impl.string().c_str());
}
23 changes: 23 additions & 0 deletions tests/chains/Solana/TWSolanaAddressTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,26 @@ TEST(TWSolanaProgram, defaultTokenAddressError) {

EXPECT_EQ(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), serumToken.get()), nullptr);
}

TEST(TWSolanaProgram, token2022Address) {
const auto solAddress = STRING("68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t");
const auto catwifhatToken = STRING("7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icFv1");

auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get()));
auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get()));
auto tokenAddress = WRAPS(TWSolanaAddressToken2022Address(solanaAddress.get(), catwifhatToken.get()));

assertStringsEqual(tokenAddress, "3PaFQnebQMHBgthRScup2B932cMxA1GBP7m9roCkomHq");
assertStringsEqual(description, "68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t");
}

TEST(TWSolanaProgram, token2022AddressError) {
const auto solAddress = STRING("68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t");
// Invalid token mint address.
const auto catwifhatToken = STRING("7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icF");

auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get()));
auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get()));

EXPECT_EQ(TWSolanaAddressToken2022Address(solanaAddress.get(), catwifhatToken.get()), nullptr);
}

0 comments on commit 992b248

Please sign in to comment.