Skip to content

Commit

Permalink
Merge pull request #971 from novasamatech/develop
Browse files Browse the repository at this point in the history
7.8.0 Add staking proxy support
  • Loading branch information
ERussel authored Feb 12, 2024
2 parents 9223284 + 4a2c152 commit fb090be
Show file tree
Hide file tree
Showing 109 changed files with 5,176 additions and 625 deletions.
246 changes: 226 additions & 20 deletions novawallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions novawallet/Assets.xcassets/iconDelete.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "delete.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ protocol StakingLocalSubscriptionFactoryProtocol {
for accountId: AccountId,
chainId: ChainModel.Id
) throws -> AnyDataProvider<DecodedBagListNode>

func getProxyListProvider(
for accountId: AccountId,
chainId: ChainModel.Id
) throws -> AnyDataProvider<DecodedProxyDefinition>
}

final class StakingLocalSubscriptionFactory: SubstrateLocalSubscriptionFactory,
Expand Down Expand Up @@ -355,4 +360,25 @@ final class StakingLocalSubscriptionFactory: SubstrateLocalSubscriptionFactory,

return provider
}

func getProxyListProvider(
for accountId: AccountId,
chainId: ChainModel.Id
) throws -> AnyDataProvider<DecodedProxyDefinition> {
clearIfNeeded()

let codingPath = Proxy.proxyList
let localKey = try LocalStorageKeyFactory().createFromStoragePath(
codingPath,
accountId: accountId,
chainId: chainId
)

return try getDataProvider(
for: localKey,
chainId: chainId,
storageCodingPath: codingPath,
shouldUseFallback: false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ typealias DecodedBondedPool = ChainStorageDecodedItem<NominationPools.BondedPool
typealias DecodedRewardPool = ChainStorageDecodedItem<NominationPools.RewardPool>
typealias DecodedSubPools = ChainStorageDecodedItem<NominationPools.SubPools>
typealias DecodedPoolId = ChainStorageDecodedItem<StringScaleMapper<NominationPools.PoolId>>
typealias DecodedProxyDefinition = ChainStorageDecodedItem<ProxyDefinition>
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ import RobinHood

protocol ProxyListLocalSubscriptionHandler {
func handleAllProxies(result: Result<[DataProviderChange<ProxyAccountModel>], Error>)
func handleProxies(
result: Result<ProxyDefinition?, Error>,
accountId: AccountId,
chainId: ChainModel.Id
)
}

extension ProxyListLocalSubscriptionHandler {
func handleAllProxies(result _: Result<[DataProviderChange<ProxyAccountModel>], Error>) {}
func handleProxies(
result _: Result<ProxyDefinition?, Error>,
accountId _: AccountId,
chainId _: ChainModel.Id
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@ import RobinHood

protocol ProxyListLocalSubscriptionFactoryProtocol {
func getProxyListProvider() throws -> StreamableProvider<ProxyAccountModel>
func getProxyListProvider(
for accountId: AccountId,
chainId: ChainModel.Id
) throws -> AnyDataProvider<DecodedProxyDefinition>
}

final class ProxyListLocalSubscriptionFactory: BaseLocalSubscriptionFactory {
static let shared = ProxyListLocalSubscriptionFactory(
chainRegistry: ChainRegistryFacade.sharedRegistry,
streamableProviderFactory: SubstrateDataProviderFactory(
facade: SubstrateDataStorageFacade.shared,
operationManager: OperationManagerFacade.sharedManager,
logger: Logger.shared
),
storageFacade: UserDataStorageFacade.shared,
operationManager: OperationManager(operationQueue: OperationManagerFacade.sharedDefaultQueue),
operationManager: OperationManagerFacade.sharedManager,
logger: Logger.shared
)

let chainRegistry: ChainRegistryProtocol
let streamableProviderFactory: SubstrateDataProviderFactoryProtocol
let storageFacade: StorageFacadeProtocol
let operationManager: OperationManagerProtocol
let logger: LoggerProtocol

init(
chainRegistry: ChainRegistryProtocol,
streamableProviderFactory: SubstrateDataProviderFactoryProtocol,
storageFacade: StorageFacadeProtocol,
operationManager: OperationManagerProtocol,
logger: LoggerProtocol
) {
self.chainRegistry = chainRegistry
self.streamableProviderFactory = streamableProviderFactory
self.storageFacade = storageFacade
self.operationManager = operationManager
self.logger = logger
Expand Down Expand Up @@ -64,4 +80,54 @@ extension ProxyListLocalSubscriptionFactory: ProxyListLocalSubscriptionFactoryPr

return provider
}

func getProxyListProvider(
for accountId: AccountId,
chainId: ChainModel.Id
) throws -> AnyDataProvider<DecodedProxyDefinition> {
clearIfNeeded()

let codingPath = Proxy.proxyList
let localKey = try LocalStorageKeyFactory().createFromStoragePath(
codingPath,
accountId: accountId,
chainId: chainId
)

if let dataProvider = getProvider(for: localKey) as? DataProvider<DecodedProxyDefinition> {
return AnyDataProvider(dataProvider)
}

guard let runtimeCodingProvider = chainRegistry.getRuntimeProvider(for: chainId) else {
throw ChainRegistryError.runtimeMetadaUnavailable
}

let repository = InMemoryDataProviderRepository<DecodedProxyDefinition>()

let streamableProvider = streamableProviderFactory.createStorageProvider(for: localKey)
let fallback = StorageProviderSourceFallback<ProxyDefinition>.init(
usesRuntimeFallback: false,
missingEntryStrategy: .defaultValue(nil)
)
let trigger = DataProviderProxyTrigger()
let source: StorageProviderSource<ProxyDefinition> = StorageProviderSource(
itemIdentifier: localKey,
possibleCodingPaths: [codingPath],
runtimeService: runtimeCodingProvider,
provider: streamableProvider,
trigger: trigger,
fallback: fallback,
operationManager: operationManager
)

let dataProvider = DataProvider(
source: AnyDataProviderSource(source),
repository: AnyDataProviderRepository(repository),
updateTrigger: trigger
)

saveProvider(dataProvider, for: localKey)

return AnyDataProvider(dataProvider)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ protocol ProxyListLocalStorageSubscriber where Self: AnyObject {
var proxyListLocalSubscriptionHandler: ProxyListLocalSubscriptionHandler { get }

func subscribeAllProxies() -> StreamableProvider<ProxyAccountModel>?

func subscribeProxies(
for accountId: AccountId,
chainId: ChainModel.Id,
modifyInternalList: @escaping (ProxyDefinition) -> ProxyDefinition
) -> AnyDataProvider<DecodedProxyDefinition>?
}

extension ProxyListLocalStorageSubscriber {
Expand Down Expand Up @@ -42,6 +48,54 @@ extension ProxyListLocalStorageSubscriber {

return provider
}

func subscribeProxies(
for accountId: AccountId,
chainId: ChainModel.Id,
modifyInternalList: @escaping (ProxyDefinition) -> ProxyDefinition
) -> AnyDataProvider<DecodedProxyDefinition>? {
guard let provider = try? proxyListLocalSubscriptionFactory.getProxyListProvider(
for: accountId,
chainId: chainId
) else {
return nil
}

let updateClosure = { [weak self] (changes: [DataProviderChange<DecodedProxyDefinition>]) in
let proxies = changes.reduceToLastChange()?.item.map { modifyInternalList($0) } ?? nil

self?.proxyListLocalSubscriptionHandler.handleProxies(
result: .success(proxies),
accountId: accountId,
chainId: chainId
)
return
}

let failureClosure = { [weak self] (error: Error) in
self?.proxyListLocalSubscriptionHandler.handleProxies(
result: .failure(error),
accountId: accountId,
chainId: chainId
)
return
}

let options = DataProviderObserverOptions(
alwaysNotifyOnRefresh: false,
waitsInProgressSyncOnAdd: false
)

provider.addObserver(
self,
deliverOn: .main,
executing: updateClosure,
failing: failureClosure,
options: options
)

return provider
}
}

extension ProxyListLocalStorageSubscriber where Self: ProxyListLocalSubscriptionHandler {
Expand Down
63 changes: 41 additions & 22 deletions novawallet/Common/Helpers/AssetBalanceFormatterFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ protocol AssetBalanceFormatterFactoryProtocol {

func createAssetPriceFormatter(for info: AssetBalanceDisplayInfo) -> LocalizableResource<TokenFormatter>

func createTotalPriceFormatter(for info: AssetBalanceDisplayInfo) -> LocalizableResource<TokenFormatter>

func createInputTokenFormatter(
for info: AssetBalanceDisplayInfo
) -> LocalizableResource<TokenFormatter>
Expand All @@ -40,12 +42,14 @@ class AssetBalanceFormatterFactory {
private func createTokenFormatterCommon(
for info: AssetBalanceDisplayInfo,
roundingMode: NumberFormatter.RoundingMode,
preferredPrecisionOffset: UInt8 = 0
preferredPrecisionOffset: UInt8 = 0,
usesSuffixForBigNumbers: Bool = true
) -> LocalizableResource<TokenFormatter> {
let formatter = createCompoundFormatter(
for: info.displayPrecision,
roundingMode: roundingMode,
prefferedPrecisionOffset: preferredPrecisionOffset
prefferedPrecisionOffset: preferredPrecisionOffset,
usesSuffixForBigNumber: usesSuffixForBigNumbers
)

let tokenFormatter = TokenFormatter(
Expand All @@ -65,9 +69,10 @@ class AssetBalanceFormatterFactory {
private func createCompoundFormatter(
for preferredPrecision: UInt16,
roundingMode: NumberFormatter.RoundingMode = .down,
prefferedPrecisionOffset: UInt8 = 0
prefferedPrecisionOffset: UInt8 = 0,
usesSuffixForBigNumber: Bool = true
) -> LocalizableDecimalFormatting {
let abbreviations: [BigNumberAbbreviation] = [
var abbreviations: [BigNumberAbbreviation] = [
BigNumberAbbreviation(
threshold: 0,
divisor: 1.0,
Expand All @@ -93,27 +98,32 @@ class AssetBalanceFormatterFactory {
divisor: 1.0,
suffix: "",
formatter: nil
),
BigNumberAbbreviation(
threshold: 1_000_000,
divisor: 1_000_000.0,
suffix: "M",
formatter: nil
),
BigNumberAbbreviation(
threshold: 1_000_000_000,
divisor: 1_000_000_000.0,
suffix: "B",
formatter: nil
),
BigNumberAbbreviation(
threshold: 1_000_000_000_000,
divisor: 1_000_000_000_000.0,
suffix: "T",
formatter: nil
)
]

if usesSuffixForBigNumber {
abbreviations.append(contentsOf: [
BigNumberAbbreviation(
threshold: 1_000_000,
divisor: 1_000_000.0,
suffix: "M",
formatter: nil
),
BigNumberAbbreviation(
threshold: 1_000_000_000,
divisor: 1_000_000_000.0,
suffix: "B",
formatter: nil
),
BigNumberAbbreviation(
threshold: 1_000_000_000_000,
divisor: 1_000_000_000_000.0,
suffix: "T",
formatter: nil
)
])
}

return BigNumberFormatter(
abbreviations: abbreviations,
precision: 2,
Expand Down Expand Up @@ -154,6 +164,15 @@ extension AssetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol {
createTokenFormatterCommon(for: info, roundingMode: .down, preferredPrecisionOffset: 2)
}

func createTotalPriceFormatter(for info: AssetBalanceDisplayInfo) -> LocalizableResource<TokenFormatter> {
createTokenFormatterCommon(
for: info,
roundingMode: .down,
preferredPrecisionOffset: 2,
usesSuffixForBigNumbers: false
)
}

func createInputTokenFormatter(
for info: AssetBalanceDisplayInfo
) -> LocalizableResource<TokenFormatter> {
Expand Down
5 changes: 5 additions & 0 deletions novawallet/Common/Helpers/ChainAccountFetching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ extension MetaAccountModel {

return chainAccount.proxy
}

func address(for chainAsset: ChainAsset) throws -> AccountAddress? {
let request = chainAsset.chain.accountRequest()
return try fetch(for: request)?.toAddress()
}
}

extension ChainModel {
Expand Down
9 changes: 9 additions & 0 deletions novawallet/Common/Model/AssetBalance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ struct AssetBalance: Equatable {
)
}

func regularTransferrableBalance() -> BigUInt {
Self.transferrableBalance(
from: freeInPlank,
frozen: frozenInPlank,
reserved: reservedInPlank,
mode: .regular
)
}

static func transferrableBalance(
from free: BigUInt,
frozen: BigUInt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ enum SubqueryMultistaking {
}

struct StatsResponse: Decodable {
let activeStakers: SubqueryNodes<ActiveStaker>
let activeStakers: SubqueryNodes<ActiveStaker>?
let stakingApies: SubqueryNodes<Apy>
let rewards: SubqueryAggregates<AccumulatedReward>
let slashes: SubqueryAggregates<AccumulatedReward>
let rewards: SubqueryAggregates<AccumulatedReward>?
let slashes: SubqueryAggregates<AccumulatedReward>?
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ extension SubqueryMultistakingOperationFactory: MultistakingOffchainOperationFac
do {
let query = try buildQuery(for: request)
let operation = createOperation(for: query) { (result: SubqueryMultistaking.StatsResponse) in
let activeStakers = result.activeStakers.groupByNetworkAccountStaking()
let rewards = result.rewards.groupByNetworkStaking()
let slashes = result.slashes.groupByNetworkStaking()
let activeStakers = result.activeStakers?.groupByNetworkAccountStaking() ?? [:]
let rewards = result.rewards?.groupByNetworkStaking() ?? [:]
let slashes = result.slashes?.groupByNetworkStaking() ?? [:]

let stateFilterByNetworkStaking = request.stateFilters.groupByNetworkStaking()

Expand Down
Loading

0 comments on commit fb090be

Please sign in to comment.