Skip to content

Commit

Permalink
Merge pull request #763 from radixdlt/feature/ABW-2750-2753-pool-stak…
Browse files Browse the repository at this point in the history
…es-review

Pools and stakes preview in general transaction and transfer
  • Loading branch information
micbakos-rdx authored Jan 25, 2024
2 parents 5bdf225 + 0d1fa1a commit 32c6584
Show file tree
Hide file tree
Showing 47 changed files with 1,523 additions and 630 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.babylon.wallet.android.data.gateway.generated.models.StateNonFungible
import com.babylon.wallet.android.data.gateway.generated.models.StateNonFungibleDetailsResponseItem
import com.babylon.wallet.android.data.gateway.model.ExplicitMetadataKey
import com.babylon.wallet.android.data.repository.toResult
import com.babylon.wallet.android.domain.model.resources.metadata.claimedEntities
import com.babylon.wallet.android.domain.model.resources.metadata.dAppDefinition
import com.babylon.wallet.android.domain.model.resources.metadata.poolUnit
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand Down Expand Up @@ -83,71 +85,145 @@ suspend fun StateApi.fetchAccountGatewayDetails(
items
}

@Suppress("LongMethod")
suspend fun StateApi.fetchPools(
poolAddresses: Set<String>,
stateVersion: Long
): List<PoolsResponse> {
if (poolAddresses.isEmpty()) return emptyList()
stateVersion: Long?
): PoolsResponse {
if (poolAddresses.isEmpty()) return PoolsResponse(emptyList(), stateVersion)

val poolUnitToPool = mutableMapOf<String, String>()
val poolToResources = mutableMapOf<String, List<FungibleResourcesCollectionItem>>()
val poolToDAppDefinition = mutableMapOf<String, String>()

val poolDetails = mutableMapOf<String, StateEntityDetailsResponseItem>()
val poolWithResources = mutableMapOf<String, List<FungibleResourcesCollectionItem>>()
val resourceToPoolComponentAssociation = mutableMapOf<String, String>()
val dApps = mutableMapOf<String, StateEntityDetailsResponseItem>()

var resolvedVersion = stateVersion
paginateDetails(
addresses = poolAddresses,
metadataKeys = ExplicitMetadataKey.forPools,
stateVersion = stateVersion,
) { poolComponents ->
poolComponents.items.forEach { pool ->
stateVersion = resolvedVersion,
) { page ->
page.items.forEach { pool ->
poolDetails[pool.address] = pool
val metadata = pool.explicitMetadata?.toMetadata().orEmpty()
val associatedResource = metadata.poolUnit().orEmpty()
resourceToPoolComponentAssociation[associatedResource] = pool.address
poolWithResources[pool.address] = pool.fungibleResources?.items.orEmpty()
val poolUnit = metadata.poolUnit().orEmpty()

// Associate Pool with Pool Unit
poolUnitToPool[poolUnit] = pool.address

// Associate Pool with resources
poolToResources[pool.address] = pool.fungibleResources?.items.orEmpty()

// Associate pool with dApp definition
val dAppDefinition = metadata.dAppDefinition()
if (dAppDefinition != null) {
poolToDAppDefinition[pool.address] = dAppDefinition
}
}
resolvedVersion = page.ledgerState.stateVersion
}

// Resolve associated dApps
val dAppDefinitionAddresses = poolToDAppDefinition.values.toSet()
if (dAppDefinitionAddresses.isNotEmpty()) {
paginateDetails(
addresses = dAppDefinitionAddresses,
metadataKeys = ExplicitMetadataKey.forDApps,
stateVersion = resolvedVersion
) { page ->
page.items.forEach { item ->
val dAppDefinition = item.address
val claimedEntities = item.explicitMetadata?.toMetadata().orEmpty().claimedEntities()
if (claimedEntities != null) {
val poolAddress = poolToDAppDefinition.entries.find { entry ->
entry.value == dAppDefinition
}?.key

if (poolAddress in claimedEntities) {
// Two way linking exists, store dApp information
dApps[dAppDefinition] = item
}
}
}
}
}

val result = mutableListOf<PoolsResponse>()
// Resolve Pool Units
val poolItems = mutableListOf<PoolsResponse.PoolItem>()
paginateDetails(
addresses = resourceToPoolComponentAssociation.keys, // Request details for resources
addresses = poolUnitToPool.keys,
metadataKeys = ExplicitMetadataKey.forAssets,
stateVersion = stateVersion
) { resourcesDetails ->
resourcesDetails.items.forEach { resourceDetails ->
val poolAddress = resourceToPoolComponentAssociation[resourceDetails.address]
stateVersion = resolvedVersion
) { page ->
page.items.forEach { poolUnitDetails ->
val poolAddress = poolUnitToPool[poolUnitDetails.address]
poolDetails[poolAddress]?.let { poolDetails ->
result.add(PoolsResponse(poolDetails, resourceDetails, poolWithResources[poolAddress].orEmpty()))
val dAppDefinition = poolToDAppDefinition[poolDetails.address]
val dAppDetails = if (dAppDefinition != null) {
dApps[dAppDefinition]
} else {
null
}
poolItems.add(
PoolsResponse.PoolItem(
poolDetails = poolDetails,
poolUnitDetails = poolUnitDetails,
associatedDAppDetails = dAppDetails,
poolResourcesDetails = poolToResources[poolAddress].orEmpty(),
)
)
}
}
}

return result
return PoolsResponse(
poolItems = poolItems,
stateVersion = resolvedVersion
)
}

data class PoolsResponse(
val poolDetails: StateEntityDetailsResponseItem,
val poolUnitDetails: StateEntityDetailsResponseItem,
val poolResourcesDetails: List<FungibleResourcesCollectionItem>
)
val poolItems: List<PoolItem>,
val stateVersion: Long?
) {
data class PoolItem(
val poolDetails: StateEntityDetailsResponseItem,
val poolUnitDetails: StateEntityDetailsResponseItem,
val associatedDAppDetails: StateEntityDetailsResponseItem?,
val poolResourcesDetails: List<FungibleResourcesCollectionItem>
)
}

suspend fun StateApi.fetchValidators(
validatorsAddresses: Set<String>,
stateVersion: Long
): List<StateEntityDetailsResponseItem> {
if (validatorsAddresses.isEmpty()) return emptyList()
stateVersion: Long?
): ValidatorsResponse {
if (validatorsAddresses.isEmpty()) return ValidatorsResponse(emptyList(), stateVersion)

val validators = mutableListOf<StateEntityDetailsResponseItem>()
var returnedStateVersion = stateVersion
paginateDetails(
addresses = validatorsAddresses,
metadataKeys = ExplicitMetadataKey.forValidators,
stateVersion = stateVersion,
) { poolsChunked ->
validators.addAll(poolsChunked.items)
returnedStateVersion = poolsChunked.ledgerState.stateVersion
}

return validators
return ValidatorsResponse(
validators = validators,
stateVersion = returnedStateVersion
)
}

data class ValidatorsResponse(
val validators: List<StateEntityDetailsResponseItem>,
val stateVersion: Long? = null
)

suspend fun StateApi.fetchVaultDetails(vaultAddresses: Set<String>): Map<String, BigDecimal> {
val vaultAmount = mutableMapOf<String, BigDecimal>()
paginateDetails(vaultAddresses) { page ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ enum class ExplicitMetadataKey(val key: String) {
val forPools: Set<ExplicitMetadataKey> = setOf(
NAME,
ICON_URL,
POOL_UNIT
POOL_UNIT,
DAPP_DEFINITION
)

val forValidators: Set<ExplicitMetadataKey> = setOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.babylon.wallet.android.data.repository.cache.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.babylon.wallet.android.data.gateway.extensions.toMetadata
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityDetailsResponseItem
import com.babylon.wallet.android.domain.model.DApp
import java.time.Instant

Expand All @@ -19,4 +21,12 @@ data class DAppEntity(
dAppAddress = definitionAddress,
metadata = metadata?.metadata.orEmpty()
)

companion object {
fun from(item: StateEntityDetailsResponseItem, syncedAt: Instant) = DAppEntity(
definitionAddress = item.address,
metadata = item.explicitMetadata?.toMetadata()?.let { MetadataColumn(it) },
synced = syncedAt
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,28 @@ data class PoolResourceJoin(
) to asEntity(syncInfo.synced)
}
}

@Entity(
primaryKeys = ["pool_address", "dApp_definition_address"],
foreignKeys = [
ForeignKey(
entity = PoolEntity::class,
parentColumns = ["address"],
childColumns = ["pool_address"]
),
ForeignKey(
entity = DAppEntity::class,
parentColumns = ["definition_address"],
childColumns = ["dApp_definition_address"]
)
],
indices = [
Index("dApp_definition_address")
]
)
data class PoolDAppJoin(
@ColumnInfo("pool_address")
val poolAddress: String,
@ColumnInfo("dApp_definition_address")
val dAppDefinitionAddress: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,23 @@ data class PoolEntity(

companion object {
@Suppress("UnsafeCallOnNullableType")
fun List<PoolsResponse>.asPoolsResourcesJoin(
fun List<PoolsResponse.PoolItem>.asPoolsResourcesJoin(
syncInfo: SyncInfo
): List<PoolWithResourcesJoinResult> =
mapNotNull { fetchedPoolDetails ->
val poolResourceEntity = fetchedPoolDetails.poolUnitDetails.asEntity(syncInfo.synced)

val resourcesInPool = fetchedPoolDetails.poolResourcesDetails.map { fungibleItem ->
fungibleItem.asPoolResourceJoin(poolAddress = poolResourceEntity.poolAddress!!, syncInfo)
}
val poolEntity = fetchedPoolDetails.poolDetails.asPoolEntity()
val associatedDAppEntity = fetchedPoolDetails.associatedDAppDetails?.let { DAppEntity.from(it, syncInfo.synced) }
poolEntity?.let {
PoolWithResourcesJoinResult(poolEntity, poolResourceEntity, resourcesInPool)
PoolWithResourcesJoinResult(
pool = poolEntity,
poolUnitResource = poolResourceEntity,
associatedDApp = associatedDAppEntity,
resources = resourcesInPool
)
}
}
}
Expand All @@ -64,5 +69,6 @@ fun StateEntityDetailsResponseItem.asPoolEntity(): PoolEntity? {
data class PoolWithResourcesJoinResult(
val pool: PoolEntity,
val poolUnitResource: ResourceEntity,
val associatedDApp: DAppEntity?,
val resources: List<Pair<PoolResourceJoin, ResourceEntity>>
)
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ interface StateDao {

@Query(
"""
SELECT MAX(state_version) FROM AccountEntity
SELECT address, state_version FROM AccountEntity
"""
)
fun getLatestStateVersion(): Long?
fun getAccountStateVersions(): List<AccountStateVersion>

@Suppress("UnsafeCallOnNullableType")
@Transaction
Expand All @@ -60,8 +60,16 @@ interface StateDao {
listOf(pool.poolUnitResource) + pool.resources.map { it.second }
}.flatten()
insertOrReplaceResources(resourcesInvolved)
val join = pools.map { poolResource -> poolResource.resources.map { it.first } }.flatten()
insertPoolResources(join)
val poolResourcesJoin = pools.map { poolResource -> poolResource.resources.map { it.first } }.flatten()
insertPoolResources(poolResourcesJoin)

insertDApps(pools.mapNotNull { it.associatedDApp })
insertPoolDApp(
pools.mapNotNull { join ->
val dAppAddress = join.associatedDApp?.definitionAddress ?: return@mapNotNull null
PoolDAppJoin(join.pool.address, dAppDefinitionAddress = dAppAddress)
}
)
}

@Transaction
Expand Down Expand Up @@ -120,6 +128,9 @@ interface StateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPoolResources(poolResources: List<PoolResourceJoin>)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPoolDApp(poolDApps: List<PoolDAppJoin>)

@Query(
"""
SELECT
Expand All @@ -137,6 +148,15 @@ interface StateDao {
)
fun getPoolDetails(addresses: Set<String>, atStateVersion: Long): List<PoolWithResourceResponse>

@Query(
"""
SELECT * FROM DAppEntity
INNER JOIN PoolDAppJoin ON PoolDAppJoin.dApp_definition_address = DAppEntity.definition_address
WHERE PoolDAppJoin.pool_address = :poolAddress
"""
)
fun getPoolAssociatedDApp(poolAddress: String): DAppEntity?

@Query(
"""
SELECT RE.* FROM ResourceEntity AS RE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ fun StateDao.getCachedPools(poolAddresses: Set<String>, atStateVersion: Long): M
// If pool's resource is not up to date or has no details, all pool info is considered stale
val poolResource = getPoolResource(join.address, resourcesCacheValidity()) ?: return@forEach

val associatedDApp = getPoolAssociatedDApp(join.address)?.toDApp()

val resource = if (join.resource != null && join.amount != null) {
join.resource.toResource(join.amount) as Resource.FungibleResource
} else {
Expand All @@ -26,14 +28,16 @@ fun StateDao.getCachedPools(poolAddresses: Set<String>, atStateVersion: Long): M
// TODO maybe add check if pool resource is up to date with details
val pool = pools[poolResource.poolAddress]
pools[poolResource.poolAddress!!] = pool?.copy(
resources = pool.resources.toMutableList().apply { add(resource) }
resources = pool.resources.toMutableList().apply { add(resource) },
associatedDApp = associatedDApp
) ?: Pool(
address = join.address,
resources = listOf(resource),
metadata = join.poolMetadata?.metadata.orEmpty(),
associatedDApp = associatedDApp
)
}
return pools
return pools.mapValues { it.value.copy(resources = it.value.resources.sorted()) }
}

fun StateDao.getCachedValidators(addresses: Set<String>, atStateVersion: Long): Map<String, ValidatorDetail> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import androidx.room.TypeConverters
PoolEntity::class,
ValidatorEntity::class,
PoolResourceJoin::class,
DAppEntity::class
DAppEntity::class,
PoolDAppJoin::class
],
version = StateDatabase.VERSION_4
version = StateDatabase.VERSION_5
)
@TypeConverters(StateDatabaseConverters::class)
abstract class StateDatabase : RoomDatabase() {
Expand All @@ -35,9 +36,12 @@ abstract class StateDatabase : RoomDatabase() {
@Deprecated("Add DAppEntity to schema")
const val VERSION_3 = 3

// Add PoolEntity.metadata to schema
@Deprecated("Add PoolEntity.metadata to schema")
const val VERSION_4 = 4

// Add PoolDAppJoin to schema
const val VERSION_5 = 5

private const val NAME = "STATE_DATABASE"

fun factory(applicationContext: Context): StateDatabase = Room
Expand Down
Loading

0 comments on commit 32c6584

Please sign in to comment.