[#381] Move Zatoshi and Amount Types to the SDK (#396)

Closes #381

This commit brings a Zatoshi type developed on the Secant project,
helper classes like Clamped and tests.

Zatoshi has been incorporated as a Codable type for SQLite Swift
to allow serialization into the pending database.

FIXES on Demo App

fix comments
This commit is contained in:
Francisco Gindre 2022-06-22 16:45:37 -03:00 committed by GitHub
parent 516d605d22
commit b9ae012e09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 949 additions and 589 deletions

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
"state": {
"branch": null,
"revision": "5cc08ebf77e68598bfe4c793927d0f2838598607",
"version": "1.6.1"
"revision": "466cc881f1760ed8c0e685900ed62dab7846a571",
"version": "1.8.0"
}
},
{
@ -105,8 +105,8 @@
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "6e94a7be32891d1b303a3fcfde8b5bf64d162e74",
"version": "1.19.1"
"revision": "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version": "1.22.0"
}
},
{
@ -132,17 +132,17 @@
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
"version": "1.18.0"
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version": "1.19.0"
}
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"state": {
"branch": "main",
"revision": "e5aaf60faf16554e47e4bb123a8b4e5c22475e9f",
"version": null
"branch": null,
"revision": "b7e8a2abab84c44046b4afe4ee4522a0fa2fcc7f",
"version": "0.0.3"
}
}
]

View File

@ -14,8 +14,8 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D8BB44E23B1DA0700D5E2A1"
BuildableName = "ZcashLightClientSample.app"
BlueprintIdentifier = "0DA1C4B027D11D9500E5006E"
BuildableName = "ZcashLightClientSample-Mainnet.app"
BlueprintName = "ZcashLightClientSample-Mainnet"
ReferencedContainer = "container:ZcashLightClientSample.xcodeproj">
</BuildableReference>
@ -50,15 +50,6 @@
ReferencedContainer = "container:ZcashLightClientSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D8BB44E23B1DA0700D5E2A1"
BuildableName = "ZcashLightClientSample.app"
BlueprintName = "ZcashLightClientSample-Mainnet"
ReferencedContainer = "container:ZcashLightClientSample.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -70,8 +61,8 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D8BB44E23B1DA0700D5E2A1"
BuildableName = "ZcashLightClientSample.app"
BlueprintIdentifier = "0DA1C4B027D11D9500E5006E"
BuildableName = "ZcashLightClientSample-Mainnet.app"
BlueprintName = "ZcashLightClientSample-Mainnet"
ReferencedContainer = "container:ZcashLightClientSample.xcodeproj">
</BuildableReference>

View File

@ -16,19 +16,13 @@ class GetBalanceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Account 0 Balance"
self.balance.text = "\(Initializer.shared.getBalance().asHumanReadableZecBalance()) ZEC"
self.verified.text = "\(Initializer.shared.getVerifiedBalance().asHumanReadableZecBalance()) ZEC"
self.balance.text = "\(Initializer.shared.getBalance().formattedString ?? "0.0") ZEC"
self.verified.text = "\(Initializer.shared.getVerifiedBalance().formattedString ?? "0.0") ZEC"
}
}
extension Int64 {
func asHumanReadableZecBalance() -> Double {
Double(self) / Double(ZcashSDK.zatoshiPerZEC)
}
}
extension Double {
func toZatoshi() -> Int64 {
Int64(self * Double(ZcashSDK.zatoshiPerZEC))
extension Zatoshi {
var formattedString: String? {
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self.amount))
}
}

View File

@ -32,8 +32,8 @@ class GetUTXOsViewController: UIViewController {
// swiftlint:disable:next force_try
let balance = try! AppDelegate.shared.sharedSynchronizer.getTransparentBalance(accountIndex: 0)
self.totalBalanceLabel.text = String(balance.total.asHumanReadableZecBalance())
self.verifiedBalanceLabel.text = String(balance.verified.asHumanReadableZecBalance())
self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.total.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.verified.amount))
}
@IBAction func shieldFunds(_ sender: Any) {

View File

@ -102,8 +102,8 @@ class SendViewController: UIViewController {
)
}
func format(balance: Int64 = 0) -> String {
"Zec \(balance.asHumanReadableZecBalance())"
func format(balance: Zatoshi = Zatoshi()) -> String {
"Zec \(balance.formattedString ?? "0.0")"
}
func toggleSendButton() {
@ -111,9 +111,9 @@ class SendViewController: UIViewController {
}
func maxFundsOn() {
let fee: Int64 = 10000
let max = wallet.getVerifiedBalance() - fee
amountTextField.text = String(max.asHumanReadableZecBalance())
let fee = Zatoshi(10000)
let max: Zatoshi = wallet.getVerifiedBalance() - fee
amountTextField.text = format(balance: max)
amountTextField.isEnabled = false
}
@ -131,14 +131,14 @@ class SendViewController: UIViewController {
}
func isBalanceValid() -> Bool {
wallet.getVerifiedBalance() > 0
wallet.getVerifiedBalance() > .zero
}
func isAmountValid() -> Bool {
guard
let value = amountTextField.text,
let amount = Double(value),
amount.toZatoshi() <= wallet.getVerifiedBalance()
let amount = NumberFormatter.zcashNumberFormatter.number(from: value).flatMap({ Zatoshi($0.int64Value) }),
amount <= wallet.getVerifiedBalance()
else {
return false
}
@ -197,7 +197,12 @@ class SendViewController: UIViewController {
}
func send() {
guard isFormValid(), let amount = amountTextField.text, let zec = Double(amount)?.toZatoshi(), let recipient = addressTextField.text else {
guard
isFormValid(),
let amount = amountTextField.text,
let zec = NumberFormatter.zcashNumberFormatter.number(from: amount).flatMap({ Zatoshi($0.int64Value) }),
let recipient = addressTextField.text
else {
loggerProxy.warn("WARNING: Form is invalid")
return
}

View File

@ -24,7 +24,7 @@ final class TransactionDetailModel {
self.minedHeight = confirmedTransaction.minedHeight.description
self.expiryHeight = confirmedTransaction.expiryHeight?.description
self.created = Date(timeIntervalSince1970: confirmedTransaction.blockTimeInSeconds).description
self.zatoshi = confirmedTransaction.value.description
self.zatoshi = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: confirmedTransaction.value.amount))
if let memoData = confirmedTransaction.memo, let memoString = String(bytes: memoData, encoding: .utf8) {
self.memo = memoString
}
@ -34,7 +34,7 @@ final class TransactionDetailModel {
self.minedHeight = pendingTransaction.minedHeight.description
self.expiryHeight = pendingTransaction.expiryHeight.description
self.created = Date(timeIntervalSince1970: pendingTransaction.createTime).description
self.zatoshi = pendingTransaction.value.description
self.zatoshi = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: pendingTransaction.value.amount))
}
init(transaction: TransactionEntity) {

View File

@ -56,155 +56,132 @@ class ZcashMainnet: ZcashNetwork {
Constants of ZcashLightClientKit. this constants don't
*/
public enum ZcashSDK {
/**
The number of zatoshi that equal 1 ZEC.
*/
/// The number of zatoshi that equal 1 ZEC.
public static var zatoshiPerZEC: BlockHeight = 100_000_000
/**
The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
*/
/// The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
public static var maxReorgSize = 100
/**
The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled
by the rust backend but it is helpful to know what it is set to and should be kept in sync.
*/
/// The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled
/// by the rust backend but it is helpful to know what it is set to and should be kept in sync.
public static var expiryOffset = 20
//
// Defaults
//
/**
Default size of batches of blocks to request from the compact block service.
*/
// mark: Defaults
/// Default size of batches of blocks to request from the compact block service.
public static var DefaultBatchSize = 100
/**
Default amount of time, in in seconds, to poll for new blocks. Typically, this should be about half the average
block time.
*/
/// Default amount of time, in in seconds, to poll for new blocks. Typically, this should be about half the average
/// block time.
public static var defaultPollInterval: TimeInterval = 20
/**
Default attempts at retrying.
*/
/// Default attempts at retrying.
public static var defaultRetries: Int = 5
/**
The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than
this before retyring.
*/
/// The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than
/// this before retyring.
public static var defaultMaxBackOffInterval: TimeInterval = 600
/**
Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the
reorg but smaller than the theoretical max reorg size of 100.
*/
/// Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the
/// reorg but smaller than the theoretical max reorg size of 100.
public static var defaultRewindDistance: Int = 10
/**
The number of blocks to allow before considering our data to be stale. This usually helps with what to do when
returning from the background and is exposed via the Synchronizer's isStale function.
*/
/// The number of blocks to allow before considering our data to be stale. This usually helps with what to do when
/// returning from the background and is exposed via the Synchronizer's isStale function.
public static var defaultStaleTolerance: Int = 10
/**
Default Name for LibRustZcash data.db
*/
/// Default Name for LibRustZcash data.db
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
/// Default Name for Compact Block caches db
public static var defaultCacheDbName = "caches.db"
/**
Default name for pending transactions db
*/
/// Default name for pending transactions db
public static var defaultPendingDbName = "pending.db"
/**
File name for the sapling spend params
*/
/// File name for the sapling spend params
public static var spendParamFilename = "sapling-spend.params"
/**
File name for the sapling output params
*/
/// File name for the sapling output params
public static var outputParamFilename = "sapling-output.params"
/**
The Url that is used by default in zcashd.
We'll want to make this externally configurable, rather than baking it into the SDK but
this will do for now, since we're using a cloudfront URL that already redirects.
*/
/// The Url that is used by default in zcashd.
/// We'll want to make this externally configurable, rather than baking it into the SDK but
/// this will do for now, since we're using a cloudfront URL that already redirects.
public static var cloudParameterURL = "https://z.cash/downloads/"
}
public protocol NetworkConstants {
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
prior to this height, at all.
*/
/// The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
/// prior to this height, at all.
static var saplingActivationHeight: BlockHeight { get }
/**
Default Name for LibRustZcash data.db
*/
/// Default Name for LibRustZcash data.db
static var defaultDataDbName: String { get }
/**
Default Name for Compact Block caches db
*/
/// Default Name for Compact Block caches db
static var defaultCacheDbName: String { get }
/**
Default name for pending transactions db
*/
/// Default name for pending transactions db
static var defaultPendingDbName: String { get }
/// Default prefix for db filenames
static var defaultDbNamePrefix: String { get }
/**
fixed height where the SDK considers that the ZIP-321 was deployed. This is a workaround
for librustzcash not figuring out the tx fee from the tx itself.
*/
/// fixed height where the SDK considers that the ZIP-321 was deployed. This is a workaround
/// for librustzcash not figuring out the tx fee from the tx itself.
static var feeChangeHeight: BlockHeight { get }
@available(*, deprecated, message: "This function will be removed soon. Use the one returning `Zatoshi` instead")
static func defaultFee(for height: BlockHeight) -> Int64
/// Returns the default fee according to the blockheight. see [ZIP-313](https://zips.z.cash/zip-0313)
static func defaultFee(for height: BlockHeight) -> Zatoshi
}
public extension NetworkConstants {
@available(*, deprecated, message: "This function will be removed soon. Use the one returning `Zatoshi` instead")
static func defaultFee(for height: BlockHeight = BlockHeight.max) -> Int64 {
guard height >= feeChangeHeight else { return 10_000 }
guard height >= feeChangeHeight else { return 10_000 }
return 1_000
}
static func defaultFee(for height: BlockHeight = BlockHeight.max) -> Zatoshi {
guard height >= feeChangeHeight else { return Zatoshi(10_000) }
return Zatoshi(1_000)
}
}
public class ZcashSDKMainnetConstants: NetworkConstants {
private init() {}
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
prior to this height, at all.
*/
/// The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
/// prior to this height, at all.
public static var saplingActivationHeight: BlockHeight = 419_200
/**
Default Name for LibRustZcash data.db
*/
/// Default Name for LibRustZcash data.db
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
/// Default Name for Compact Block caches db
public static var defaultCacheDbName = "caches.db"
/**
Default name for pending transactions db
*/
/// Default name for pending transactions db
public static var defaultPendingDbName = "pending.db"
public static var defaultDbNamePrefix = "ZcashSdk_mainnet_"
@ -215,31 +192,26 @@ public class ZcashSDKMainnetConstants: NetworkConstants {
public class ZcashSDKTestnetConstants: NetworkConstants {
private init() {}
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
prior to this height, at all.
*/
/// The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
/// prior to this height, at all.
public static var saplingActivationHeight: BlockHeight = 280_000
/**
Default Name for LibRustZcash data.db
*/
/// Default Name for LibRustZcash data.db
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
/// Default Name for Compact Block caches db
public static var defaultCacheDbName = "caches.db"
/**
Default name for pending transactions db
*/
/// Default name for pending transactions db
public static var defaultPendingDbName = "pending.db"
public static var defaultDbNamePrefix = "ZcashSdk_testnet_"
/**
Estimated height where wallets are supposed to change the fee
*/
/// Estimated height where wallets are supposed to change the fee
public static var feeChangeHeight: BlockHeight = 1_028_500
}

View File

@ -8,6 +8,7 @@
import Foundation
import SQLite
struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
enum CodingKeys: String, CodingKey {
case toAddress = "to_address"
case accountIndex = "account_index"
@ -38,7 +39,7 @@ struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
var createTime: TimeInterval
var raw: Data?
var id: Int?
var value: Int
var value: Zatoshi
var memo: Data?
var rawTransactionId: Data?
@ -69,7 +70,7 @@ struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
extension PendingTransaction {
// TODO: Handle Memo
init(value: Int, toAddress: String, memo: String?, account index: Int) {
init(value: Zatoshi, toAddress: String, memo: String?, account index: Int) {
self = PendingTransaction(
toAddress: toAddress,
accountIndex: index,
@ -83,7 +84,7 @@ extension PendingTransaction {
createTime: Date().timeIntervalSince1970,
raw: nil,
id: nil,
value: Int(value),
value: value,
memo: memo?.encodeAsZcashTransactionMemo(),
rawTransactionId: nil
)
@ -104,7 +105,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
static var createTime = Expression<TimeInterval?>("create_time")
static var raw = Expression<Blob?>("raw")
static var id = Expression<Int>("id")
static var value = Expression<Int>("value")
static var value = Expression<Zatoshi>("value")
static var memo = Expression<Blob?>("memo")
static var rawTransactionId = Expression<Blob?>("txid")
}
@ -186,6 +187,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
do {
let pendingTx: PendingTransaction = try row.decode()
return pendingTx
} catch {
throw StorageError.operationFailed

View File

@ -120,7 +120,7 @@ enum TransactionBuilder {
transactionIndex: Int(transactionIndex),
raw: raw,
id: Int(id),
value: Int(value),
value: Zatoshi(value),
memo: memo,
rawTransactionId: transactionId
)
@ -161,7 +161,7 @@ enum TransactionBuilder {
transactionIndex: Int(transactionIndex),
raw: rawData,
id: Int(id),
value: Int(value),
value: Zatoshi(value),
memo: memo,
rawTransactionId: transactionId
)

View File

@ -37,7 +37,7 @@ struct ConfirmedTransaction: ConfirmedTransactionEntity {
var transactionIndex: Int
var raw: Data?
var id: Int?
var value: Int
var value: Zatoshi
var memo: Data?
var rawTransactionId: Data?
}

View File

@ -166,16 +166,18 @@ class UnspentTransactionOutputSQLDAO: UnspentTransactionOutputRepository {
.filter(TableColumns.address == address)
) ?? 0
return TransparentBalance(verified: Int64(verified), total: Int64(total), address: address)
return WalletBalance(
verified: Zatoshi(Int64(verified)),
total: Zatoshi(Int64(total))
)
} catch {
throw StorageError.operationFailed
}
}
}
struct TransparentBalance: WalletBalance {
var verified: Int64
var total: Int64
struct TransparentBalance {
var balance: WalletBalance
var address: String
}

View File

@ -87,7 +87,7 @@ public protocol AbstractTransaction {
/**
value in zatoshi
*/
var value: Int { get set }
var value: Zatoshi { get set }
/**
data containing the memo if any
@ -159,3 +159,9 @@ public extension ConfirmedTransactionEntity {
self.blockTimeInSeconds * 1000
}
}
public extension AbstractTransaction {
var intValue: Int {
Int(self.value.amount)
}
}

View File

@ -0,0 +1,19 @@
//
// NumberFormatter+Zcash.swift
//
// Created by Lukáš Korba on 02.06.2022.
// modified by Francisco Gindre on 6/17/22.
//
import Foundation
public extension NumberFormatter {
static let zcashNumberFormatter: NumberFormatter = {
var formatter = NumberFormatter()
formatter.maximumFractionDigits = 8
formatter.maximumIntegerDigits = 8
formatter.numberStyle = .decimal
formatter.usesGroupingSeparator = true
return formatter
}()
}

View File

@ -0,0 +1,38 @@
//
// Zatoshi+Codable.swift
//
//
// Created by Francisco Gindre on 6/20/22.
//
import Foundation
/// This extension is needed to support SQLite Swift Codable Types
extension Zatoshi: Codable {
enum CodingError: Error {
case encodingError(String)
}
/// This codable implementation responds to limitaitons that SQLite Swift explains
/// on its documentation https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#codable-types
/// SQLite Sqift will encode custom types into a string and stores it in a single column. They do so by
/// leveraging the Codable interface so this has to abide by them and their choice.
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(String.self)
guard let amount = Int64(value) else {
throw CodingError.encodingError("Decoding Error")
}
self.amount = amount
}
/// This codable implementation responds to limitaitons that SQLite Swift explains
/// on its documentation https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#codable-types
/// SQLite Sqift will encode custom types into a string and stores it in a single column. They do so by
/// leveraging the Codable interface so this has to abide by them and their choice.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(String(self.amount))
}
}

View File

@ -0,0 +1,20 @@
//
// Zatoshi+SQLite.swift
//
//
// Created by Francisco Gindre on 6/20/22.
//
import SQLite
extension Zatoshi: Value {
public static var declaredDatatype = Int64.declaredDatatype
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Zatoshi {
Zatoshi(datatypeValue)
}
public var datatypeValue: Int64 {
self.amount
}
}

View File

@ -252,21 +252,47 @@ public class Initializer {
try? accountRepository.findBy(account: account)?.address
}
/**
get (unverified) balance from the given account index
- Parameter account: the index of the account
*/
/// get (unverified) balance from the given account index
/// - Parameter account: the index of the account
@available(*, deprecated, message: "This function will be removed soon. Use the function returning `Zatoshi` instead")
public func getBalance(account index: Int = 0) -> Int64 {
rustBackend.getBalance(dbData: dataDbURL, account: Int32(index), networkType: network.networkType)
}
/**
get verified balance from the given account index
- Parameter account: the index of the account
*/
/// get (unverified) balance from the given account index
/// - Parameter account: the index of the account
/// - Returns: balance in `Zatoshi`
public func getBalance(account index: Int = 0) -> Zatoshi {
Zatoshi(
rustBackend.getBalance(
dbData: dataDbURL,
account: Int32(index),
networkType: network.networkType
)
)
}
/// get verified balance from the given account index
/// - Parameter account: the index of the account
@available(*, deprecated, message: "This function will be removed soon. Use the one returning `Zatoshi` instead")
public func getVerifiedBalance(account index: Int = 0) -> Int64 {
rustBackend.getVerifiedBalance(dbData: dataDbURL, account: Int32(index), networkType: network.networkType)
}
/// get verified balance from the given account index
/// - Parameter account: the index of the account
/// - Returns: balance in `Zatoshi`
public func getVerifiedBalance(account index: Int = 0) -> Zatoshi {
Zatoshi(
rustBackend.getVerifiedBalance(
dbData: dataDbURL,
account: Int32(index),
networkType: network.networkType
)
)
}
/**
checks if the provided address is a valid shielded zAddress

View File

@ -102,7 +102,7 @@ public protocol UnifiedAddress {
var zAddress: SaplingShieldedAddress { get }
}
public protocol WalletBalance {
var verified: Int64 { get set }
var total: Int64 { get set }
public struct WalletBalance {
public var verified: Zatoshi
public var total: Zatoshi
}

View File

@ -0,0 +1,91 @@
//
// Zatoshi.swift
// secant-testnet
//
// Created by Lukáš Korba on 26.05.2022.
// Modified and ported to ZcashLightClientKit by Francisco Gindre
import Foundation
public struct Zatoshi {
public enum Constants {
public static let oneZecInZatoshi: Int64 = 100_000_000
public static let maxZecSupply: Int64 = 21_000_000
public static let maxZatoshi: Int64 = Constants.oneZecInZatoshi * Constants.maxZecSupply
}
public static var zero: Zatoshi { Zatoshi() }
static let decimalHandler = NSDecimalNumberHandler(
roundingMode: NSDecimalNumber.RoundingMode.bankers,
scale: 8,
raiseOnExactness: true,
raiseOnOverflow: true,
raiseOnUnderflow: true,
raiseOnDivideByZero: true
)
@Clamped(-Constants.maxZatoshi...Constants.maxZatoshi)
public var amount: Int64 = 0
/// Converts `Zatoshi` to `NSDecimalNumber`
public var decimalValue: NSDecimalNumber {
NSDecimalNumber(decimal: Decimal(amount) / Decimal(Constants.oneZecInZatoshi))
}
public init(_ amount: Int64 = 0) {
self.amount = amount
}
/// Converts `Zatoshi` to human readable format, up to 8 fraction digits
public func decimalString(formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> String {
formatter.string(from: decimalValue.roundedZec) ?? ""
}
/// Converts `Decimal` to `Zatoshi`
public static func from(decimal: Decimal) -> Zatoshi {
let roundedZec = NSDecimalNumber(decimal: decimal).roundedZec
let zec2zatoshi = Decimal(Constants.oneZecInZatoshi) * roundedZec.decimalValue
return Zatoshi(NSDecimalNumber(decimal: zec2zatoshi).int64Value)
}
/// Converts `String` to `Zatoshi`
public static func from(decimalString: String, formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> Zatoshi? {
if let number = formatter.number(from: decimalString) {
return Zatoshi.from(decimal: number.decimalValue)
}
return nil
}
public static func + (left: Zatoshi, right: Zatoshi) -> Zatoshi {
Zatoshi(left.amount + right.amount)
}
public static func - (left: Zatoshi, right: Zatoshi) -> Zatoshi {
Zatoshi(left.amount - right.amount)
}
}
extension Zatoshi: Equatable {
public static func == (lhs: Zatoshi, rhs: Zatoshi) -> Bool {
lhs.amount == rhs.amount
}
}
extension Zatoshi: Comparable {
public static func < (lhs: Zatoshi, rhs: Zatoshi) -> Bool {
lhs.amount < rhs.amount
}
}
extension NSDecimalNumber {
/// Round the decimal to 8 fraction digits
var roundedZec: NSDecimalNumber {
self.rounding(accordingToBehavior: Zatoshi.decimalHandler)
}
/// Converts `NSDecimalNumber` to human readable format, up to 8 fraction digits
var decimalString: String {
self.roundedZec.stringValue
}
}

View File

@ -294,7 +294,8 @@ class ZcashRustBackend: ZcashRustBackendWelding {
static func downloadedUtxoBalance(dbData: URL, address: String, networkType: NetworkType) throws -> WalletBalance {
let verified = try getVerifiedTransparentBalance(dbData: dbData, address: address, networkType: networkType)
let total = try getTransparentBalance(dbData: dbData, address: address, networkType: networkType)
return TransparentBalance(verified: verified, total: total, address: address)
return WalletBalance(verified: Zatoshi(verified), total: Zatoshi(total))
}
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? {

View File

@ -8,9 +8,8 @@
import Foundation
/**
Represents errors thrown by a Synchronizer
*/
/// Represents errors thrown by a Synchronizer
public enum SynchronizerError: Error {
case initFailed(message: String)
case notPrepared
@ -48,98 +47,74 @@ extension ShieldFundsError: LocalizedError {
}
}
/**
Represent the connection state to the lightwalletd server
*/
/// Represent the connection state to the lightwalletd server
public enum ConnectionState {
/**
not in use
*/
/// not in use
case idle
/**
there's a connection being attempted from a non error state
*/
/// there's a connection being attempted from a non error state
case connecting
/**
connection is established, ready to use or in use
*/
/// connection is established, ready to use or in use
case online
/**
the connection is being re-established after losing it temporarily
*/
/// the connection is being re-established after losing it temporarily
case reconnecting
/**
the connection has been closed
*/
/// the connection has been closed
case shutdown
}
/**
Primary interface for interacting with the SDK. Defines the contract that specific
implementations like SdkSynchronizer fulfill.
*/
/// Primary interface for interacting with the SDK. Defines the contract that specific
/// implementations like SdkSynchronizer fulfill.
public protocol Synchronizer {
/**
Value representing the Status of this Synchronizer. As the status changes, it will be also notified
*/
/// Value representing the Status of this Synchronizer. As the status changes, it will be also notified
var status: SyncStatus { get }
/**
reflects current connection state to LightwalletEndpoint
*/
/// reflects current connection state to LightwalletEndpoint
var connectionState: ConnectionState { get }
/**
prepares this initializer to operate. Initializes the internal state with the given Extended Viewing Keys and a wallet birthday found in the initializer object
*/
/// prepares this initializer to operate. Initializes the internal state with the given
/// Extended Viewing Keys and a wallet birthday found in the initializer object
func prepare() throws
/**
Starts this synchronizer within the given scope.
Implementations should leverage structured concurrency and
cancel all jobs when this scope completes.
*/
///Starts this synchronizer within the given scope.
///
///Implementations should leverage structured concurrency and
///cancel all jobs when this scope completes.
func start(retry: Bool) throws
/**
Stop this synchronizer. Implementations should ensure that calling this method cancels all jobs that were created by this instance.
*/
/// Stop this synchronizer. Implementations should ensure that calling this method cancels all jobs that were created by this instance.
func stop() throws
/**
Gets the sapling shielded address for the given account.
- Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
- Returns the address or nil if account index is incorrect
*/
/// Gets the sapling shielded address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress?
/**
Gets the unified address for the given account.
- Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
- Returns the address or nil if account index is incorrect
*/
/// Gets the unified address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress?
/**
Gets the transparent address for the given account.
- Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
- Returns the address or nil if account index is incorrect
*/
/// Gets the transparent address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getTransparentAddress(accountIndex: Int) -> TransparentAddress?
/**
Sends zatoshi.
- Parameter spendingKey: the key that allows spends to occur.
- Parameter zatoshi: the amount of zatoshi to send.
- Parameter toAddress: the recipient's address.
- Parameter memo: the optional memo to include as part of the transaction.
- Parameter accountIndex: the optional account id to use. By default, the first account is used.
*/
/// Sends zatoshi.
/// - Parameter spendingKey: the key that allows spends to occur.
/// - Parameter zatoshi: the amount of zatoshi to send.
/// - Parameter toAddress: the recipient's address.
/// - Parameter memo: the optional memo to include as part of the transaction.
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used.
@available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead")
// swiftlint:disable:next function_parameter_count
func sendToAddress(
spendingKey: String,
@ -149,14 +124,29 @@ public protocol Synchronizer {
from accountIndex: Int,
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
)
/**
Sends zatoshi.
- Parameter spendingKey: the key that allows spends to occur.
- Parameter transparentSecretKey: the key that allows to spend transaprent funds
- Parameter memo: the optional memo to include as part of the transaction.
- Parameter accountIndex: the optional account id that will be used to shield your funds to. By default, the first account is used.
*/
/// Sends zatoshi.
/// - Parameter spendingKey: the key that allows spends to occur.
/// - Parameter zatoshi: the amount to send in Zatoshi.
/// - Parameter toAddress: the recipient's address.
/// - Parameter memo: the optional memo to include as part of the transaction.
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used.
// swiftlint:disable:next function_parameter_count
func sendToAddress(
spendingKey: String,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
)
/// Sends zatoshi.
/// - Parameter spendingKey: the key that allows spends to occur.
/// - Parameter transparentSecretKey: the key that allows to spend transaprent funds
/// - Parameter memo: the optional memo to include as part of the transaction.
/// - Parameter accountIndex: the optional account id that will be used to shield your funds to. By default, the first account is used.
func shieldFunds(
spendingKey: String,
transparentSecretKey: String,
@ -164,142 +154,124 @@ public protocol Synchronizer {
from accountIndex: Int,
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
)
/**
Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
an option if the transaction has not yet been submitted to the server.
- Parameter transaction: the transaction to cancel.
- Returns: true when the cancellation request was successful. False when it is too late.
*/
/// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
/// an option if the transaction has not yet been submitted to the server.
/// - Parameter transaction: the transaction to cancel.
/// - Returns: true when the cancellation request was successful. False when it is too late.
func cancelSpend(transaction: PendingTransactionEntity) -> Bool
/**
all outbound pending transactions that have been sent but are awaiting confirmations
*/
/// all outbound pending transactions that have been sent but are awaiting confirmations
var pendingTransactions: [PendingTransactionEntity] { get }
/**
all the transactions that are on the blockchain
*/
/// all the transactions that are on the blockchain
var clearedTransactions: [ConfirmedTransactionEntity] { get }
/**
All transactions that are related to sending funds
*/
/// All transactions that are related to sending funds
var sentTransactions: [ConfirmedTransactionEntity] { get }
/**
all transactions related to receiving funds
*/
/// all transactions related to receiving funds
var receivedTransactions: [ConfirmedTransactionEntity] { get }
/**
A repository serving transactions in a paginated manner
- Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
*/
/// A repository serving transactions in a paginated manner
/// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/**
Returns a list of confirmed transactions that preceed the given transaction with a limit count.
- Parameters:
- from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction
- limit: the maximum amount of items this should return if available
- Returns: an array with the given Transactions or nil
*/
/// Returns a list of confirmed transactions that preceed the given transaction with a limit count.
/// - Parameters:
/// - from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction
/// - limit: the maximum amount of items this should return if available
/// - Returns: an array with the given Transactions or nil
func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]?
/**
Returns the latest downloaded height from the compact block cache
*/
/// Returns the latest downloaded height from the compact block cache
func latestDownloadedHeight() throws -> BlockHeight
/**
Returns the latest block height from the provided Lightwallet endpoint
*/
/// Returns the latest block height from the provided Lightwallet endpoint
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/**
Returns the latest block height from the provided Lightwallet endpoint
Blocking
*/
/// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking
func latestHeight() throws -> BlockHeight
/**
Returns the latests UTXOs for the given address from the specified height on
*/
/// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void)
/**
Returns the last stored unshielded balance
*/
/// Returns the last stored unshielded balance
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance
/**
Returns the shielded total balance (includes verified and unverified balance)
*/
/// Returns the shielded total balance (includes verified and unverified balance)
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
func getShieldedBalance(accountIndex: Int) -> Int64
/**
Returns the shielded verified balance (anchor is 10 blocks back)
*/
/// Returns the shielded total balance (includes verified and unverified balance)
func getShieldedBalance(accountIndex: Int) -> Zatoshi
/// Returns the shielded verified balance (anchor is 10 blocks back)
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
func getShieldedVerifiedBalance(accountIndex: Int) -> Int64
/// Returns the shielded verified balance (anchor is 10 blocks back)
func getShieldedVerifiedBalance(accountIndex: Int) -> Zatoshi
/**
Stops the synchronizer and rescans the known blocks with the current keys.
- Parameter policy: the rewind policy
- Throws rewindErrorUnknownArchorHeight when the rewind points to an invalid height
- Throws rewindError for other errors
- Note rewind does not trigger notifications as a reorg would. You need to restart the synchronizer afterwards
*/
/// Stops the synchronizer and rescans the known blocks with the current keys.
/// - Parameter policy: the rewind policy
/// - Throws rewindErrorUnknownArchorHeight when the rewind points to an invalid height
/// - Throws rewindError for other errors
/// - Note rewind does not trigger notifications as a reorg would. You need to restart the synchronizer afterwards
func rewind(_ policy: RewindPolicy) throws
}
public enum SyncStatus: Equatable {
/**
Indicates that this Synchronizer is actively preparing to start, which usually involves setting up database tables, migrations or taking other maintenance steps that need to occur after an upgrade.
*/
/// Indicates that this Synchronizer is actively preparing to start,
/// which usually involves setting up database tables, migrations or
/// taking other maintenance steps that need to occur after an upgrade.
case unprepared
/**
Indicates that this Synchronizer is actively downloading new blocks from the server.
*/
/// Indicates that this Synchronizer is actively downloading new blocks from the server.
case downloading(_ status: BlockProgress)
/**
Indicates that this Synchronizer is actively validating new blocks that were downloaded
from the server. Blocks need to be verified before they are scanned. This confirms that
each block is chain-sequential, thereby detecting missing blocks and reorgs.
*/
/// Indicates that this Synchronizer is actively validating new blocks that were downloaded
/// from the server. Blocks need to be verified before they are scanned. This confirms that
/// each block is chain-sequential, thereby detecting missing blocks and reorgs.
case validating
/**
Indicates that this Synchronizer is actively scanning new valid blocks that were downloaded from the server.
*/
/// Indicates that this Synchronizer is actively scanning new valid blocks that were
/// downloaded from the server.
case scanning(_ progress: BlockProgress)
/**
Indicates that this Synchronizer is actively enhancing newly scanned blocks with additional transaction details, fetched from the server.
*/
/// Indicates that this Synchronizer is actively enhancing newly scanned blocks
/// with additional transaction details, fetched from the server.
case enhancing(_ progress: EnhancementProgress)
/**
fetches the transparent balance and stores it locally
*/
/// fetches the transparent balance and stores it locally
case fetching
/**
Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
When set, a UI element may want to turn green.
*/
/// Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
/// When set, a UI element may want to turn green.
case synced
/**
Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
*/
/// Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
case stopped
/**
Indicates that this Synchronizer is disconnected from its lightwalletd server.
When set, a UI element may want to turn red.
*/
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
/// When set, a UI element may want to turn red.
case disconnected
case error(_ error: Error)
@ -321,22 +293,19 @@ public enum SyncStatus: Equatable {
}
}
/**
Kind of transactions handled by a Synchronizer
*/
/// Kind of transactions handled by a Synchronizer
public enum TransactionKind {
case sent
case received
case all
}
/**
Type of rewind available
-birthday: rewinds the local state to this wallet's birthday
-height: rewinds to the nearest blockheight to the one given as argument.
-transaction: rewinds to the nearest height based on the anchor of the provided transaction.
*/
/// Type of rewind available
/// -birthday: rewinds the local state to this wallet's birthday
/// -height: rewinds to the nearest blockheight to the one given as argument.
/// -transaction: rewinds to the nearest height based on the anchor of the provided transaction.
public enum RewindPolicy {
case birthday
case height(blockheight: BlockHeight)

View File

@ -9,98 +9,75 @@
import Foundation
public extension Notification.Name {
/**
Notification is posted whenever transactions are updated
- Important: not yet posted
*/
/// Notification is posted whenever transactions are updated
///
/// - Important: not yet posted
static let transactionsUpdated = Notification.Name("SDKSyncronizerTransactionUpdated")
/**
Posted when the synchronizer is started.
*/
/// Posted when the synchronizer is started.
static let synchronizerStarted = Notification.Name("SDKSyncronizerStarted")
/**
Posted when there are progress updates.
- Note: Query userInfo object for NotificationKeys.progress for Float progress percentage and NotificationKeys.blockHeight for the current progress height
*/
/// Posted when there are progress updates.
///
/// - Note: Query userInfo object for NotificationKeys.progress for Float
/// progress percentage and NotificationKeys.blockHeight /// for the current progress height
static let synchronizerProgressUpdated = Notification.Name("SDKSyncronizerProgressUpdated")
static let synchronizerStatusWillUpdate = Notification.Name("SDKSynchronizerStatusWillUpdate")
/**
Posted when the synchronizer is synced to latest height
*/
/// Posted when the synchronizer is synced to latest height
static let synchronizerSynced = Notification.Name("SDKSyncronizerSynced")
/**
Posted when the synchronizer is stopped
*/
/// Posted when the synchronizer is stopped
static let synchronizerStopped = Notification.Name("SDKSyncronizerStopped")
/**
Posted when the synchronizer loses connection
*/
/// Posted when the synchronizer loses connection
static let synchronizerDisconnected = Notification.Name("SDKSyncronizerDisconnected")
/**
Posted when the synchronizer starts syncing
*/
/// Posted when the synchronizer starts syncing
static let synchronizerSyncing = Notification.Name("SDKSyncronizerSyncing")
/**
Posted when synchronizer starts downloading blocks
*/
/// Posted when synchronizer starts downloading blocks
static let synchronizerDownloading = Notification.Name("SDKSyncronizerDownloading")
/**
Posted when synchronizer starts validating blocks
*/
/// Posted when synchronizer starts validating blocks
static let synchronizerValidating = Notification.Name("SDKSyncronizerValidating")
/**
Posted when synchronizer starts scanning blocks
*/
/// Posted when synchronizer starts scanning blocks
static let synchronizerScanning = Notification.Name("SDKSyncronizerScanning")
/**
Posted when the synchronizer starts Enhancing
*/
/// Posted when the synchronizer starts Enhancing
static let synchronizerEnhancing = Notification.Name("SDKSyncronizerEnhancing")
/**
Posted when the synchronizer starts fetching UTXOs
*/
/// Posted when the synchronizer starts fetching UTXOs
static let synchronizerFetching = Notification.Name("SDKSyncronizerFetching")
/**
Posted when the synchronizer finds a pendingTransaction that hast been newly mined
- Note: query userInfo on NotificationKeys.minedTransaction for the transaction
*/
/// Posted when the synchronizer finds a pendingTransaction that hast been newly mined
/// - Note: query userInfo on NotificationKeys.minedTransaction for the transaction
static let synchronizerMinedTransaction = Notification.Name("synchronizerMinedTransaction")
/**
Posted when the synchronizer finds a mined transaction
- Note: query userInfo on NotificationKeys.foundTransactions for the [ConfirmedTransactionEntity]. This notification could arrive in a background thread.
*/
/// Posted when the synchronizer finds a mined transaction
/// - Note: query userInfo on NotificationKeys.foundTransactions for
/// the `[ConfirmedTransactionEntity]`. This notification could arrive in a background thread.
static let synchronizerFoundTransactions = Notification.Name("synchronizerFoundTransactions")
/**
Posted when the synchronizer presents an error
- Note: query userInfo on NotificationKeys.error for an error
*/
/// Posted when the synchronizer presents an error
/// - Note: query userInfo on NotificationKeys.error for an error
static let synchronizerFailed = Notification.Name("SDKSynchronizerFailed")
static let synchronizerConnectionStateChanged = Notification.Name("SynchronizerConnectionStateChanged")
}
/**
Synchronizer implementation for UIKit and iOS 12+
*/
/// Synchronizer implementation for UIKit and iOS 12+
// swiftlint:disable type_body_length
public class SDKSynchronizer: Synchronizer {
public enum NotificationKeys {
public static let progress = "SDKSynchronizer.progress"
public static let blockHeight = "SDKSynchronizer.blockHeight"
@ -132,10 +109,8 @@ public class SDKSynchronizer: Synchronizer {
private var transactionRepository: TransactionRepository
private var utxoRepository: UnspentTransactionOutputRepository
/**
Creates an SDKSynchronizer instance
- Parameter initializer: a wallet Initializer object
*/
/// Creates an SDKSynchronizer instance
/// - Parameter initializer: a wallet Initializer object
public convenience init(initializer: Initializer) throws {
try self.init(
status: .unprepared,
@ -183,10 +158,9 @@ public class SDKSynchronizer: Synchronizer {
self.status = .disconnected
}
/**
Starts the synchronizer
- Throws: CompactBlockProcessorError when failures occur
*/
/// Starts the synchronizer
/// - Throws: CompactBlockProcessorError when failures occur
public func start(retry: Bool = false) throws {
switch status {
case .unprepared:
@ -204,10 +178,8 @@ public class SDKSynchronizer: Synchronizer {
}
}
}
/**
Stops the synchronizer
*/
/// Stops the synchronizer
public func stop() {
guard status != .stopped, status != .disconnected else {
LoggerProxy.info("attempted to stop when status was: \(status)")
@ -482,7 +454,7 @@ public class SDKSynchronizer: Synchronizer {
// MARK: Synchronizer methods
// swiftlint:disable:next function_parameter_count
@available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead")
public func sendToAddress(
spendingKey: String,
zatoshi: Int64,
@ -490,6 +462,25 @@ public class SDKSynchronizer: Synchronizer {
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
sendToAddress(
spendingKey: spendingKey,
zatoshi: Zatoshi(zatoshi),
toAddress: toAddress,
memo: memo,
from: accountIndex,
resultBlock: resultBlock
)
}
// swiftlint:disable:next function_parameter_count
public func sendToAddress(
spendingKey: String,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
initializer.downloadParametersIfNeeded { downloadResult in
DispatchQueue.main.async { [weak self] in
@ -532,7 +523,7 @@ public class SDKSynchronizer: Synchronizer {
let viewingKey = try derivationTool.deriveViewingKey(spendingKey: spendingKey)
let zAddr = try derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
let shieldingSpend = try transactionManager.initSpend(zatoshi: Int(tBalance.verified), toAddress: zAddr, memo: memo, from: 0)
let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: zAddr, memo: memo, from: 0)
transactionManager.encodeShieldingTransaction(
spendingKey: spendingKey,
@ -567,7 +558,7 @@ public class SDKSynchronizer: Synchronizer {
// swiftlint:disable:next function_parameter_count
func createToAddress(
spendingKey: String,
zatoshi: Int64,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
from accountIndex: Int,
@ -575,7 +566,7 @@ public class SDKSynchronizer: Synchronizer {
) {
do {
let spend = try transactionManager.initSpend(
zatoshi: Int(zatoshi),
zatoshi: zatoshi,
toAddress: toAddress,
memo: memo,
from: accountIndex
@ -671,15 +662,24 @@ public class SDKSynchronizer: Synchronizer {
public func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result)
}
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
initializer.getBalance(account: accountIndex).amount
}
public func getShieldedBalance(accountIndex: Int = 0) -> Zatoshi {
initializer.getBalance(account: accountIndex)
}
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
public func getShieldedVerifiedBalance(accountIndex: Int = 0) -> Int64 {
initializer.getVerifiedBalance(account: accountIndex).amount
}
public func getShieldedVerifiedBalance(accountIndex: Int = 0) -> Zatoshi {
initializer.getVerifiedBalance(account: accountIndex)
}
public func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress? {
blockProcessor.getShieldedAddress(accountIndex: accountIndex)
}

View File

@ -8,7 +8,7 @@
import Foundation
enum TransactionManagerError: Error {
case couldNotCreateSpend(toAddress: String, account: Int, zatoshi: Int)
case couldNotCreateSpend(toAddress: String, account: Int, zatoshi: Zatoshi)
case encodingFailed(PendingTransactionEntity)
case updateFailed(PendingTransactionEntity)
case notPending(PendingTransactionEntity)
@ -39,7 +39,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
}
func initSpend(
zatoshi: Int,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
from accountIndex: Int
@ -149,7 +149,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
do {
let encodedTransaction = try self.encoder.createTransaction(
spendingKey: spendingKey,
zatoshi: pendingTransaction.value,
zatoshi: pendingTransaction.intValue,
to: pendingTransaction.toAddress,
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
from: pendingTransaction.accountIndex

View File

@ -14,7 +14,7 @@ transactions through to completion.
*/
protocol OutboundTransactionManager {
func initSpend(zatoshi: Int, toAddress: String, memo: String?, from accountIndex: Int) throws -> PendingTransactionEntity
func initSpend(zatoshi: Zatoshi, toAddress: String, memo: String?, from accountIndex: Int) throws -> PendingTransactionEntity
func encodeShieldingTransaction(spendingKey: String, tsk: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)

View File

@ -0,0 +1,32 @@
//
// Clamped.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/20/21.
//
// credits: https://iteo.medium.com/swift-property-wrappers-how-to-use-them-right-77095817d1b
import Foundation
/**
Limits a value to an enclosing range
*/
@propertyWrapper
public struct Clamped<Value: Comparable> {
private var value: Value
private let range: ClosedRange<Value>
public var wrappedValue: Value {
get { value }
set { value = clamp(newValue, using: range) }
}
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.value = wrappedValue
self.range = range
value = clamp(wrappedValue, using: range)
}
private func clamp(_ value: Value, using range: ClosedRange<Value>) -> Value {
min(range.upperBound, max(range.lowerBound, value))
}
}

View File

@ -18,7 +18,7 @@ class AdvancedReOrgTests: XCTestCase {
// TODO: Parameterize this from environment
let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
let sendAmount: Int64 = 1000
let sendAmount = Zatoshi(1000)
var birthday: BlockHeight = 663150
let defaultLatestHeight: BlockHeight = 663175
var coordinator: TestCoordinator!
@ -87,8 +87,8 @@ class AdvancedReOrgTests: XCTestCase {
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
var shouldContinue = false
let receivedTxHeight: BlockHeight = 663188
var initialTotalBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
var initialTotalBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1)
self.expectedReorgHeight = receivedTxHeight + 1
/*
@ -128,8 +128,8 @@ class AdvancedReOrgTests: XCTestCase {
3. sync up to received_Tx_height
*/
let receivedTxExpectation = XCTestExpectation(description: "received tx")
var receivedTxTotalBalance = Int64(-1)
var receivedTxVerifiedBalance = Int64(-1)
var receivedTxTotalBalance = Zatoshi(-1)
var receivedTxVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchro in
synchronizer = synchro
@ -153,7 +153,7 @@ class AdvancedReOrgTests: XCTestCase {
/*
3a. verify that balance is previous balance + tx amount
*/
XCTAssertEqual(receivedTxTotalBalance, initialTotalBalance + Int64(receivedTx.value))
XCTAssertEqual(receivedTxTotalBalance, initialTotalBalance + receivedTx.value)
XCTAssertEqual(receivedTxVerifiedBalance, initialVerifiedBalance)
/*
@ -192,8 +192,8 @@ class AdvancedReOrgTests: XCTestCase {
*/
let reorgSyncexpectation = XCTestExpectation(description: "reorg expectation")
var afterReorgTxTotalBalance = Int64(-1)
var afterReorgTxVerifiedBalance = Int64(-1)
var afterReorgTxTotalBalance = Zatoshi(-1)
var afterReorgTxVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in
afterReorgTxTotalBalance = synchronizer.initializer.getBalance()
@ -218,8 +218,8 @@ class AdvancedReOrgTests: XCTestCase {
*/
let finalsyncExpectation = XCTestExpectation(description: "final sync")
var finalReorgTxTotalBalance = Int64(-1)
var finalReorgTxVerifiedBalance = Int64(-1)
var finalReorgTxTotalBalance = Zatoshi(-1)
var finalReorgTxVerifiedBalance = Zatoshi(-1)
try coordinator.applyStaged(blockheight: reorgedTxheight + 1)
sleep(3)
@ -239,7 +239,7 @@ class AdvancedReOrgTests: XCTestCase {
XCTAssertEqual(reorgedTx.minedHeight, reorgedTxheight)
XCTAssertEqual(initialVerifiedBalance, finalReorgTxVerifiedBalance)
XCTAssertEqual(initialTotalBalance + Int64(receivedTx.value), finalReorgTxTotalBalance)
XCTAssertEqual(initialTotalBalance + receivedTx.value, finalReorgTxTotalBalance)
}
/**
@ -271,7 +271,7 @@ class AdvancedReOrgTests: XCTestCase {
func testReorgChangesOutboundTxIndex() throws {
try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service, branchID: branchID, chainName: chainName)
let receivedTxHeight: BlockHeight = 663188
var initialTotalBalance: Int64 = -1
var initialTotalBalance = Zatoshi(-1)
/*
2. applyStaged(received_Tx_height)
@ -294,7 +294,7 @@ class AdvancedReOrgTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "sendToAddress")
var pendingEntity: PendingTransactionEntity?
var error: Error?
let sendAmount: Int64 = 10000
let sendAmount = Zatoshi(10000)
/*
4. create transaction
@ -382,7 +382,7 @@ class AdvancedReOrgTests: XCTestCase {
*/
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
XCTAssertEqual(pMinedHeight, sentTxHeight)
XCTAssertEqual(initialTotalBalance - sendAmount - Int64(1000), synchronizer.initializer.getBalance()) // fee change on this branch
XCTAssertEqual(initialTotalBalance - sendAmount - Zatoshi(1000), synchronizer.initializer.getBalance()) // fee change on this branch
afterReOrgExpectation.fulfill()
},
error: self.handleError
@ -408,8 +408,10 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [lastSyncExpectation], timeout: 5)
XCTAssertEqual(coordinator.synchronizer.pendingTransactions.count, 0)
XCTAssertEqual(initialTotalBalance - Int64(pendingTx.value) - Int64(1000), coordinator.synchronizer.initializer.getVerifiedBalance())
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), coordinator.synchronizer.initializer.getVerifiedBalance())
XCTAssertEqual(initialTotalBalance - pendingTx.value - Zatoshi(1000), coordinator.synchronizer.initializer.getVerifiedBalance())
let resultingBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
XCTAssertEqual(resultingBalance, coordinator.synchronizer.initializer.getVerifiedBalance())
}
func testIncomingTransactionIndexChange() throws {
@ -422,8 +424,8 @@ class AdvancedReOrgTests: XCTestCase {
sleep(1)
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
var preReorgTotalBalance = Int64(0)
var preReorgVerifiedBalance = Int64(0)
var preReorgTotalBalance = Zatoshi.zero
var preReorgVerifiedBalance = Zatoshi.zero
try coordinator.sync(completion: { synchronizer in
preReorgTotalBalance = synchronizer.initializer.getBalance()
preReorgVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -442,8 +444,8 @@ class AdvancedReOrgTests: XCTestCase {
let afterReorgSync = XCTestExpectation(description: "after reorg sync")
var postReorgTotalBalance = Int64(0)
var postReorgVerifiedBalance = Int64(0)
var postReorgTotalBalance = Zatoshi.zero
var postReorgVerifiedBalance = Zatoshi.zero
try coordinator.sync(completion: { synchronizer in
postReorgTotalBalance = synchronizer.initializer.getBalance()
postReorgVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -462,8 +464,8 @@ class AdvancedReOrgTests: XCTestCase {
try coordinator.applyStaged(blockheight: receivedTxHeight - 1)
sleep(2)
let expectation = XCTestExpectation(description: "sync to \(receivedTxHeight - 1) expectation")
var initialBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
var initialBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in
initialBalance = synchronizer.initializer.getBalance()
initialVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -477,8 +479,8 @@ class AdvancedReOrgTests: XCTestCase {
sleep(2)
let afterTxSyncExpectation = XCTestExpectation(description: "sync to \(afterTxHeight) expectation")
var afterTxBalance: Int64 = -1
var afterTxVerifiedBalance: Int64 = -1
var afterTxBalance = Zatoshi(-1)
var afterTxVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in
afterTxBalance = synchronizer.initializer.getBalance()
afterTxVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -502,8 +504,8 @@ class AdvancedReOrgTests: XCTestCase {
sleep(2)
let afterReorgExpectation = XCTestExpectation(description: "after reorg expectation")
var afterReOrgBalance: Int64 = -1
var afterReOrgVerifiedBalance: Int64 = -1
var afterReOrgBalance = Zatoshi(-1)
var afterReOrgVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in
afterReOrgBalance = synchronizer.initializer.getBalance()
afterReOrgVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -543,8 +545,8 @@ class AdvancedReOrgTests: XCTestCase {
*/
let firstSyncExpectation = XCTestExpectation(description: "first sync test expectation")
var initialBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
var initialBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1)
var incomingTx: ConfirmedTransactionEntity?
try coordinator.sync(completion: { _ in
firstSyncExpectation.fulfill()
@ -622,8 +624,8 @@ class AdvancedReOrgTests: XCTestCase {
let finalHeight = BlockHeight(663200)
try coordinator.applyStaged(blockheight: txReorgHeight)
let firstSyncExpectation = XCTestExpectation(description: "first sync test expectation")
var initialBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
var initialBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in
initialBalance = synchronizer.initializer.getBalance()
initialVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
@ -696,7 +698,7 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5)
sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
@ -706,7 +708,7 @@ class AdvancedReOrgTests: XCTestCase {
*/
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: 20000,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
memo: "this is a test",
from: 0,
@ -877,12 +879,12 @@ class AdvancedReOrgTests: XCTestCase {
)
XCTAssertEqual(
initialTotalBalance - Int64(newlyPendingTx.value) - Int64(1000),
initialTotalBalance - newlyPendingTx.value - Zatoshi(1000),
coordinator.synchronizer.initializer.getBalance()
)
XCTAssertEqual(
initialTotalBalance - Int64(newlyPendingTx.value) - Int64(1000),
initialTotalBalance - newlyPendingTx.value - Zatoshi(1000),
coordinator.synchronizer.initializer.getVerifiedBalance()
)
}
@ -917,8 +919,8 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5)
let initialBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let initialBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
guard let initialTxHeight = try coordinator.synchronizer.allReceivedTransactions().first?.minedHeight else {
XCTFail("no incoming transaction found!")
return
@ -982,8 +984,8 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
try coordinator.applyStaged(blockheight: reorgHeight)
@ -1056,7 +1058,7 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5)
sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
@ -1066,7 +1068,7 @@ class AdvancedReOrgTests: XCTestCase {
*/
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: 20000,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
memo: "this is a test",
from: 0,

View File

@ -17,7 +17,7 @@ class BalanceTests: XCTestCase {
// TODO: Parameterize this from environment
let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
let sendAmount: Int64 = 1000
let sendAmount = Zatoshi(1000)
let defaultLatestHeight: BlockHeight = 663188
let branchID = "2bb40e60"
let chainName = "main"
@ -64,12 +64,12 @@ class BalanceTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 12)
// 2 check that there are no unconfirmed funds
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
let maxBalance = verifiedBalance - Int64(network.constants.defaultFee(for: defaultLatestHeight))
let maxBalance = verifiedBalance - network.constants.defaultFee(for: defaultLatestHeight)
// 3 create a transaction for the max amount possible
// 4 send the transaction
@ -179,8 +179,8 @@ class BalanceTests: XCTestCase {
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), 0)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), 0)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), .zero)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), .zero)
}
/**
@ -208,12 +208,12 @@ class BalanceTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 12)
// 2 check that there are no unconfirmed funds
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
let maxBalanceMinusOne = verifiedBalance - Int64(network.constants.defaultFee(for: defaultLatestHeight)) - 1
let maxBalanceMinusOne = verifiedBalance - network.constants.defaultFee(for: defaultLatestHeight) - Zatoshi(1)
// 3 create a transaction for the max amount possible
// 4 send the transaction
@ -321,8 +321,8 @@ class BalanceTests: XCTestCase {
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), 1)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), 1)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), Zatoshi(1))
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), Zatoshi(1))
}
/**
@ -350,12 +350,12 @@ class BalanceTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 12)
// 2 check that there are no unconfirmed funds
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
let maxBalanceMinusOne = 100000 - network.constants.defaultFee(for: defaultLatestHeight)
let maxBalanceMinusOne = Zatoshi(100000) - network.constants.defaultFee(for: defaultLatestHeight)
// 3 create a transaction for the max amount possible
// 4 send the transaction
@ -462,8 +462,8 @@ class BalanceTests: XCTestCase {
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), 100000)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), 100000)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), Zatoshi(100000))
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), Zatoshi(100000))
}
/**
@ -499,17 +499,17 @@ class BalanceTests: XCTestCase {
return
}
let presendVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let presendVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
/*
there's more zatoshi to send than network fee
*/
XCTAssertTrue(presendVerifiedBalance >= (Int64(network.constants.defaultFee(for: defaultLatestHeight)) + sendAmount))
XCTAssertTrue(presendVerifiedBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
var pendingTx: PendingTransactionEntity?
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: Int64(sendAmount),
zatoshi: sendAmount,
toAddress: testRecipientAddress,
memo: "test send \(self.description) \(Date().description)",
from: 0,
@ -529,7 +529,7 @@ class BalanceTests: XCTestCase {
}
)
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > 0)
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > .zero)
wait(for: [sentTransactionExpectation], timeout: 12)
// sync and mine
@ -579,7 +579,7 @@ class BalanceTests: XCTestCase {
/*
basic health check
*/
XCTAssertEqual(Int64(transaction.value), self.sendAmount)
XCTAssertEqual(transaction.value, self.sendAmount)
/*
build up repos to get data
@ -631,9 +631,9 @@ class BalanceTests: XCTestCase {
self.verifiedBalanceValidation(
previousBalance: presendVerifiedBalance,
spentNoteValue: Int64(sentNote.value),
changeValue: Int64(receivedNote.value),
sentAmount: Int64(self.sendAmount),
spentNoteValue: Zatoshi(Int64(sentNote.value)),
changeValue: Zatoshi(Int64(receivedNote.value)),
sentAmount: self.sendAmount,
currentVerifiedBalance: self.coordinator.synchronizer.initializer.getVerifiedBalance()
)
}
@ -671,16 +671,16 @@ class BalanceTests: XCTestCase {
return
}
let presendBalance = coordinator.synchronizer.initializer.getBalance()
let presendBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
// there's more zatoshi to send than network fee
XCTAssertTrue(presendBalance >= (Int64(network.constants.defaultFee(for: defaultLatestHeight)) + sendAmount))
XCTAssertTrue(presendBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
var pendingTx: PendingTransactionEntity?
var error: Error?
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: Int64(sendAmount),
zatoshi: sendAmount,
toAddress: testRecipientAddress,
memo: "test send \(self.description) \(Date().description)",
from: 0,
@ -698,7 +698,7 @@ class BalanceTests: XCTestCase {
}
)
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > 0)
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > .zero)
wait(for: [sentTransactionExpectation], timeout: 12)
if let e = error {
@ -711,11 +711,11 @@ class BalanceTests: XCTestCase {
return
}
XCTAssertEqual(Int64(transaction.value), self.sendAmount)
XCTAssertEqual(transaction.value, self.sendAmount)
XCTAssertEqual(
self.coordinator.synchronizer.initializer.getBalance(),
presendBalance - Int64(self.sendAmount) - network.constants.defaultFee(for: defaultLatestHeight)
presendBalance - self.sendAmount - network.constants.defaultFee(for: defaultLatestHeight)
)
XCTAssertNil(transaction.errorCode)
@ -747,7 +747,7 @@ class BalanceTests: XCTestCase {
wait(for: [mineExpectation], timeout: 5)
XCTAssertEqual(
presendBalance - self.sendAmount - Int64(network.constants.defaultFee(for: defaultLatestHeight)),
presendBalance - self.sendAmount - network.constants.defaultFee(for: defaultLatestHeight),
coordinator.synchronizer.initializer.getBalance()
)
}
@ -777,7 +777,7 @@ class BalanceTests: XCTestCase {
wait(for: [syncedExpectation], timeout: 5)
XCTAssertEqual(coordinator.synchronizer.clearedTransactions.count, 2)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), 200000)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), Zatoshi(200000))
}
/**
@ -820,8 +820,8 @@ class BalanceTests: XCTestCase {
wait(for: [syncedExpectation], timeout: 6)
let previousVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let previousTotalBalance = coordinator.synchronizer.initializer.getBalance()
let previousVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let previousTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
guard let spendingKeys = coordinator.spendingKeys?.first else {
XCTFail("null spending keys")
@ -835,7 +835,7 @@ class BalanceTests: XCTestCase {
var pendingTx: PendingTransactionEntity?
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKeys,
zatoshi: Int64(sendAmount),
zatoshi: sendAmount,
toAddress: testRecipientAddress,
memo: memo,
from: 0,
@ -890,7 +890,7 @@ class BalanceTests: XCTestCase {
/*
Theres a sent transaction matching the amount sent to the given zAddr
*/
XCTAssertEqual(Int64(confirmedTx.value), self.sendAmount)
XCTAssertEqual(confirmedTx.value, self.sendAmount)
XCTAssertEqual(confirmedTx.toAddress, self.testRecipientAddress)
XCTAssertEqual(confirmedTx.memo?.asZcashTransactionMemo(), memo)
@ -934,7 +934,7 @@ class BalanceTests: XCTestCase {
*/
XCTAssertEqual(
previousVerifiedBalance - self.sendAmount - self.network.constants.defaultFee(for: self.defaultLatestHeight),
Int64(receivedNote.value)
Zatoshi(Int64(receivedNote.value))
)
/*
@ -942,16 +942,16 @@ class BalanceTests: XCTestCase {
*/
self.verifiedBalanceValidation(
previousBalance: previousVerifiedBalance,
spentNoteValue: Int64(sentNote.value),
changeValue: Int64(receivedNote.value),
sentAmount: Int64(self.sendAmount),
spentNoteValue: Zatoshi(Int64(sentNote.value)),
changeValue: Zatoshi(Int64(receivedNote.value)),
sentAmount: self.sendAmount,
currentVerifiedBalance: synchronizer.initializer.getVerifiedBalance()
)
self.totalBalanceValidation(
totalBalance: synchronizer.initializer.getBalance(),
previousTotalbalance: previousTotalBalance,
sentAmount: Int64(self.sendAmount)
sentAmount: self.sendAmount
)
syncToMinedheightExpectation.fulfill()
@ -1001,8 +1001,8 @@ class BalanceTests: XCTestCase {
return
}
let previousVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let previousTotalBalance = coordinator.synchronizer.initializer.getBalance()
let previousVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let previousTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingTx: PendingTransactionEntity?
coordinator.synchronizer.sendToAddress(
@ -1089,21 +1089,21 @@ class BalanceTests: XCTestCase {
check if (previous available funds - spent note + change) equals to (previous available funds - sent amount)
*/
func verifiedBalanceValidation(
previousBalance: Int64,
spentNoteValue: Int64,
changeValue: Int64,
sentAmount: Int64,
currentVerifiedBalance: Int64
previousBalance: Zatoshi,
spentNoteValue: Zatoshi,
changeValue: Zatoshi,
sentAmount: Zatoshi,
currentVerifiedBalance: Zatoshi
) {
XCTAssertEqual(previousBalance - spentNoteValue + changeValue, currentVerifiedBalance - sentAmount)
}
func totalBalanceValidation(
totalBalance: Int64,
previousTotalbalance: Int64,
sentAmount: Int64
totalBalance: Zatoshi,
previousTotalbalance: Zatoshi,
sentAmount: Zatoshi
) {
XCTAssertEqual(totalBalance, previousTotalbalance - sentAmount - Int64(network.constants.defaultFee(for: defaultLatestHeight)))
XCTAssertEqual(totalBalance, previousTotalbalance - sentAmount - network.constants.defaultFee(for: defaultLatestHeight))
}
}

View File

@ -18,7 +18,7 @@ class NetworkUpgradeTests: XCTestCase {
// TODO: Parameterize this from environment
let testRecipientAddress = "ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"
let sendAmount: Int64 = 1000
let sendAmount = Zatoshi(1000)
let branchID = "2bb40e60"
let chainName = "main"
@ -63,7 +63,7 @@ class NetworkUpgradeTests: XCTestCase {
}, error: self.handleError)
wait(for: [firstSyncExpectation], timeout: 120)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
guard verifiedBalance > network.constants.defaultFee(for: activationHeight) else {
XCTFail("not enough balance to continue test")
return
@ -74,7 +74,7 @@ class NetworkUpgradeTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
let spendAmount: Int64 = 10000
let spendAmount = Zatoshi(10000)
/*
send transaction to recipient address
@ -163,7 +163,7 @@ class NetworkUpgradeTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
let spendAmount: Int64 = 10000
let spendAmount = Zatoshi(10000)
/*
send transaction to recipient address
@ -227,12 +227,12 @@ class NetworkUpgradeTests: XCTestCase {
}, error: self.handleError)
wait(for: [firstSyncExpectation], timeout: 120)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: activationHeight))
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
let spendAmount: Int64 = 10000
let spendAmount = Zatoshi(10000)
/*
send transaction to recipient address
@ -318,14 +318,14 @@ class NetworkUpgradeTests: XCTestCase {
try coordinator.applyStaged(blockheight: activationHeight - 10)
sleep(3)
let verifiedBalancePreActivation = coordinator.synchronizer.initializer.getVerifiedBalance()
let verifiedBalancePreActivation: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
try coordinator.sync(completion: { _ in
firstSyncExpectation.fulfill()
}, error: self.handleError)
wait(for: [firstSyncExpectation], timeout: 120)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
guard verifiedBalance > network.constants.defaultFee(for: activationHeight) else {
XCTFail("balance is not enough to continue with this test")
return
@ -333,7 +333,7 @@ class NetworkUpgradeTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
let spendAmount: Int64 = 10000
let spendAmount = Zatoshi(10000)
/*
send transaction to recipient address
@ -421,7 +421,7 @@ class NetworkUpgradeTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 120)
let preActivationBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let preActivationBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
try coordinator.applyStaged(blockheight: activationHeight + 30)
sleep(2)
@ -436,14 +436,16 @@ class NetworkUpgradeTests: XCTestCase {
XCTFail("this test requires funds received after activation height")
return
}
let postActivationBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let postActivationBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
XCTAssertTrue(preActivationBalance < postActivationBalance, "This test requires that funds post activation are greater that pre activation")
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity?
// spend all the funds
let spendAmount: Int64 = postActivationBalance - Int64(network.constants.defaultFee(for: activationHeight))
let spendAmount = Zatoshi(
postActivationBalance.amount - Int64(network.constants.defaultFee(for: activationHeight).amount)
)
/*
send transaction to recipient address
@ -473,7 +475,7 @@ class NetworkUpgradeTests: XCTestCase {
return
}
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), 0)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), .zero)
}
func handleError(_ error: Error?) {

View File

@ -96,7 +96,7 @@ class PendingTransactionUpdatesTest: XCTestCase {
coordinator.synchronizer.sendToAddress(
// swiftlint:disable:next force_unwrapping
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: 20000,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
memo: "this is a test",
from: 0,

View File

@ -69,8 +69,8 @@ class RewindRescanTests: XCTestCase {
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
try coordinator.applyStaged(blockheight: defaultLatestHeight + 50)
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
sleep(1)
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
@ -79,8 +79,8 @@ class RewindRescanTests: XCTestCase {
}, error: handleError)
wait(for: [firstSyncExpectation], timeout: 12)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
// 2 check that there are no unconfirmed funds
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
@ -118,7 +118,7 @@ class RewindRescanTests: XCTestCase {
let newChaintTip = defaultLatestHeight + 10000
try coordinator.applyStaged(blockheight: newChaintTip)
sleep(3)
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let initialVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
try coordinator.sync(
@ -129,8 +129,8 @@ class RewindRescanTests: XCTestCase {
)
wait(for: [firstSyncExpectation], timeout: 20)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
// 2 check that there are no unconfirmed funds
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
@ -168,7 +168,7 @@ class RewindRescanTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "after rewind expectation")
coordinator.synchronizer.sendToAddress(
spendingKey: coordinator.spendingKey,
zatoshi: 1000,
zatoshi: Zatoshi(1000),
toAddress: testRecipientAddress,
memo: nil,
from: 0
@ -176,7 +176,7 @@ class RewindRescanTests: XCTestCase {
sendExpectation.fulfill()
switch result {
case .success(let pendingTx):
XCTAssertEqual(1000, pendingTx.value)
XCTAssertEqual(Zatoshi(1000), pendingTx.value)
case .failure(let error):
XCTFail("sending fail: \(error)")
}
@ -198,8 +198,8 @@ class RewindRescanTests: XCTestCase {
}, error: handleError)
wait(for: [firstSyncExpectation], timeout: 12)
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
// 2 check that there are no unconfirmed funds
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
@ -253,12 +253,12 @@ class RewindRescanTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 12)
// 2 check that there are no unconfirmed funds
let verifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance = coordinator.synchronizer.initializer.getBalance()
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
XCTAssertEqual(verifiedBalance, totalBalance)
let maxBalance = verifiedBalance - Int64(network.constants.defaultFee(for: defaultLatestHeight))
let maxBalance = verifiedBalance - network.constants.defaultFee(for: defaultLatestHeight)
// 3 create a transaction for the max amount possible
// 4 send the transaction
@ -390,7 +390,7 @@ class RewindRescanTests: XCTestCase {
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), 0)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), 0)
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), .zero)
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), .zero)
}
}

View File

@ -17,7 +17,7 @@ class ShieldFundsTests: XCTestCase {
// TODO: Parameterize this from environment
let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
let sendAmount: Int64 = 1000
let sendAmount = Zatoshi(1000)
var birthday: BlockHeight = 1631000
var coordinator: TestCoordinator!
var syncedExpectation = XCTestExpectation(description: "synced")
@ -90,8 +90,8 @@ class ShieldFundsTests: XCTestCase {
let utxoHeight = BlockHeight(1631177)
var shouldContinue = false
var initialTotalBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
var initialTotalBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1)
var initialTransparentBalance: WalletBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
let utxo = try GetAddressUtxosReply(jsonString: """
@ -128,12 +128,12 @@ class ShieldFundsTests: XCTestCase {
}
// at this point the balance should be all zeroes for transparent and shielded funds
XCTAssertEqual(initialTotalBalance, 0)
XCTAssertEqual(initialVerifiedBalance, 0)
XCTAssertEqual(initialTotalBalance, Zatoshi.zero)
XCTAssertEqual(initialVerifiedBalance, Zatoshi.zero)
initialTransparentBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(initialTransparentBalance.total, 0)
XCTAssertEqual(initialTransparentBalance.verified, 0)
XCTAssertEqual(initialTransparentBalance.total, .zero)
XCTAssertEqual(initialTransparentBalance.verified, .zero)
// 4. Add the UTXO to darksidewalletd fake chain
try coordinator.service.addUTXO(utxo)
@ -163,8 +163,8 @@ class ShieldFundsTests: XCTestCase {
// and 10000 zatoshi of total (not verified) transparent funds.
let tFundsDetectedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(tFundsDetectedBalance.total, 10000)
XCTAssertEqual(tFundsDetectedBalance.verified, 10000) //FIXME: this should be zero
XCTAssertEqual(tFundsDetectedBalance.total, Zatoshi(10000))
XCTAssertEqual(tFundsDetectedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero
let tFundsConfirmationSyncExpectation = XCTestExpectation(description: "t funds confirmation")
@ -189,8 +189,8 @@ class ShieldFundsTests: XCTestCase {
// the transparent funds should be 10000 zatoshis both total and verified
let confirmedTFundsBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(confirmedTFundsBalance.total, 10000)
XCTAssertEqual(confirmedTFundsBalance.verified, 10000)
XCTAssertEqual(confirmedTFundsBalance.total, Zatoshi(10000))
XCTAssertEqual(confirmedTFundsBalance.verified, Zatoshi(10000))
// 9. shield the funds
let shieldFundsExpectation = XCTestExpectation(description: "shield funds")
@ -221,7 +221,7 @@ class ShieldFundsTests: XCTestCase {
case .success(let pendingTx):
shouldContinue = true
XCTAssertEqual(pendingTx.value, 10000)
XCTAssertEqual(pendingTx.value, Zatoshi(10000))
shieldingPendingTx = pendingTx
}
shieldFundsExpectation.fulfill()
@ -235,9 +235,9 @@ class ShieldFundsTests: XCTestCase {
// when funds are shielded the UTXOs should be marked as spend and not shown on the balance.
// now balance should be zero shielded, zero transaparent.
// verify that the balance has been marked as spent regardless of confirmation
XCTAssertEqual(postShieldingBalance.verified, 10000) //FIXME: this should be zero
XCTAssertEqual(postShieldingBalance.total, 10000) //FIXME: this should be zero
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), 0)
XCTAssertEqual(postShieldingBalance.verified, Zatoshi(10000)) //FIXME: this should be zero
XCTAssertEqual(postShieldingBalance.total, Zatoshi(10000)) //FIXME: this should be zero
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), .zero)
// 10. clear the UTXO from darksidewalletd's cache
try coordinator.service.clearAddedUTXOs()
@ -281,9 +281,9 @@ class ShieldFundsTests: XCTestCase {
// Fees at the time of writing the tests are 1000 zatoshi as defined on ZIP-313
let postShieldingShieldedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(postShieldingShieldedBalance.total, 10000) //FIXME: this should be zero
XCTAssertEqual(postShieldingShieldedBalance.verified, 10000) //FIXME: this should be zero
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), 0) //FIXME: this should be 9000
XCTAssertEqual(postShieldingShieldedBalance.total, Zatoshi(10000)) //FIXME: this should be zero
XCTAssertEqual(postShieldingShieldedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), .zero) //FIXME: this should be 9000
// 14. proceed confirm the shielded funds by staging ten more blocks
try coordinator.service.applyStaged(nextLatestHeight: utxoHeight + 10 + 1 + 10)
@ -311,10 +311,10 @@ class ShieldFundsTests: XCTestCase {
XCTAssertNotNil(clearedTransaction)
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), 9000)
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), Zatoshi(9000))
let postShieldingConfirmationShieldedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.total, 0)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.verified, 0)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.total, .zero)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.verified, .zero)
}

View File

@ -97,7 +97,7 @@ class Z2TReceiveTests: XCTestCase {
let sendExpectation = XCTestExpectation(description: "sendToAddress")
var pendingEntity: PendingTransactionEntity?
var error: Error?
let sendAmount: Int64 = 10000
let sendAmount = Zatoshi(10000)
/*
4. create transaction
*/

View File

@ -125,7 +125,7 @@ class PendingTransactionRepositoryTests: XCTestCase {
func testUpdate() {
let newAccountIndex = 1
let newValue: Int = 123_456
let newValue = Zatoshi(123_456)
let transaction = createAndStoreMockedTransaction()
guard let id = transaction.id else {
@ -174,6 +174,11 @@ class PendingTransactionRepositoryTests: XCTestCase {
}
private func mockTransaction() -> PendingTransactionEntity {
PendingTransaction(value: Int.random(in: 1 ... 1_000_000), toAddress: recipientAddress, memo: nil, account: 0)
PendingTransaction(
value: Zatoshi(Int64.random(in: 1 ... 1_000_000)),
toAddress: recipientAddress,
memo: nil,
account: 0
)
}
}

View File

@ -0,0 +1,183 @@
//
// ZatoshiTests.swift
// secantTests
//
// Created by Lukáš Korba on 26.05.2022.
//
import XCTest
@testable import ZcashLightClientKit
class ZatoshiTests: XCTestCase {
let usNumberFormatter = NumberFormatter()
override func setUp() {
super.setUp()
usNumberFormatter.maximumFractionDigits = 8
usNumberFormatter.maximumIntegerDigits = 8
usNumberFormatter.numberStyle = .decimal
usNumberFormatter.usesGroupingSeparator = true
usNumberFormatter.locale = Locale(identifier: "en_US")
}
func testLowerBound() throws {
let number = Zatoshi(-Zatoshi.Constants.maxZatoshi - 1)
XCTAssertEqual(
-Zatoshi.Constants.maxZatoshi,
number.amount,
"Zatoshi tests: `testLowerBound` the value is expected to be clamped to lower bound but it's \(number.amount)"
)
}
func testUpperBound() throws {
let number = Zatoshi(Zatoshi.Constants.maxZatoshi + 1)
XCTAssertEqual(
Zatoshi.Constants.maxZatoshi,
number.amount,
"Zatoshi tests: `testUpperBound` the value is expected to be clamped to upper bound but it's \(number.amount)"
)
}
func testAddingZatoshi() throws {
let numberA1 = Zatoshi(100_000)
let numberB1 = Zatoshi(200_000)
let result1 = numberA1 + numberB1
XCTAssertEqual(
result1.amount,
Zatoshi(300_000).amount,
"Zatoshi tests: `testAddingZatoshi` the value is expected to be 300_000 but it's \(result1.amount)"
)
let numberA2 = Zatoshi(-100_000)
let numberB2 = Zatoshi(200_000)
let result2 = numberA2 + numberB2
XCTAssertEqual(
result2.amount,
Zatoshi(100_000).amount,
"Zatoshi tests: `testAddingZatoshi` the value is expected to be 100_000 but it's \(result2.amount)"
)
let numberA3 = Zatoshi(100_000)
let numberB3 = Zatoshi(-200_000)
let result3 = numberA3 + numberB3
XCTAssertEqual(
result3.amount,
Zatoshi(-100_000).amount,
"Zatoshi tests: `testAddingZatoshi` the value is expected to be -100_000 but it's \(result3.amount)"
)
let number = Zatoshi(Zatoshi.Constants.maxZatoshi)
let result4 = number + number
XCTAssertEqual(
result4.amount,
Zatoshi.Constants.maxZatoshi,
"Zatoshi tests: `testAddingZatoshi` the value is expected to be clapmed to upper bound but it's \(result4.amount)"
)
}
func testSubtractingZatoshi() throws {
let numberA1 = Zatoshi(100_000)
let numberB1 = Zatoshi(200_000)
let result1 = numberA1 - numberB1
XCTAssertEqual(
result1.amount,
Zatoshi(-100_000).amount,
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be -100_000 but it's \(result1.amount)"
)
let numberA2 = Zatoshi(-100_000)
let numberB2 = Zatoshi(200_000)
let result2 = numberA2 - numberB2
XCTAssertEqual(
result2.amount,
Zatoshi(-300_000).amount,
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be -300_000 but it's \(result2.amount)"
)
let numberA3 = Zatoshi(100_000)
let numberB3 = Zatoshi(-200_000)
let result3 = numberA3 - numberB3
XCTAssertEqual(
result3.amount,
Zatoshi(300_000).amount,
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be 300_000 but it's \(result3.amount)"
)
let number = Zatoshi(-Zatoshi.Constants.maxZatoshi)
let result4 = number + number
XCTAssertEqual(
result4.amount,
-Zatoshi.Constants.maxZatoshi,
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be clapmed to lower bound but it's \(result4.amount)"
)
}
func testHumanReadable() throws {
// result of this division is 1.4285714285714285714285714285714285714
let number = Zatoshi.from(decimal: Decimal(200.0 / 140.0))
// IMPORTANT: the INTERNAL value of number is still 1.4285714285714285714285714285714285714!!!
// but decimalString is rounding it to maximumFractionDigits set to be 8
// We can't compare it to double value 1.42857143 (or even Decimal(1.42857143))
// so we convert it to string, in that case we are prooving it to be rendered
// to the user exactly the way we want
XCTAssertEqual(
number.decimalString(formatter: usNumberFormatter),
"1.42857143",
"Zatoshi tests: the value is expected to be 1.42857143 but it's \(number.decimalString())"
)
}
func testUSDtoZecToUSD() throws {
// The price of zec is $140, we want to send $200
let usd2zec = NSDecimalNumber(decimal: 200.0 / 140.0)
XCTAssertEqual(
usd2zec.decimalString,
"1.42857143",
"Zatoshi tests: `testUSDtoZatoshiToUSD` the value is expected to be 1.42857143 but it's \(usd2zec.decimalString)"
)
// convert it back
let zec2usd = NSDecimalNumber(decimal: usd2zec.decimalValue * 140.0)
XCTAssertEqual(
zec2usd.decimalString,
"200",
"Zatoshi tests: `testUSDtoZatoshiToUSD` the value is expected to be 200 but it's \(zec2usd.decimalString)"
)
}
func testStringToZatoshi() throws {
if let number = Zatoshi.from(decimalString: "200.0", formatter: usNumberFormatter) {
XCTAssertEqual(
number.decimalString(formatter: usNumberFormatter),
"200",
"Zatoshi tests: `testStringToZec` the value is expected to be 200 but it's \(number.decimalString())"
)
} else {
XCTFail("Zatoshi tests: `testStringToZatoshi` failed to convert number.")
}
if let number = Zatoshi.from(decimalString: "0.02836478949923", formatter: usNumberFormatter) {
XCTAssertEqual(
number.amount,
2_836_479,
"Zatoshi tests: the value is expected to be 2_836_478 but it's \(number.amount)"
)
} else {
XCTFail("Zatoshi tests: `testStringToZatoshi` failed to convert number.")
}
}
}

View File

@ -80,7 +80,7 @@ class MockTransactionRepository {
transactionIndex: index,
raw: Data(),
id: index,
value: Int.random(in: 1 ... ZcashSDK.zatoshiPerZEC),
value: Zatoshi(Int64.random(in: 1 ... Zatoshi.Constants.oneZecInZatoshi)),
memo: nil,
rawTransactionId: Data()
)
@ -96,7 +96,7 @@ class MockTransactionRepository {
transactionIndex: index,
raw: Data(),
id: index,
value: Int.random(in: 1 ... ZcashSDK.zatoshiPerZEC),
value: Zatoshi(Int64.random(in: 1 ... Zatoshi.Constants.oneZecInZatoshi)),
memo: nil,
rawTransactionId: Data()
)