Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup staking proxy #935

Merged
merged 10 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
ERussel marked this conversation as resolved.
Show resolved Hide resolved
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
Loading