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

Deposit instruction based on deposit rules #877

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
12 changes: 12 additions & 0 deletions RadixWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
832880692AE664C30014FBF3 /* EntityFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832880682AE664C30014FBF3 /* EntityFlag.swift */; };
8328806C2AE6725F0014FBF3 /* AccountPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8328806B2AE6725F0014FBF3 /* AccountPreferencesTests.swift */; };
83D0B5692AE01EDE0048DCBE /* RadixEngineToolkitError+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D0B5682AE01EDE0048DCBE /* RadixEngineToolkitError+Description.swift */; };
83EE47502AF027DF00155F03 /* AssetTransferDepositRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EE474F2AF027DF00155F03 /* AssetTransferDepositRuleTests.swift */; };
E62BB7632AEA856300D46DAC /* ImportMnemonicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E62BB7622AEA856300D46DAC /* ImportMnemonicTests.swift */; };
E6390FB32AE6C3E200B4DEE2 /* ProfileStore+OverlayWindowClient+Alert+OwnershipConflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6390FB22AE6C3E200B4DEE2 /* ProfileStore+OverlayWindowClient+Alert+OwnershipConflict.swift */; };
E63D123D2ADD1FC00001CBB1 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = E63D123C2ADD1FC00001CBB1 /* SwiftUIIntrospect */; };
Expand Down Expand Up @@ -2401,6 +2402,7 @@
832880682AE664C30014FBF3 /* EntityFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityFlag.swift; sourceTree = "<group>"; };
8328806B2AE6725F0014FBF3 /* AccountPreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPreferencesTests.swift; sourceTree = "<group>"; };
83D0B5682AE01EDE0048DCBE /* RadixEngineToolkitError+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RadixEngineToolkitError+Description.swift"; sourceTree = "<group>"; };
83EE474F2AF027DF00155F03 /* AssetTransferDepositRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetTransferDepositRuleTests.swift; sourceTree = "<group>"; };
E62BB7622AEA856300D46DAC /* ImportMnemonicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportMnemonicTests.swift; sourceTree = "<group>"; };
E6390FB22AE6C3E200B4DEE2 /* ProfileStore+OverlayWindowClient+Alert+OwnershipConflict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileStore+OverlayWindowClient+Alert+OwnershipConflict.swift"; sourceTree = "<group>"; };
E67F14442AE029BA00738BE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6667,6 +6669,14 @@
path = AccountPreferencesTests;
sourceTree = "<group>";
};
83EE474E2AF027CE00155F03 /* AssetTransferTests */ = {
isa = PBXGroup;
children = (
83EE474F2AF027DF00155F03 /* AssetTransferDepositRuleTests.swift */,
);
path = AssetTransferTests;
sourceTree = "<group>";
};
E63D123B2ADD1FC00001CBB1 /* Frameworks */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -6766,6 +6776,7 @@
E6DBA2732ADEBFB200A38425 /* Features */ = {
isa = PBXGroup;
children = (
83EE474E2AF027CE00155F03 /* AssetTransferTests */,
830EA9DE2AEB8201004C8051 /* AccountAndPersonaHidingTests */,
8328806A2AE6724B0014FBF3 /* AccountPreferencesTests */,
E6DBA2782ADEBFB200A38425 /* SettingsFeatureTests */,
Expand Down Expand Up @@ -7507,6 +7518,7 @@
E6DBA3732ADEBFB300A38425 /* ROLAClientTests.swift in Sources */,
E6DBA31F2ADEBFB300A38425 /* RadixConnectCustomData.swift in Sources */,
E6DBA34B2ADEBFB300A38425 /* SLIP10TestHelpers.swift in Sources */,
83EE47502AF027DF00155F03 /* AssetTransferDepositRuleTests.swift in Sources */,
E6DBA3462ADEBFB300A38425 /* DataToHexStringTests.swift in Sources */,
E6DBA38B2ADEC0CB00A38425 /* SLIP10TestVectorsHelper.swift in Sources */,
E6DBA3572ADEBFB300A38425 /* SLIP10InterfaceTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ extension OnLedgerEntitiesClient {
)

let filteredFungibleResources = fungibleResources.filter { resource in
!poolUnitResources.fungibleResourceAddresses.contains(resource.resourceAddress.address)
!poolUnitResources.fungibleResourceAddresses.contains(resource.resourceAddress)
}

let filteredNonFungibleResources = nonFungibleResources.filter { resource in
!poolUnitResources.nonFungibleResourceAddresses.contains(resource.resourceAddress.address)
!poolUnitResources.nonFungibleResourceAddresses.contains(resource.resourceAddress)
}

return await .init(
Expand All @@ -70,7 +70,7 @@ extension OnLedgerEntitiesClient {
metadata: .init(item.explicitMetadata),
fungibleResources: filteredFungibleResources.sorted(),
nonFungibleResources: filteredNonFungibleResources.sorted(),
poolUnitResources: poolUnitResources.nonEmpty
poolUnitResources: poolUnitResources
)
}

Expand Down Expand Up @@ -304,10 +304,6 @@ extension OnLedgerEntitiesClient {
}

let amount = try RETDecimal(value: vault.amount)
guard amount > 0 else {
return nil
}

return try .init(
resourceAddress: .init(validatingAddress: vaultAggregated.resourceAddress),
atLedgerState: ledgerState,
Expand All @@ -327,10 +323,6 @@ extension OnLedgerEntitiesClient {
return nil
}

guard vault.totalCount > 0 else {
return nil
}

return try .init(
resourceAddress: .init(validatingAddress: vaultAggregated.resourceAddress),
atLedgerState: ledgerState,
Expand Down Expand Up @@ -472,7 +464,7 @@ extension OnLedgerEntitiesClient {
}

extension OnLedgerEntity.Account.PoolUnitResources {
var nonEmpty: OnLedgerEntity.Account.PoolUnitResources {
var nonEmptyVaults: OnLedgerEntity.Account.PoolUnitResources {
let stakes = radixNetworkStakes.compactMap { stake in
let stakeUnitResource: OnLedgerEntity.OwnedFungibleResource? = {
guard let stakeUnitResource = stake.stakeUnitResource, stakeUnitResource.amount > 0 else {
Expand Down Expand Up @@ -502,6 +494,34 @@ extension OnLedgerEntity.Account.PoolUnitResources {
}
}

extension [OnLedgerEntity.OwnedNonFungibleResource] {
public var nonEmptyVaults: [OnLedgerEntity.OwnedNonFungibleResource] {
filter { $0.nonFungibleIdsCount > 0 }
}
}

extension OnLedgerEntity.OwnedFungibleResources {
public var nonEmptyVaults: OnLedgerEntity.OwnedFungibleResources {
.init(
xrdResource: xrdResource.flatMap { $0.amount > 0 ? $0 : nil },
nonXrdResources: nonXrdResources.filter { $0.amount > 0 }
)
}
}

extension OnLedgerEntity.Account {
public var nonEmptyVaults: OnLedgerEntity.Account {
.init(
address: address,
atLedgerState: atLedgerState,
metadata: metadata,
fungibleResources: fungibleResources.nonEmptyVaults,
nonFungibleResources: nonFungibleResources.nonEmptyVaults,
poolUnitResources: poolUnitResources.nonEmptyVaults
)
}
}

extension OnLedgerEntitiesClient {
public struct OwnedStakeDetails: Hashable, Sendable {
public let validator: OnLedgerEntity.Validator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,15 +374,15 @@ extension Address {
extension OnLedgerEntity.Account.PoolUnitResources {
// The fungible resources used to build up the pool units.
// Will be used to filter out those from the general fungible resources list.
var fungibleResourceAddresses: [String] {
radixNetworkStakes.compactMap(\.stakeUnitResource?.resourceAddress.address) +
poolUnits.map(\.resource.resourceAddress.address)
var fungibleResourceAddresses: [ResourceAddress] {
GhenadieVP marked this conversation as resolved.
Show resolved Hide resolved
radixNetworkStakes.compactMap(\.stakeUnitResource?.resourceAddress) +
poolUnits.map(\.resource.resourceAddress)
}

// The non fungible resources used to build up the pool units.
// Will be used to filter out those from the general fungible resources list.
var nonFungibleResourceAddresses: [String] {
radixNetworkStakes.compactMap(\.stakeClaimResource?.resourceAddress.address)
var nonFungibleResourceAddresses: [ResourceAddress] {
radixNetworkStakes.compactMap(\.stakeClaimResource?.resourceAddress)
}
}

Expand Down
19 changes: 19 additions & 0 deletions RadixWallet/Core/SharedModels/Assets/OnLedgerEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,22 @@ extension [OnLedgerEntity.NonFungibleToken.NFTData] {
self[.claimAmount]?.decimal
}
}

extension OnLedgerEntity.Account {
public var allFungibleResourceAddresses: [ResourceAddress] {
fungibleResources.xrdResource.asArray(\.resourceAddress) + fungibleResources.nonXrdResources.map(\.resourceAddress)
}

public var allResourceAddresses: Set<ResourceAddress> {
Set(
allFungibleResourceAddresses
+ nonFungibleResources.map(\.resourceAddress)
+ poolUnitResources.fungibleResourceAddresses
+ poolUnitResources.nonFungibleResourceAddresses
)
}

public func hasResource(_ resourceAddress: ResourceAddress) -> Bool {
allResourceAddresses.contains(resourceAddress)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extension AccountList {
guard !Task.isCancelled else {
return
}
await send(.internal(.accountPortfolioUpdate(accountPortfolio)))
await send(.internal(.accountPortfolioUpdate(accountPortfolio.nonEmptyVaults)))
}
}
case .backUpMnemonic:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ extension AssetTransfer.State {
guard $0.account != nil else {
return false
}
let fungibleAssets = $0.assets.compactMap(/ResourceAsset.State.fungibleAsset)
let nonFungibleAssets = $0.assets.compactMap(/ResourceAsset.State.nonFungibleAsset)
let fungibleAssets = $0.assets.fungibleAssets
let nonFungibleAssets = $0.assets.nonFungibleAssets

if !fungibleAssets.isEmpty {
return fungibleAssets.allSatisfy { $0.transferAmount != nil && $0.totalTransferSum <= $0.balance }
Expand Down Expand Up @@ -197,6 +197,7 @@ extension AssetTransfer {

try await instructionForDepositing(
bucket: bucket,
resource: resource.address,
into: account.recipient
)
}
Expand All @@ -221,6 +222,7 @@ extension AssetTransfer {

try await instructionForDepositing(
bucket: bucket,
resource: resource.address,
into: account.recipient
)
}
Expand All @@ -232,38 +234,80 @@ extension AssetTransfer {

func instructionForDepositing(
bucket: ManifestBuilderBucket,
resource: ResourceAddress,
into receivingAccount: ReceivingAccount.State.Account
) async throws -> ManifestBuilder.InstructionsChain.Instruction {
@Dependency(\.userDefaultsClient) var userDefaultsClient
@Dependency(\.secureStorageClient) var secureStorageClient
let isUserAccount = receivingAccount.isUserAccount
// TODO: Temporary revert of checking if the receiving account is a ledger account
let isSoftwareAccount = true // !receivingAccount.isLedgerAccount
let recipientAddress = receivingAccount.address
let userHasAccessToMnemonic = if let deviceFactorSourceID = receivingAccount.left?.deviceFactorSourceID {
await secureStorageClient.containsMnemonicIdentifiedByFactorSourceID(deviceFactorSourceID)
} else { false }

guard isUserAccount, isSoftwareAccount, userHasAccessToMnemonic else {
return try ManifestBuilder.accountTryDepositOrAbort(
recipientAddress.intoEngine(),
nil,
bucket
)

if case let .left(userAccount) = receivingAccount {
@Dependency(\.secureStorageClient) var secureStorageClient

let needsSignatureForDepositing = await needsSignatureForDepositting(into: userAccount, resource: resource)
let isSoftwareAccount = !receivingAccount.isLedgerAccount
let userHasAccessToMnemonic = userAccount.deviceFactorSourceID.map { deviceFactorSourceID in
secureStorageClient.containsMnemonicIdentifiedByFactorSourceID(deviceFactorSourceID)
} ?? false

if needsSignatureForDepositing, isSoftwareAccount && userHasAccessToMnemonic || !isSoftwareAccount {
return try ManifestBuilder.accountDeposit(
recipientAddress.intoEngine(),
bucket
)
}
}

return try ManifestBuilder.accountDeposit(
return try ManifestBuilder.accountTryDepositOrAbort(
recipientAddress.intoEngine(),
nil,
bucket
)
}

/// Determines if depositting the resource into an account requires the addition of a signature
func needsSignatureForDepositting(into receivingAccount: Profile.Network.Account, resource resourceAddress: ResourceAddress) async -> Bool {
let depositSettings = receivingAccount.onLedgerSettings.thirdPartyDeposits
let resourceException = depositSettings.assetsExceptionList.first { $0.address == resourceAddress }?.exceptionRule

switch (depositSettings.depositRule, resourceException) {
// AcceptAll
case (.acceptAll, .none):
return false
case (.acceptAll, .allow):
return false
case (.acceptAll, .deny):
return true

// Accept Known
case (.acceptKnown, .allow):
return false
case (.acceptKnown, .none):
// Check if the resource is known to the account
@Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient
let hasResource = await (try? onLedgerEntitiesClient
.getAccount(receivingAccount.address)
.hasResource(resourceAddress)
) ?? false

return !hasResource
case (.acceptKnown, .deny):
return true

// DenyAll
case (.denyAll, .none):
return true
case (.denyAll, .allow):
return false
case (.denyAll, .deny):
return true
}
}

extension AssetTransfer {
private func extractInvolvedFungibleResources(
_ receivingAccounts: IdentifiedArrayOf<ReceivingAccount.State>
) async throws -> IdentifiedArrayOf<InvolvedFungibleResource> {
let allResourceAddresses: [ResourceAddress] = try receivingAccounts.flatMap {
let addresses = try $0.assets.compactMap(/ResourceAsset.State.fungibleAsset).map {
let addresses = try $0.assets.fungibleAssets.map {
try ResourceAddress(validatingAddress: $0.id)
}
return addresses
Expand All @@ -277,7 +321,8 @@ extension AssetTransfer {
guard let account = receivingAccount.account else {
continue
}
for fungibleAsset in receivingAccount.assets.compactMap(/ResourceAsset.State.fungibleAsset) {
let assets = receivingAccount.assets.fungibleAssets
for fungibleAsset in assets {
guard let transferAmount = fungibleAsset.transferAmount else {
continue
}
Expand Down Expand Up @@ -313,8 +358,9 @@ extension AssetTransfer {
}

let accountAddress = account.address
let assets = receivingAccount.assets.nonFungibleAssets

for nonFungibleAsset in receivingAccount.assets.compactMap(/ResourceAsset.State.nonFungibleAsset) {
for nonFungibleAsset in assets {
if resources[id: nonFungibleAsset.resourceAddress] != nil {
if resources[id: nonFungibleAsset.resourceAddress]?.accounts[id: accountAddress] != nil {
resources[id: nonFungibleAsset.resourceAddress]?.accounts[id: accountAddress]?.tokens.append(nonFungibleAsset.nftToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ import SwiftUI
// MARK: - ResourceAsset
// Higher order reducer composing all types of assets that can be transferred
public struct ResourceAsset: Sendable, FeatureReducer {
public enum State: Sendable, Hashable, Identifiable {
public struct State: Sendable, Hashable, Identifiable {
public enum Kind: Sendable, Hashable {
case fungibleAsset(FungibleResourceAsset.State)
case nonFungibleAsset(NonFungibleResourceAsset.State)
}

public typealias ID = String
public var id: ID {
switch self {
switch self.kind {
case let .fungibleAsset(asset):
asset.id
case let .nonFungibleAsset(asset):
asset.id
}
}

case fungibleAsset(FungibleResourceAsset.State)
case nonFungibleAsset(NonFungibleResourceAsset.State)
public var kind: Kind
public var additionalSignatureRequired: Bool = false
}

public enum ChildAction: Sendable, Equatable {
Expand All @@ -34,13 +39,17 @@ public struct ResourceAsset: Sendable, FeatureReducer {
}

public var body: some ReducerOf<Self> {
Scope(state: \.kind, action: /Action.child) {
EmptyReducer()
.ifCaseLet(/State.Kind.fungibleAsset, action: /ChildAction.fungibleAsset) {
FungibleResourceAsset()
}
.ifCaseLet(/State.Kind.nonFungibleAsset, action: /ChildAction.nonFungibleAsset) {
NonFungibleResourceAsset()
}
}

Reduce(core)
.ifCaseLet(/State.fungibleAsset, action: /Action.child .. ChildAction.fungibleAsset) {
FungibleResourceAsset()
}
.ifCaseLet(/State.nonFungibleAsset, action: /Action.child .. ChildAction.nonFungibleAsset) {
NonFungibleResourceAsset()
}
}

public func reduce(into state: inout State, childAction: ChildAction) -> Effect<Action> {
Expand All @@ -62,9 +71,9 @@ public struct ResourceAsset: Sendable, FeatureReducer {

extension ResourceAsset.State {
mutating func unsetFocus() {
if case var .fungibleAsset(state) = self, state.focused {
if case var .fungibleAsset(state) = self.kind, state.focused {
state.focused = false
self = .fungibleAsset(state)
self.kind = .fungibleAsset(state)
}
}
}
Loading
Loading