From 718fb0234cb042834fc7928d8453125b77c4618a Mon Sep 17 00:00:00 2001 From: Jakub Porzuczek Date: Mon, 29 Jan 2024 15:31:16 +0100 Subject: [PATCH 1/5] compute guarantees for staking/pool tx previews, optimize claim processor logic --- .../processor/PoolContributionProcessor.kt | 45 ++++++++--- .../processor/ValidatorClaimProcessor.kt | 78 ++++++++++--------- .../processor/ValidatorStakeProcessor.kt | 6 +- .../processor/ValidatorUnstakeProcessor.kt | 6 +- 4 files changed, 85 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt index da09aee30f..6e976e3640 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt @@ -1,13 +1,13 @@ package com.babylon.wallet.android.presentation.transaction.analysis.processor -import com.babylon.wallet.android.domain.model.GuaranteeType import com.babylon.wallet.android.domain.model.Transferable import com.babylon.wallet.android.domain.model.TransferableAsset +import com.babylon.wallet.android.domain.model.assets.Asset import com.babylon.wallet.android.domain.model.assets.PoolUnit import com.babylon.wallet.android.domain.model.resources.Resource import com.babylon.wallet.android.domain.model.resources.metadata.poolUnit -import com.babylon.wallet.android.domain.usecases.GetResourcesUseCase import com.babylon.wallet.android.domain.usecases.assets.GetPoolDetailsUseCase +import com.babylon.wallet.android.domain.usecases.assets.ResolveAssetsFromAddressUseCase import com.babylon.wallet.android.presentation.transaction.AccountWithTransferableResources import com.babylon.wallet.android.presentation.transaction.PreviewType import com.radixdlt.ret.DetailedManifestClass @@ -21,37 +21,40 @@ import rdx.works.profile.domain.accountsOnCurrentNetwork import javax.inject.Inject class PoolContributionProcessor @Inject constructor( - private val getResourcesUseCase: GetResourcesUseCase, + private val resolveAssetsFromAddressUseCase: ResolveAssetsFromAddressUseCase, private val getPoolDetailsUseCase: GetPoolDetailsUseCase, private val getProfileUseCase: GetProfileUseCase ) : PreviewTypeProcessor { override suspend fun process(summary: ExecutionSummary, classification: DetailedManifestClass.PoolContribution): PreviewType { - val resources = getResourcesUseCase(addresses = summary.involvedResourceAddresses).getOrThrow() + val assets = resolveAssetsFromAddressUseCase( + fungibleAddresses = summary.involvedFungibleAddresses, + nonFungibleIds = summary.involvedNonFungibleIds + ).getOrThrow() val involvedPools = getPoolDetailsUseCase(classification.poolAddresses.map { it.addressString() }.toSet()).getOrThrow() - val defaultDepositGuarantees = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee + val defaultDepositGuarantee = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee val accountsWithdrawnFrom = summary.accountWithdraws.keys val ownedAccountsWithdrawnFrom = getProfileUseCase.accountsOnCurrentNetwork().filter { accountsWithdrawnFrom.contains(it.address) } - val from = summary.extractWithdraws(ownedAccountsWithdrawnFrom, resources) + val from = summary.extractWithdraws(ownedAccountsWithdrawnFrom, assets.map { it.resource }) val to = summary.accountDeposits.map { depositsPerAddress -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(depositsPerAddress.key) ?: error("No account found") - val deposits = depositsPerAddress.value.mapNotNull { deposit -> + val deposits = depositsPerAddress.value.map { deposit -> val resourceAddress = deposit.resourceAddress val contributions = classification.poolContributions.filter { it.poolUnitsResourceAddress.addressString() == resourceAddress } if (contributions.isEmpty()) { - null + resolveGeneralAsset(deposit, summary, assets, defaultDepositGuarantee) } else { val pool = involvedPools.find { it.address == contributions.first().poolAddress.addressString() } ?: error("No pool found") - val poolResource = resources.find { it.resourceAddress == pool.metadata.poolUnit() } as? Resource.FungibleResource + val poolResource = assets.find { + it.resource.resourceAddress == pool.metadata.poolUnit() + }?.resource as? Resource.FungibleResource ?: error("No pool resource found") val contributedResourceAddresses = contributions.first().contributedResources.keys - val guaranteeType = (deposit as? ResourceIndicator.Fungible)?.guaranteeType(defaultDepositGuarantees) - ?: GuaranteeType.Guaranteed - + val guaranteeType = deposit.guaranteeType(defaultDepositGuarantee) val poolUnitAmount = contributions.find { it.poolUnitsResourceAddress.addressString() == poolResource.resourceAddress }?.poolUnitsAmount?.asStr()?.toBigDecimalOrNull() @@ -83,6 +86,24 @@ class PoolContributionProcessor @Inject constructor( ) } + private fun resolveGeneralAsset( + deposit: ResourceIndicator, + summary: ExecutionSummary, + involvedAssets: List, + defaultDepositGuarantee: Double + ): Transferable.Depositing { + val asset = if (deposit.isNewlyCreated(summary = summary)) { + deposit.toNewlyCreatedTransferableAsset(deposit.newlyCreatedMetadata(summary = summary)) + } else { + deposit.toTransferableAsset(involvedAssets) + } + + return Transferable.Depositing( + transferable = asset, + guaranteeType = deposit.guaranteeType(defaultDepositGuarantee) + ) + } + private fun ExecutionSummary.extractWithdraws(allOwnedAccounts: List, resources: List) = accountWithdraws.entries.map { transferEntry -> val accountOnNetwork = allOwnedAccounts.find { it.address == transferEntry.key } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorClaimProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorClaimProcessor.kt index a310b46ef3..140a2ccf84 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorClaimProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorClaimProcessor.kt @@ -1,5 +1,6 @@ package com.babylon.wallet.android.presentation.transaction.analysis.processor +import com.babylon.wallet.android.domain.model.GuaranteeType import com.babylon.wallet.android.domain.model.Transferable import com.babylon.wallet.android.domain.model.TransferableAsset import com.babylon.wallet.android.domain.model.assets.StakeClaim @@ -13,11 +14,14 @@ import com.babylon.wallet.android.presentation.transaction.AccountWithTransferab import com.babylon.wallet.android.presentation.transaction.PreviewType import com.radixdlt.ret.DetailedManifestClass import com.radixdlt.ret.ExecutionSummary +import com.radixdlt.ret.ResourceIndicator import com.radixdlt.ret.nonFungibleLocalIdAsStr +import kotlinx.coroutines.flow.first import rdx.works.profile.derivation.model.NetworkId import rdx.works.profile.domain.GetProfileUseCase import rdx.works.profile.domain.accountOnCurrentNetwork import rdx.works.profile.domain.currentNetwork +import java.math.BigDecimal import javax.inject.Inject class ValidatorClaimProcessor @Inject constructor( @@ -36,7 +40,7 @@ class ValidatorClaimProcessor @Inject constructor( }.flatten() val toAccounts = extractDeposits(summary, getProfileUseCase, resources) - val fromAccounts = classification.extractWithdrawals( + val fromAccounts = extractWithdrawals( executionSummary = summary, getProfileUseCase = getProfileUseCase, resources = resources, @@ -51,7 +55,7 @@ class ValidatorClaimProcessor @Inject constructor( ) } - private suspend fun DetailedManifestClass.ValidatorClaim.extractWithdrawals( + private suspend fun extractWithdrawals( executionSummary: ExecutionSummary, getProfileUseCase: GetProfileUseCase, resources: List, @@ -60,39 +64,38 @@ class ValidatorClaimProcessor @Inject constructor( ): List { return executionSummary.accountWithdraws.map { claimsPerAddress -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(claimsPerAddress.key) ?: error("No account found") - val withdrawingNfts = claimsPerAddress.value.distinctBy { it.resourceAddress }.map { resourceClaim -> - val resourceAddress = resourceClaim.resourceAddress - val nftResource = - resources.find { it.resourceAddress == resourceAddress } as? Resource.NonFungibleResource - ?: error("No resource found") - val validatorClaim = validatorClaims.find { it.claimNftAddress.addressString() == resourceAddress } - ?: error("No validator claim found") - val validator = - involvedValidators.find { validatorClaim.validatorAddress.addressString() == it.address } ?: error("No validator found") - val items = validatorClaims.filter { it.claimNftAddress.addressString() == resourceAddress }.map { claim -> - claim.claimNftIds.map { localId -> - val localIdString = nonFungibleLocalIdAsStr(localId) - val claimAmount = stakeClaimsNfts.find { - resourceAddress == it.collectionAddress && localIdString == nonFungibleLocalIdAsStr(it.localId.toRetId()) - }?.claimAmountXrd - Resource.NonFungibleResource.Item( - collectionAddress = resourceAddress, - localId = Resource.NonFungibleResource.Item.ID.from(localId) - ) to (claimAmount ?: claim.xrdAmount.asStr().toBigDecimal()) + val withdrawingNfts = + claimsPerAddress.value.filterIsInstance().groupBy { it.resourceAddress.addressString() } + .map { resourceClaim -> + val resourceAddress = resourceClaim.key + val nftResource = + resources.find { it.resourceAddress == resourceAddress } as? Resource.NonFungibleResource + ?: error("No resource found") + val validatorAddress = nftResource.validatorAddress ?: error("No validator address") + val validator = involvedValidators.find { validatorAddress == it.address } ?: error("No validator found") + val items = resourceClaim.value.map { resourceIndicator -> + resourceIndicator.localIds.map { localId -> + val claimAmount = stakeClaimsNfts.find { + resourceAddress == it.collectionAddress && localId == nonFungibleLocalIdAsStr(it.localId.toRetId()) + }?.claimAmountXrd ?: BigDecimal.ZERO + Resource.NonFungibleResource.Item( + collectionAddress = resourceAddress, + localId = Resource.NonFungibleResource.Item.ID.from(localId) + ) to claimAmount + } + }.flatten() + Transferable.Withdrawing( + transferable = TransferableAsset.NonFungible.StakeClaimAssets( + claim = StakeClaim( + nonFungibleResource = nftResource.copy(items = items.map { it.first }), + validator = validator + ), + xrdWorthPerNftItem = items.associate { + it.first.localId.displayable to it.second + } + ) + ) } - }.flatten() - Transferable.Withdrawing( - transferable = TransferableAsset.NonFungible.StakeClaimAssets( - claim = StakeClaim( - nonFungibleResource = nftResource.copy(items = items.map { it.first }), - validator = validator - ), - xrdWorthPerNftItem = items.associate { - it.first.localId.displayable to it.second - } - ) - ) - } AccountWithTransferableResources.Owned( account = ownedAccount, resources = withdrawingNfts @@ -106,10 +109,12 @@ class ValidatorClaimProcessor @Inject constructor( resources: List ) = executionSummary.accountDeposits.map { entry -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(entry.key) ?: error("No account found") + val defaultDepositGuarantees = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee val xrdResource = resources.find { it.resourceAddress == XrdResource.address(NetworkId.from(ownedAccount.networkID)) } as? Resource.FungibleResource ?: error("No resource found") - + val guaranteeType = entry.value.firstOrNull()?.guaranteeType(defaultDepositGuarantees) + ?: GuaranteeType.Guaranteed val amount = entry.value.sumOf { it.amount } AccountWithTransferableResources.Owned( account = ownedAccount, @@ -119,7 +124,8 @@ class ValidatorClaimProcessor @Inject constructor( amount = amount, resource = xrdResource.copy(ownedAmount = amount), isNewlyCreated = false - ) + ), + guaranteeType = guaranteeType ) ) ) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorStakeProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorStakeProcessor.kt index 7cb8d38889..4097a77acf 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorStakeProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorStakeProcessor.kt @@ -12,6 +12,7 @@ import com.babylon.wallet.android.presentation.transaction.AccountWithTransferab import com.babylon.wallet.android.presentation.transaction.PreviewType import com.radixdlt.ret.DetailedManifestClass import com.radixdlt.ret.ExecutionSummary +import kotlinx.coroutines.flow.first import rdx.works.profile.derivation.model.NetworkId import rdx.works.profile.domain.GetProfileUseCase import rdx.works.profile.domain.accountOnCurrentNetwork @@ -51,6 +52,7 @@ class ValidatorStakeProcessor @Inject constructor( involvedValidators: List ) = executionSummary.accountDeposits.map { depositsPerAccount -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(depositsPerAccount.key) ?: error("No account found") + val defaultDepositGuarantees = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee val depositingLsu = depositsPerAccount.value.map { depositedResource -> val resourceAddress = depositedResource.resourceAddress val lsuResource = resources.find { @@ -60,12 +62,14 @@ class ValidatorStakeProcessor @Inject constructor( val validator = involvedValidators.find { it.address == stakes.first().validatorAddress.addressString() } ?: error("No validator found") val amount = stakes.sumOf { it.liquidStakeUnitAmount.asStr().toBigDecimal() } + val guaranteeType = depositedResource.guaranteeType(defaultDepositGuarantees) Transferable.Depositing( transferable = TransferableAsset.Fungible.LSUAsset( amount = amount, lsu = LiquidStakeUnit(lsuResource.copy(ownedAmount = amount), validator), xrdWorth = stakes.sumOf { it.xrdAmount.asStr().toBigDecimal() }, - ) + ), + guaranteeType = guaranteeType ) } AccountWithTransferableResources.Owned( diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorUnstakeProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorUnstakeProcessor.kt index 7361a91e27..c4a16563a1 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorUnstakeProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/ValidatorUnstakeProcessor.kt @@ -13,6 +13,7 @@ import com.babylon.wallet.android.presentation.transaction.AccountWithTransferab import com.babylon.wallet.android.presentation.transaction.PreviewType import com.radixdlt.ret.DetailedManifestClass import com.radixdlt.ret.ExecutionSummary +import kotlinx.coroutines.flow.first import rdx.works.profile.domain.GetProfileUseCase import rdx.works.profile.domain.accountOnCurrentNetwork import rdx.works.profile.domain.currentNetwork @@ -47,6 +48,7 @@ class ValidatorUnstakeProcessor @Inject constructor( involvedValidators: List ) = executionSummary.accountDeposits.map { claimsPerAddress -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(claimsPerAddress.key) ?: error("No account found") + val defaultDepositGuarantees = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee val depositingNfts = claimsPerAddress.value.map { claimedResource -> val resourceAddress = claimedResource.resourceAddress val nftResource = @@ -68,6 +70,7 @@ class ValidatorUnstakeProcessor @Inject constructor( localId = Resource.NonFungibleResource.Item.ID.from(localId) ) to xrdWorth } + val guaranteeType = claimedResource.guaranteeType(defaultDepositGuarantees) Transferable.Depositing( transferable = TransferableAsset.NonFungible.StakeClaimAssets( claim = StakeClaim( @@ -78,7 +81,8 @@ class ValidatorUnstakeProcessor @Inject constructor( it.first.localId.displayable to it.second }, isNewlyCreated = true - ) + ), + guaranteeType = guaranteeType ) } AccountWithTransferableResources.Owned( From 39077c4cf5fb891ce4d64b6a63dbada1510a419d Mon Sep 17 00:00:00 2001 From: Jakub Porzuczek Date: Tue, 30 Jan 2024 13:56:52 +0100 Subject: [PATCH 2/5] make use of new resolve assets use case in pool contribution processor --- .../analysis/processor/PoolContributionProcessor.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt index 6e976e3640..5113b56779 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt @@ -36,7 +36,7 @@ class PoolContributionProcessor @Inject constructor( val ownedAccountsWithdrawnFrom = getProfileUseCase.accountsOnCurrentNetwork().filter { accountsWithdrawnFrom.contains(it.address) } - val from = summary.extractWithdraws(ownedAccountsWithdrawnFrom, assets.map { it.resource }) + val from = summary.extractWithdraws(ownedAccountsWithdrawnFrom, assets) val to = summary.accountDeposits.map { depositsPerAddress -> val ownedAccount = getProfileUseCase.accountOnCurrentNetwork(depositsPerAddress.key) ?: error("No account found") val deposits = depositsPerAddress.value.map { deposit -> @@ -104,12 +104,12 @@ class PoolContributionProcessor @Inject constructor( ) } - private fun ExecutionSummary.extractWithdraws(allOwnedAccounts: List, resources: List) = + private fun ExecutionSummary.extractWithdraws(allOwnedAccounts: List, assets: List) = accountWithdraws.entries.map { transferEntry -> val accountOnNetwork = allOwnedAccounts.find { it.address == transferEntry.key } val withdrawing = transferEntry.value.map { resourceIndicator -> - Transferable.Withdrawing(resourceIndicator.toTransferableResource(resources)) + Transferable.Withdrawing(resourceIndicator.toTransferableAsset(assets)) } accountOnNetwork?.let { account -> AccountWithTransferableResources.Owned( From ff8df28b9370dd318707b7336e50f5e6a1d2feac Mon Sep 17 00:00:00 2001 From: Jakub Porzuczek Date: Tue, 30 Jan 2024 14:59:20 +0100 Subject: [PATCH 3/5] update tests --- .../presentation/transaction/TransactionReviewViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt index b305e05761..cb5b921c11 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt @@ -137,9 +137,9 @@ internal class TransactionReviewViewModelTest : StateViewModelTest Date: Tue, 30 Jan 2024 15:20:12 +0100 Subject: [PATCH 4/5] fix rebase compile error --- .../analysis/processor/PoolContributionProcessor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt index 5113b56779..c8b95819d7 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt @@ -27,8 +27,8 @@ class PoolContributionProcessor @Inject constructor( ) : PreviewTypeProcessor { override suspend fun process(summary: ExecutionSummary, classification: DetailedManifestClass.PoolContribution): PreviewType { val assets = resolveAssetsFromAddressUseCase( - fungibleAddresses = summary.involvedFungibleAddresses, - nonFungibleIds = summary.involvedNonFungibleIds + fungibleAddresses = summary.involvedFungibleAddresses(), + nonFungibleIds = summary.involvedNonFungibleIds() ).getOrThrow() val involvedPools = getPoolDetailsUseCase(classification.poolAddresses.map { it.addressString() }.toSet()).getOrThrow() val defaultDepositGuarantee = getProfileUseCase.invoke().first().appPreferences.transaction.defaultDepositGuarantee From a525dca785e15f3e658e3086010cdbe8054551ea Mon Sep 17 00:00:00 2001 From: Jakub Porzuczek Date: Tue, 30 Jan 2024 15:34:35 +0100 Subject: [PATCH 5/5] detekt --- .../transaction/analysis/processor/PoolContributionProcessor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt index c8b95819d7..f0e5c0ce81 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/analysis/processor/PoolContributionProcessor.kt @@ -25,6 +25,7 @@ class PoolContributionProcessor @Inject constructor( private val getPoolDetailsUseCase: GetPoolDetailsUseCase, private val getProfileUseCase: GetProfileUseCase ) : PreviewTypeProcessor { + @Suppress("LongMethod") override suspend fun process(summary: ExecutionSummary, classification: DetailedManifestClass.PoolContribution): PreviewType { val assets = resolveAssetsFromAddressUseCase( fungibleAddresses = summary.involvedFungibleAddresses(),