Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/src/main/java/cash/p/terminal/core/Interfaces.kt
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,6 @@ interface ISendTronAdapter : IBalanceAdapter {
suspend fun send(amount: BigDecimal, to: TronAddress, feeLimit: Long?): String
suspend fun send(contract: Contract, feeLimit: Long?): String
suspend fun send(createdTransaction: CreatedTransaction): String
suspend fun isAddressActive(address: TronAddress): Boolean
fun isOwnAddress(address: TronAddress): Boolean

suspend fun getNowBlock(): NowBlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,6 @@ abstract class BaseTronAdapter(
)
}

override suspend fun isAddressActive(address: Address): Boolean = withContext(Dispatchers.IO) {
tronKit.isAccountActive(address)
}

override fun isOwnAddress(address: Address): Boolean {
return address == tronKit.address
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cash.p.terminal.modules.send.tron

import cash.p.terminal.R
import cash.p.terminal.core.ISendTronAdapter
import cash.p.terminal.core.tryOrNull
import cash.p.terminal.entities.Address
import cash.p.terminal.strings.helpers.Translator
import cash.p.terminal.ui_compose.entities.FormsInputStateWarning
import cash.p.terminal.wallet.Token
import cash.p.terminal.wallet.entities.TokenType
Expand All @@ -18,45 +20,41 @@ class SendTronAddressService(
private var address: Address? = null
private var addressError: Throwable? = null
private var tronAddress: TronAddress? = null
private var isInactiveAddress: Boolean = false

private val _stateFlow = MutableStateFlow(
State(
address = address,
tronAddress = tronAddress,
addressError = addressError,
isInactiveAddress = isInactiveAddress,
canBeSend = tronAddress != null && (addressError == null || addressError is FormsInputStateWarning)
)
)
val stateFlow = _stateFlow.asStateFlow()

suspend fun setAddress(address: Address?) {
fun setAddress(address: Address?) {
this.address = address

validateAddress()

emitState()
}

private suspend fun validateAddress() {
private fun validateAddress() {
addressError = null
tronAddress = null
val address = this.address ?: return

try {
val validAddress = TronAddress.fromBase58(address.hex)
isInactiveAddress = !adapter.isAddressActive(validAddress)

if (token.type == TokenType.Native && adapter.isOwnAddress(validAddress)) {
addressError = Throwable(cash.p.terminal.strings.helpers.Translator.getString(R.string.Tron_SelfSendTrxNotAllowed))
}
val validAddress = tryOrNull { TronAddress.fromBase58(address.hex) }
if (validAddress == null) {
addressError = Throwable(Translator.getString(R.string.SwapSettings_Error_InvalidAddress))
return
}

tronAddress = validAddress
} catch (e: Exception) {
isInactiveAddress = false
addressError = Throwable(cash.p.terminal.strings.helpers.Translator.getString(R.string.SwapSettings_Error_InvalidAddress))
if (token.type == TokenType.Native && adapter.isOwnAddress(validAddress)) {
addressError = Throwable(Translator.getString(R.string.Tron_SelfSendTrxNotAllowed))
}

tronAddress = validAddress
}

private fun emitState() {
Expand All @@ -65,7 +63,6 @@ class SendTronAddressService(
address = address,
tronAddress = tronAddress,
addressError = addressError,
isInactiveAddress = isInactiveAddress,
canBeSend = tronAddress != null && (addressError == null || addressError is FormsInputStateWarning)
)
}
Expand All @@ -75,7 +72,6 @@ class SendTronAddressService(
val address: Address?,
val tronAddress: TronAddress?,
val addressError: Throwable?,
val isInactiveAddress: Boolean,
val canBeSend: Boolean
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ data class SendTronConfirmationData(
val contact: Contact?,
val coin: Coin,
val feeCoin: Coin,
val isInactiveAddress: Boolean,
val memo: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,7 @@ class SendTronViewModel(
feeCoinRate = it
}
}
viewModelScope.launch {
addressService.setAddress(address)
}
addressService.setAddress(address)
if (offlineSignSupported) {
viewModelScope.launch {
// Keep the TAPOS anchor filled whenever online: fetch on connect/reconnect and retry
Expand Down Expand Up @@ -196,9 +194,7 @@ class SendTronViewModel(

fun onEnterAddress(address: Address?) {
resetRiskAccepted()
viewModelScope.launch {
addressService.setAddress(address)
}
addressService.setAddress(address)
}

fun onNavigateToConfirmation() {
Expand All @@ -212,8 +208,7 @@ class SendTronViewModel(
address = address,
contact = contact(address),
coin = wallet.coin,
feeCoin = feeToken.coin,
isInactiveAddress = addressState.isInactiveAddress
feeCoin = feeToken.coin
)

viewModelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cash.p.terminal.modules.send.tron

import cash.p.terminal.core.ISendTronAdapter
import cash.p.terminal.entities.Address
import cash.p.terminal.wallet.Token
import cash.p.terminal.wallet.entities.Coin
import cash.p.terminal.wallet.entities.TokenType
import io.horizontalsystems.core.entities.Blockchain
import io.horizontalsystems.core.entities.BlockchainType
import io.horizontalsystems.tronkit.models.Address as TronAddress
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class SendTronAddressServiceTest {

private val adapter = mockk<ISendTronAdapter>(relaxed = true)
private val service = SendTronAddressService(adapter, trxToken)
private val address = Address(TRON_ADDRESS)
private val tronAddress = TronAddress.fromBase58(TRON_ADDRESS)

@Before
fun setUp() {
every { adapter.isOwnAddress(any()) } returns false
}

@Test
fun setAddress_validAddress_allowsSendWithoutNetworkValidation() {
service.setAddress(address)

val state = service.stateFlow.value

assertEquals(address, state.address)
assertEquals(tronAddress, state.tronAddress)
assertNull(state.addressError)
assertTrue(state.canBeSend)
verify(exactly = 1) { adapter.isOwnAddress(tronAddress) }
}

@Test
fun setAddress_corruptedChecksumAddress_returnsValidationErrorWithoutCheckingOwnership() {
service.setAddress(Address(TRON_ADDRESS.dropLast(1) + "s"))

val state = service.stateFlow.value

assertNull(state.tronAddress)
assertNotNull(state.addressError)
assertFalse(state.canBeSend)
verify(exactly = 0) { adapter.isOwnAddress(any()) }
}

@Test
fun setAddress_ownNativeAddress_returnsSelfSendError() {
every { adapter.isOwnAddress(tronAddress) } returns true

service.setAddress(address)

val state = service.stateFlow.value

assertEquals(tronAddress, state.tronAddress)
assertNotNull(state.addressError)
assertFalse(state.canBeSend)
}

private companion object {
const val TRON_ADDRESS = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"

val trxToken = Token(
coin = Coin(uid = "tron", name = "TRON", code = "TRX"),
blockchain = Blockchain(BlockchainType.Tron, "TRON", null),
type = TokenType.Native,
decimals = 6,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import cash.p.terminal.wallet.MarketKitWrapper
import cash.p.terminal.wallet.Token
import cash.p.terminal.wallet.Wallet
import cash.p.terminal.wallet.WalletFactory
import cash.p.terminal.wallet.entities.BalanceData
import cash.p.terminal.wallet.entities.Coin
import cash.p.terminal.wallet.entities.TokenType
import cash.p.terminal.wallet.managers.IBalanceHiddenManager
Expand Down Expand Up @@ -143,6 +144,7 @@ class SendTronViewModelTest : KoinTest {
every { poisonAddressManager.isAddressSuspicious(any(), any(), any()) } returns false
every { payloadEncoder.encode(any()) } returns "payload"
coEvery { offlineSignedTransactionRepository.save(any(), any()) } returns Unit
every { adapter.trxBalanceData } returns BalanceData(available = BigDecimal.TEN)
coEvery { adapter.estimateFee(any(), any()) } returns listOf(Fee.Energy(required = 10, price = 2))
coEvery { adapter.getNowBlock() } returns nowBlock
coEvery { adapter.signOffline(any()) } returns SignedOfflineTronTransaction(
Expand All @@ -157,7 +159,7 @@ class SendTronViewModelTest : KoinTest {
canBeSend = value != null && value > BigDecimal.ZERO,
)
}
coEvery { addressService.setAddress(any()) } coAnswers {
every { addressService.setAddress(any()) } answers {
val value = firstArg<Address?>()
addressStateFlow.value = createAddressState(
address = value,
Expand Down Expand Up @@ -266,6 +268,25 @@ class SendTronViewModelTest : KoinTest {
}
}

@Test
fun onNavigateToConfirmation_accountActivationFee_includesActivationAndTotalFees() = runTest(dispatcher) {
coEvery { adapter.estimateFee(any(), any()) } returns listOf(
Fee.AccountActivation(amount = 1_000_000),
Fee.Bandwidth(points = 10, price = 2),
)
val viewModel = createViewModel()

viewModel.onEnterAddress(address)
viewModel.onEnterAmount(amount)
advanceUntilIdle()
viewModel.onNavigateToConfirmation()
advanceUntilIdle()

val confirmationData = checkNotNull(viewModel.confirmationData)
assertEquals(BigDecimal("1.000000"), confirmationData.activationFee)
assertEquals(BigDecimal("1.000020"), confirmationData.fee)
}

@Test
fun offlineSignSupported_watchAccount_returnsFalse() {
val watchWallet = createWallet(trxToken, account(AccountType.TronAddress(TRON_ADDRESS)))
Expand Down Expand Up @@ -322,7 +343,6 @@ class SendTronViewModelTest : KoinTest {
address = address,
tronAddress = address?.let { TronAddress.fromBase58(it.hex) },
addressError = null,
isInactiveAddress = false,
canBeSend = canBeSend,
)

Expand Down
Loading