diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt index 1403dcdaf9..09221d88a5 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt @@ -18,6 +18,7 @@ import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.model.ethpool.P2PEthPoolBroadcastResult import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import com.tangem.domain.staking.model.stakekit.StakingError @@ -81,7 +82,9 @@ internal class DefaultP2PEthPoolRepository( override suspend fun getVaults(network: P2PEthPoolNetwork): Either> = either { withContext(dispatchers.io) { handleApiResponse(p2pEthPoolApi.getVaults(network.value)) { result -> - result.vaults.map { vaultConverter.convert(it) } + result.vaults + .map { vaultConverter.convert(it) } + .filter { it.vaultAddress.lowercase() !in P2PEthPoolStakingConfig.TEST_VAULT_ADDRESSES } } } } diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/P2PEthPoolVaultFilterTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/P2PEthPoolVaultFilterTest.kt new file mode 100644 index 0000000000..c3bd1c75af --- /dev/null +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/P2PEthPoolVaultFilterTest.kt @@ -0,0 +1,100 @@ +package com.tangem.data.staking + +import com.google.common.truth.Truth.assertThat +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.ethpool.P2PEthPoolApi +import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolNetworkDTO +import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolResponse +import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolVaultDTO +import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolVaultsResponse +import com.tangem.datasource.local.token.P2PEthPoolVaultsStore +import com.tangem.domain.staking.model.StakingIntegrationID +import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork +import com.tangem.domain.staking.toggles.StakingFeatureToggles +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import java.math.BigDecimal + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class P2PEthPoolVaultFilterTest { + + private companion object { + const val PRODUCTION_VAULT_ADDRESS = "0x4c09BC47db288F998b33CD63BCc1b6ddCCe13F33" + const val TEST_VAULT_ADDRESS = "0xB72668D6FF7A0e318F83097A754c6AEd0f8AF034" + } + + private val api = mockk() + private val store = mockk(relaxed = true) + private val featureToggles = mockk { + every { isIntegrationEnabled(StakingIntegrationID.P2PEthPool) } returns true + } + private val repository = DefaultP2PEthPoolRepository( + p2pEthPoolApi = api, + p2pEthPoolVaultsStore = store, + dispatchers = TestingCoroutineDispatcherProvider(), + stakingFeatureToggles = featureToggles, + ) + + private fun buildVaultDTO(address: String) = P2PEthPoolVaultDTO( + vaultAddress = address, + displayName = "Vault $address", + apy = BigDecimal("4.5"), + baseApy = BigDecimal("4.0"), + capacity = BigDecimal("10000"), + totalAssets = BigDecimal("5000"), + feePercent = BigDecimal("10"), + isPrivate = false, + isGenesis = false, + isSmoothingPool = true, + isErc20 = false, + tokenName = null, + tokenSymbol = null, + createdAt = 0L, + ) + + private fun successResponse(vararg addresses: String) = ApiResponse.Success( + P2PEthPoolResponse( + error = null, + result = P2PEthPoolVaultsResponse( + network = P2PEthPoolNetworkDTO.MAINNET, + vaults = addresses.map { buildVaultDTO(it) }, + ), + ), + ) + + @Test + fun `test vault address is filtered from getVaults result`() = runTest { + coEvery { api.getVaults(any()) } returns successResponse(PRODUCTION_VAULT_ADDRESS, TEST_VAULT_ADDRESS) + + val vaults = repository.getVaults(P2PEthPoolNetwork.MAINNET).getOrNull() + + assertThat(vaults).hasSize(1) + assertThat(vaults?.first()?.vaultAddress).isEqualTo(PRODUCTION_VAULT_ADDRESS) + } + + @Test + fun `production vault address passes filter`() = runTest { + coEvery { api.getVaults(any()) } returns successResponse(PRODUCTION_VAULT_ADDRESS) + + val vaults = repository.getVaults(P2PEthPoolNetwork.MAINNET).getOrNull() + + assertThat(vaults).hasSize(1) + } + + @Test + fun `filter is case-insensitive`() = runTest { + coEvery { api.getVaults(any()) } returns successResponse( + TEST_VAULT_ADDRESS.uppercase(), + TEST_VAULT_ADDRESS.lowercase(), + ) + + val vaults = repository.getVaults(P2PEthPoolNetwork.MAINNET).getOrNull() + + assertThat(vaults).isEmpty() + } +} \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt index 27ea9df657..9869412961 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt @@ -11,4 +11,9 @@ object P2PEthPoolStakingConfig { val activeNetwork: P2PEthPoolNetwork get() = if (USE_TESTNET) P2PEthPoolNetwork.TESTNET else P2PEthPoolNetwork.MAINNET + + /** Vault addresses returned by the backend that should not be shown to users (test/stub vaults). Stored in lowercase. */ + val TEST_VAULT_ADDRESSES: Set = setOf( + "0xb72668d6ff7a0e318f83097a754c6aed0f8af034", + ) } \ No newline at end of file