Skip to content

Commit

Permalink
Merge pull request #935 from novasamatech/feature/setup-staking-proxy
Browse files Browse the repository at this point in the history
Setup staking proxy
  • Loading branch information
ERussel authored Jan 23, 2024
2 parents 6f75a36 + a3582a6 commit ae63f45
Show file tree
Hide file tree
Showing 49 changed files with 854 additions and 333 deletions.
52 changes: 48 additions & 4 deletions novawallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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
12 changes: 11 additions & 1 deletion novawallet/Common/Services/Proxy/ProxyModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,21 @@ struct AccountIdKey: JSONListConvertible, Hashable {
}
}

struct ProxyDefinition: Decodable {
struct ProxyDefinition: Decodable, Equatable {
let definition: [Proxy.ProxyDefinition]

init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
definition = try container.decode([Proxy.ProxyDefinition].self)
}

init(definition: [Proxy.ProxyDefinition]) {
self.definition = definition
}
}

enum ProxyFilter {
static func filteredStakingProxy(from proxy: ProxyDefinition) -> ProxyDefinition {
ProxyDefinition(definition: proxy.definition.filter { $0.proxyType.allowStaking })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class StakingAccountSubscription: WebSocketSubscribing {
let childSubscriptionFactory: ChildSubscriptionFactoryProtocol
let operationQueue: OperationQueue
let logger: LoggerProtocol?

let chainHasProxy: Bool
private let mutex = NSLock()

private var subscription: Subscription?
Expand All @@ -48,6 +48,7 @@ final class StakingAccountSubscription: WebSocketSubscribing {
accountId: AccountId,
chainId: ChainModel.Id,
chainFormat: ChainFormat,
chainHasProxy: Bool,
chainRegistry: ChainRegistryProtocol,
provider: StreamableProvider<StashItem>,
childSubscriptionFactory: ChildSubscriptionFactoryProtocol,
Expand All @@ -57,6 +58,7 @@ final class StakingAccountSubscription: WebSocketSubscribing {
self.accountId = accountId
self.chainId = chainId
self.chainFormat = chainFormat
self.chainHasProxy = chainHasProxy
self.chainRegistry = chainRegistry
self.provider = provider
self.childSubscriptionFactory = childSubscriptionFactory
Expand Down Expand Up @@ -130,6 +132,10 @@ final class StakingAccountSubscription: WebSocketSubscribing {
)
}

if chainHasProxy {
requests.append(.init(storagePath: Proxy.proxyList, accountId: stashId))
}

return requests
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ protocol StakingAccountUpdatingServiceProtocol {
func setupSubscription(
for accountId: AccountId,
chainId: ChainModel.Id,
chainFormat: ChainFormat
chainFormat: ChainFormat,
chainHasProxy: Bool
) throws

func clearSubscription()
Expand Down Expand Up @@ -40,7 +41,8 @@ class StakingAccountUpdatingService: StakingAccountUpdatingServiceProtocol {
func setupSubscription(
for accountId: AccountId,
chainId: ChainModel.Id,
chainFormat: ChainFormat
chainFormat: ChainFormat,
chainHasProxy: Bool
) throws {
let address = try accountId.toAddress(using: chainFormat)
let stashItemProvider = substrateDataProviderFactory.createStashItemProvider(for: address, chainId: chainId)
Expand All @@ -49,6 +51,7 @@ class StakingAccountUpdatingService: StakingAccountUpdatingServiceProtocol {
accountId: accountId,
chainId: chainId,
chainFormat: chainFormat,
chainHasProxy: chainHasProxy,
chainRegistry: chainRegistry,
provider: stashItemProvider,
childSubscriptionFactory: childSubscriptionFactory,
Expand Down
17 changes: 16 additions & 1 deletion novawallet/Common/Substrate/Types/Proxy/Proxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import BigInt
enum Proxy {
static var name: String { "Proxy" }

struct ProxyDefinition: Decodable {
struct ProxyDefinition: Decodable, Equatable {
enum CodingKeys: String, CodingKey {
case proxy = "delegate"
case proxyType
Expand Down Expand Up @@ -91,5 +91,20 @@ enum Proxy {

try container.encode(JSON.null)
}

var allowStaking: Bool {
switch self {
case .any, .nonTransfer, .staking:
return true
case .governance,
.staking,
.nominationPools,
.identityJudgement,
.cancelProxy,
.auction,
.other:
return false
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import UIKit
import SoraUI
import Kingfisher

final class SwapNetworkFeeView: GenericTitleValueView<RoundedButton, GenericPairValueView<RoundedButton, UILabel>>,
final class NetworkFeeInfoView: GenericTitleValueView<RoundedButton, GenericPairValueView<RoundedButton, UILabel>>,
SkeletonableView {
var titleButton: RoundedButton { titleView }
var valueTopButton: RoundedButton { valueView.fView }
Expand Down Expand Up @@ -60,16 +60,16 @@ final class SwapNetworkFeeView: GenericTitleValueView<RoundedButton, GenericPair
}
}

extension SwapNetworkFeeView {
func bind(viewModel: SwapFeeViewModel) {
extension NetworkFeeInfoView {
func bind(viewModel: NetworkFeeInfoViewModel) {
valueTopButton.imageWithTitleView?.iconImage = viewModel.isEditable ? iconPencil : nil
valueTopButton.isUserInteractionEnabled = viewModel.isEditable
valueTopButton.imageWithTitleView?.title = viewModel.balanceViewModel.amount
valueBottomLabel.text = viewModel.balanceViewModel.price
valueTopButton.invalidateLayout()
}

func bind(loadableViewModel: LoadableViewModelState<SwapFeeViewModel>) {
func bind(loadableViewModel: LoadableViewModelState<NetworkFeeInfoViewModel>) {
switch loadableViewModel {
case let .cached(value), let .loaded(value):
isLoading = false
Expand All @@ -82,7 +82,7 @@ extension SwapNetworkFeeView {
}
}

extension SwapNetworkFeeView {
extension NetworkFeeInfoView {
func createSkeletons(for spaceSize: CGSize) -> [Skeletonable] {
let size = CGSize(width: 68, height: 8)
let offset = CGPoint(
Expand Down
4 changes: 4 additions & 0 deletions novawallet/Common/ViewModel/NetworkFeeInfoViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct NetworkFeeInfoViewModel {
var isEditable: Bool
var balanceViewModel: BalanceViewModelProtocol
}
Loading

0 comments on commit ae63f45

Please sign in to comment.