From 992b248dfa94202019b718f714918e431ec4c305 Mon Sep 17 00:00:00 2001 From: 10gic Date: Wed, 11 Sep 2024 17:41:05 +0800 Subject: [PATCH] [Solana]: Add a function for generating token addresses for the Token-2022 Program (#4010) * Add function TWSolanaAddressToken2022Address * Extract common code into a separate function to reduce duplication * Add C++ test cases for TWSolanaAddressToken2022Address --- include/TrustWalletCore/TWSolanaAddress.h | 8 +++++ .../tw_solana/src/program/stake_program.rs | 3 +- .../tests/get_default_token_address.rs | 9 +++-- rust/wallet_core_rs/src/ffi/solana/address.rs | 35 ++++++++++++++++--- .../tests/solana/solana_address.rs | 24 ++++++++++++- src/interface/TWSolanaAddress.cpp | 19 ++++++++++ tests/chains/Solana/TWSolanaAddressTests.cpp | 23 ++++++++++++ 7 files changed, 112 insertions(+), 9 deletions(-) diff --git a/include/TrustWalletCore/TWSolanaAddress.h b/include/TrustWalletCore/TWSolanaAddress.h index 78ed40af7d2..3a21460e759 100644 --- a/include/TrustWalletCore/TWSolanaAddress.h +++ b/include/TrustWalletCore/TWSolanaAddress.h @@ -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 diff --git a/rust/chains/tw_solana/src/program/stake_program.rs b/rust/chains/tw_solana/src/program/stake_program.rs index 37ac2e794fb..e70a9a2fe3b 100644 --- a/rust/chains/tw_solana/src/program/stake_program.rs +++ b/rust/chains/tw_solana/src/program/stake_program.rs @@ -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::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, diff --git a/rust/chains/tw_solana/tests/get_default_token_address.rs b/rust/chains/tw_solana/tests/get_default_token_address.rs index d2b4690f9dc..4f075dde885 100644 --- a/rust/chains/tw_solana/tests/get_default_token_address.rs +++ b/rust/chains/tw_solana/tests/get_default_token_address.rs @@ -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( @@ -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); } diff --git a/rust/wallet_core_rs/src/ffi/solana/address.rs b/rust/wallet_core_rs/src/ffi/solana/address.rs index 9429a8f2836..2e745e4c272 100644 --- a/rust/wallet_core_rs/src/ffi/solana/address.rs +++ b/rust/wallet_core_rs/src/ffi/solana/address.rs @@ -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 @@ -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() } diff --git a/rust/wallet_core_rs/tests/solana/solana_address.rs b/rust/wallet_core_rs/tests/solana/solana_address.rs index 7bbe4ed7b39..57ff4aa5e18 100644 --- a/rust/wallet_core_rs/tests/solana/solana_address.rs +++ b/rust/wallet_core_rs/tests/solana/solana_address.rs @@ -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() { @@ -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"); +} diff --git a/src/interface/TWSolanaAddress.cpp b/src/interface/TWSolanaAddress.cpp index 5599061bf65..41a03176174 100644 --- a/src/interface/TWSolanaAddress.cpp +++ b/src/interface/TWSolanaAddress.cpp @@ -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()); } diff --git a/tests/chains/Solana/TWSolanaAddressTests.cpp b/tests/chains/Solana/TWSolanaAddressTests.cpp index d18050b8417..5607877186b 100644 --- a/tests/chains/Solana/TWSolanaAddressTests.cpp +++ b/tests/chains/Solana/TWSolanaAddressTests.cpp @@ -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); +}