From 73af6c90393622520387f32811aff991dfa2d011 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:45:27 +0100 Subject: [PATCH 1/4] Wrap SOME of Radix Engine Toolkit types, first of MANY PRs. (#3) --- .pre-commit-config.yaml | 9 + .tarpaulin.toml | 12 +- Cargo.lock | 17 + Cargo.toml | 9 +- Package.swift | 2 +- _typos.toml | 7 + .../Methods/AppearanceID+Wrap+Functions.swift | 5 + .../Methods/BagOfBytes+Wrap+Functions.swift | 5 + .../Methods/Decimal192+Methods.swift | 1 - .../Methods/Decimal192+Wrap+Functions.swift | 113 + .../Methods/DisplayName+Wrap+Functions.swift | 5 + .../Methods/Mnemonic+Wrap+Functions.swift | 5 + ... => SecureStorageKey+Wrap+Functions.swift} | 0 .../Swiftified/AppearanceID+Swiftified.swift | 12 +- .../Swiftified/BagOfBytes+Random.swift | 3 - .../Swiftified/Decimal192+Swiftified.swift | 64 +- .../Swiftified/DisplayName+Swiftified.swift | 5 - .../Swiftified/LocaleConfig+Swiftified.swift | 9 + .../Swiftified/Mnemonic+Swiftified.swift | 1 + apple/Sources/UniFFI/Sargon.swift | 1962 ++++++++++++++++- .../contents.xcworkspacedata | 24 +- .../Sources/Planbok/Features/AppFeature.swift | 2 +- .../CreateAccountFlowFeature.swift | 110 + .../Steps/NameAccountFeature.swift} | 61 +- .../Steps/SelectGradientFeature.swift | 105 + .../Flows/Onboarding/OnboardingFeature.swift | 119 + .../Flows/Onboarding/WelcomeFeature.swift | 70 + .../Onboarding/WriteDownMnemonicFeature.swift | 84 + .../Planbok/Features/MainFeature.swift | 86 +- .../Planbok/Features/OnboardingFeature.swift | 64 - .../Planbok/View/Views/AccountView.swift | 1 - .../Planbok/View/Views/LabeledTextField.swift | 14 + scripts/ios/ensure-not-local.sh | 11 + scripts/ios/release.sh | 3 +- src/core/error/common_error.rs | 7 +- src/core/secure_random_bytes.rs | 7 + src/core/types/bag_of_bytes.rs | 6 +- src/core/types/decimal.rs | 268 --- src/core/types/decimal192.rs | 1156 ++++++++++ src/core/types/epoch.rs | 62 + src/core/types/hex_32bytes.rs | 795 +++++-- src/core/types/keys/ed25519/private_key.rs | 21 +- src/core/types/keys/ed25519/public_key.rs | 10 +- src/core/types/keys/secp256k1/private_key.rs | 11 +- src/core/types/keys/secp256k1/public_key.rs | 14 +- src/core/types/locale_config.rs | 102 + src/core/types/mod.rs | 14 +- src/core/types/nonce.rs | 79 + src/core/types/rounding_mode.rs | 100 + .../types/signatures/ed25519_signature.rs | 178 ++ src/core/types/signatures/mod.rs | 9 + .../types/signatures/secp256k1_signature.rs | 179 ++ src/core/types/signatures/signature.rs | 70 + .../signatures/signature_with_public_key.rs | 42 + .../cap26/cap26_path/paths/is_entity_path.rs | 2 +- src/lib.rs | 3 + .../v100/app_preferences/gateways/gateways.rs | 2 +- .../app_preferences/p2p_links/p2p_link.rs | 2 - .../p2p_links/radix_connect_password.rs | 2 - .../transaction_preferences.rs | 8 +- .../persona_data_entry_email_address.rs | 3 +- .../entry_kinds/persona_data_entry_name.rs | 7 +- .../persona_data_entry_phone_number.rs | 3 +- .../factors/factor_source_id_from_hash.rs | 2 - .../authorized_dapp/requested_quantity.rs | 14 +- .../authorized_dapp/shared_with_dapp.rs | 2 +- src/sargon.udl | 3 + src/wallet/mod.rs | 2 + src/wallet/wallet_accounts.rs | 43 - src/wallet/wallet_device_factor_sources.rs | 127 ++ .../dummy_types.rs | 28 + .../execution_summary.rs | 6 + .../manifest_summary.rs | 21 + src/wrapped_radix_engine_toolkit/message.rs | 16 + src/wrapped_radix_engine_toolkit/mod.rs | 23 + .../notarized_transaction.rs | 19 + .../signed_intent.rs | 39 + .../transaction_hash.rs | 7 + .../transaction_header.rs | 45 + .../transaction_intent.rs | 22 + .../transaction_manifest.rs | 40 + tests/uniffi/bindings/test_decimal192.swift | 59 + tests/uniffi/main.rs | 4 + 83 files changed, 5908 insertions(+), 776 deletions(-) create mode 100644 apple/Sources/Sargon/Extensions/Methods/AppearanceID+Wrap+Functions.swift create mode 100644 apple/Sources/Sargon/Extensions/Methods/BagOfBytes+Wrap+Functions.swift delete mode 100644 apple/Sources/Sargon/Extensions/Methods/Decimal192+Methods.swift create mode 100644 apple/Sources/Sargon/Extensions/Methods/Decimal192+Wrap+Functions.swift create mode 100644 apple/Sources/Sargon/Extensions/Methods/DisplayName+Wrap+Functions.swift create mode 100644 apple/Sources/Sargon/Extensions/Methods/Mnemonic+Wrap+Functions.swift rename apple/Sources/Sargon/Extensions/Methods/{SecureStorageKey+Methods.swift => SecureStorageKey+Wrap+Functions.swift} (100%) create mode 100644 apple/Sources/Sargon/Extensions/Swiftified/LocaleConfig+Swiftified.swift create mode 100644 apple/Sources/Sargon/Extensions/Swiftified/Mnemonic+Swiftified.swift create mode 100644 examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/CreateAccountFlowFeature.swift rename examples/iOS/Sources/Planbok/Features/{CreateAccountFeature.swift => Flows/CreateAccount/Steps/NameAccountFeature.swift} (53%) create mode 100644 examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift create mode 100644 examples/iOS/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift create mode 100644 examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift create mode 100644 examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift delete mode 100644 examples/iOS/Sources/Planbok/Features/OnboardingFeature.swift create mode 100644 examples/iOS/Sources/Planbok/View/Views/LabeledTextField.swift create mode 100755 scripts/ios/ensure-not-local.sh delete mode 100644 src/core/types/decimal.rs create mode 100644 src/core/types/decimal192.rs create mode 100644 src/core/types/epoch.rs create mode 100644 src/core/types/locale_config.rs create mode 100644 src/core/types/nonce.rs create mode 100644 src/core/types/rounding_mode.rs create mode 100644 src/core/types/signatures/ed25519_signature.rs create mode 100644 src/core/types/signatures/mod.rs create mode 100644 src/core/types/signatures/secp256k1_signature.rs create mode 100644 src/core/types/signatures/signature.rs create mode 100644 src/core/types/signatures/signature_with_public_key.rs create mode 100644 src/wallet/wallet_device_factor_sources.rs create mode 100644 src/wrapped_radix_engine_toolkit/dummy_types.rs create mode 100644 src/wrapped_radix_engine_toolkit/execution_summary.rs create mode 100644 src/wrapped_radix_engine_toolkit/manifest_summary.rs create mode 100644 src/wrapped_radix_engine_toolkit/message.rs create mode 100644 src/wrapped_radix_engine_toolkit/mod.rs create mode 100644 src/wrapped_radix_engine_toolkit/notarized_transaction.rs create mode 100644 src/wrapped_radix_engine_toolkit/signed_intent.rs create mode 100644 src/wrapped_radix_engine_toolkit/transaction_hash.rs create mode 100644 src/wrapped_radix_engine_toolkit/transaction_header.rs create mode 100644 src/wrapped_radix_engine_toolkit/transaction_intent.rs create mode 100644 src/wrapped_radix_engine_toolkit/transaction_manifest.rs create mode 100644 tests/uniffi/bindings/test_decimal192.swift diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fb01d451..d7bd5dfba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,5 @@ +fail_fast: true + repos: - repo: https://github.com/crate-ci/typos rev: v1.17.2 @@ -5,6 +7,13 @@ repos: - id: typos - repo: local hooks: + - id: ensure Swift not changed to local development + name: ensure Swift not changed to local development + language: system + types: [file, swift] + entry: ./scripts/ios/ensure-not-local.sh + pass_filenames: false + - id: fmt name: fmt language: system diff --git a/.tarpaulin.toml b/.tarpaulin.toml index f68845a44..e08d56888 100644 --- a/.tarpaulin.toml +++ b/.tarpaulin.toml @@ -1,9 +1,17 @@ [all] exclude-files = [ "tests/*", + "src/wrapped_radix_engine_toolkit/*", + "example/*", + "target/*", + "apple/*", + ".swiftpm/*", + "scripts/*", + ".build/*", + "Package.swift", ] verbose = false all-features = true -timeout = "1800s" +timeout = "5m" +skip-clean = true out = ["Xml"] -force-clean = false diff --git a/Cargo.lock b/Cargo.lock index fa325ad0f..93529b44c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,12 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "askama" version = "0.12.1" @@ -1393,6 +1399,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1973,6 +1989,7 @@ dependencies = [ "itertools 0.12.1", "log", "memoize", + "num-format", "nutype", "pretty_assertions", "pretty_env_logger", diff --git a/Cargo.toml b/Cargo.toml index b5a2969be..8a2ba741d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,11 @@ path = "src/bin.rs" [dependencies] log = "0.4.20" pretty_env_logger = "0.5.0" -derive_more = { version = "1.0.0-beta.6", features = ["debug", "display"] } +derive_more = { version = "1.0.0-beta.6", features = [ + "debug", + "display", + "from_str", +] } serde = { version = "1.0.193", features = ["derive", "rc", "std"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } serde_with = { version = "3.4.0" } @@ -46,7 +50,7 @@ radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", radix-engine-toolkit-json = { git = "https://github.com/radixdlt/radix-engine-toolkit", rev = "9dc3deeb3299b4f4c255f36bb3d504681379ad5a" } radix-engine-toolkit = { git = "https://github.com/radixdlt/radix-engine-toolkit", rev = "9dc3deeb3299b4f4c255f36bb3d504681379ad5a" } enum-iterator = "1.4.1" -bip32 = "0.5.1" # only need Secp256k1, to do validation of PublicKey +bip32 = "0.5.1" # only need Secp256k1, to do validation of PublicKey ed25519-dalek = "1.0.1" rand = "0.8.5" hex = "0.4.3" @@ -64,6 +68,7 @@ bip39 = { version = "2.0.0", features = ["serde"] } time-util = { version = "0.3.4", features = ["chrono"] } assert-json-diff = "2.0.2" url = { version = "2.5.0", features = ["serde"] } +num-format = "0.4.4" [build-dependencies] uniffi = { version = "0.26.0", features = ["build"] } diff --git a/Package.swift b/Package.swift index 73aa60cbb..53499e635 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let sargonBinaryTargetName = "SargonCoreRS" let binaryTarget: Target -let useLocalFramework = true +let useLocalFramework = false if useLocalFramework { binaryTarget = .binaryTarget( diff --git a/_typos.toml b/_typos.toml index 944fcbb52..8368950e7 100644 --- a/_typos.toml +++ b/_typos.toml @@ -5,3 +5,10 @@ extend-exclude = [ "tarpaulin-report.html", "tests/vectors/fixtures/*.json", ] + + +[default.extend-identifiers] +inout = "inout" + +[default.extend-words] +inout = "inout" \ No newline at end of file diff --git a/apple/Sources/Sargon/Extensions/Methods/AppearanceID+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/AppearanceID+Wrap+Functions.swift new file mode 100644 index 000000000..c2295d447 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/AppearanceID+Wrap+Functions.swift @@ -0,0 +1,5 @@ +extension AppearanceID: CaseIterable { + public static var allCases: [Self] { + appearanceIdsAll() + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/BagOfBytes+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/BagOfBytes+Wrap+Functions.swift new file mode 100644 index 000000000..89f567b45 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/BagOfBytes+Wrap+Functions.swift @@ -0,0 +1,5 @@ +extension BagOfBytes { + public init(data: Data) { + self = newBagOfBytesFrom(bytes: data) + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/Decimal192+Methods.swift b/apple/Sources/Sargon/Extensions/Methods/Decimal192+Methods.swift deleted file mode 100644 index 10afa8dd7..000000000 --- a/apple/Sources/Sargon/Extensions/Methods/Decimal192+Methods.swift +++ /dev/null @@ -1 +0,0 @@ -//extension Decimal192: Comparable {} diff --git a/apple/Sources/Sargon/Extensions/Methods/Decimal192+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Decimal192+Wrap+Functions.swift new file mode 100644 index 000000000..059004e81 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/Decimal192+Wrap+Functions.swift @@ -0,0 +1,113 @@ +extension Decimal192 { + public init(_ string: String) throws { + self = try newDecimalFromString(string: string) + } +} + +extension Decimal192: CustomStringConvertible { + public var description: String { + decimalToString(decimal: self) + } +} + +extension Decimal192 { + public static let maxDivisibility: UInt8 = 18 +} + +extension Decimal192 { + /// Parse a local respecting string + public init( + formattedString: String, + locale: Locale = .autoupdatingCurrent + ) throws { + let localConfig: LocaleConfig = LocaleConfig(locale: locale) + self = try newDecimalFromFormattedString( + formattedString: formattedString, + locale: localConfig + ) + } +} + +// MARK: Truncation and rounding + +extension Decimal192 { + + private func rounded(decimalPlaces: UInt8, roundingMode: RoundingMode) -> Self { + precondition( + decimalPlaces <= Decimal192.maxDivisibility, + "Decimal places MUST be 0...18, was: \(decimalPlaces)" + ) + do { + return try decimalRound( + decimal: self, + decimalPlaces: Int32(decimalPlaces), + roundingMode: roundingMode + ) + } catch { + fatalError("Failed to round, error: \(error)") + } + } + + + /// Rounds to `decimalPlaces` decimals + public func rounded(decimalPlaces: UInt8 = 0) -> Self { + rounded( + decimalPlaces: decimalPlaces, + roundingMode: .toNearestMidpointAwayFromZero + ) + } + + /// Rounds to `decimalPlaces` decimals, in the direction of 0 + public func floor(decimalPlaces: UInt8) -> Self { + rounded(decimalPlaces: decimalPlaces, roundingMode: .toZero) + } + + /// Rounds to `decimalPlaces` decimals, in the direction away from zero + public func ceil(decimalPlaces: UInt8) -> Self { + rounded(decimalPlaces: decimalPlaces, roundingMode: .awayFromZero) + } + +} + +extension Decimal192 { + public var clamped: Self { + decimalClampedToZero(decimal: self) + } + + public func isNegative() -> Bool { + decimalIsNegative(decimal: self) + } +} + +extension Decimal192: Comparable { + public static func > (lhs: Self, rhs: Self) -> Bool { + lhs.greaterThan(other: rhs) + } + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs.lessThan(other: rhs) + } + public static func >= (lhs: Self, rhs: Self) -> Bool { + lhs.greaterThanOrEqual(other: rhs) + } + public static func <= (lhs: Self, rhs: Self) -> Bool { + lhs.lessThanOrEqual(other: rhs) + } +} +extension Decimal192 { + + public func lessThan(other: Self) -> Bool { + decimalLessThan(lhs: self, rhs: other) + } + + public func lessThanOrEqual(other: Self) -> Bool { + decimalLessThanOrEqual(lhs: self, rhs: other) + } + + public func greaterThan(other: Self) -> Bool { + decimalGreaterThan(lhs: self, rhs: other) + } + + public func greaterThanOrEqual(other: Self) -> Bool { + decimalGreaterThanOrEqual(lhs: self, rhs: other) + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/DisplayName+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/DisplayName+Wrap+Functions.swift new file mode 100644 index 000000000..2fdc40db2 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/DisplayName+Wrap+Functions.swift @@ -0,0 +1,5 @@ +extension DisplayName { + public init(validating name: String) throws { + self = try newDisplayName(name: name) + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/Mnemonic+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Mnemonic+Wrap+Functions.swift new file mode 100644 index 000000000..b32fe1f5c --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/Mnemonic+Wrap+Functions.swift @@ -0,0 +1,5 @@ +extension Mnemonic { + public var phrase: String { + mnemonicPhrase(from: self) + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/SecureStorageKey+Methods.swift b/apple/Sources/Sargon/Extensions/Methods/SecureStorageKey+Wrap+Functions.swift similarity index 100% rename from apple/Sources/Sargon/Extensions/Methods/SecureStorageKey+Methods.swift rename to apple/Sources/Sargon/Extensions/Methods/SecureStorageKey+Wrap+Functions.swift diff --git a/apple/Sources/Sargon/Extensions/Swiftified/AppearanceID+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/AppearanceID+Swiftified.swift index 3251092d3..d4deaca59 100644 --- a/apple/Sources/Sargon/Extensions/Swiftified/AppearanceID+Swiftified.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/AppearanceID+Swiftified.swift @@ -1,8 +1,14 @@ public typealias AppearanceID = AppearanceId extension AppearanceID: Sendable {} -extension AppearanceID: CaseIterable { - public static var allCases: [Self] { - appearanceIdsAll() +extension AppearanceID: Identifiable { + public typealias ID = UInt8 + public var id: ID { + value + } +} +extension AppearanceID: CustomStringConvertible { + public var description: String { + value.description } } diff --git a/apple/Sources/Sargon/Extensions/Swiftified/BagOfBytes+Random.swift b/apple/Sources/Sargon/Extensions/Swiftified/BagOfBytes+Random.swift index c44318e59..d67975f15 100644 --- a/apple/Sources/Sargon/Extensions/Swiftified/BagOfBytes+Random.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/BagOfBytes+Random.swift @@ -1,7 +1,4 @@ extension BagOfBytes { - public init(data: Data) { - self = newBagOfBytesFrom(bytes: data) - } public static func random(byteCount: Int) -> Self { var data = Data(repeating: 0, count: byteCount) data.withUnsafeMutableBytes { diff --git a/apple/Sources/Sargon/Extensions/Swiftified/Decimal192+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/Decimal192+Swiftified.swift index bd0ba6380..12687e79c 100644 --- a/apple/Sources/Sargon/Extensions/Swiftified/Decimal192+Swiftified.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/Decimal192+Swiftified.swift @@ -1,11 +1,55 @@ extension Decimal192: Sendable {} -//extension Decimal192: ExpressibleByStringLiteral { -// -//} -//extension Decimal192: ExpressibleByFloatLiteral { -// -//} -//extension Decimal192: SignedNumeric /* Numeric, ExpressibleByIntegerLiteral AdditiveArithmetic */ -//{ -// -//} + +extension Decimal192: ExpressibleByStringLiteral { + public init(stringLiteral string: String) { + try! self.init(string) + } +} +extension Decimal192: ExpressibleByIntegerLiteral { + public init(integerLiteral i64: Int64) { + self = newDecimalFromI64(value: i64) + } +} + + +extension Decimal192: AdditiveArithmetic { + public static var zero: Self { + newDecimalFromU64(value: 0) + } + public static func + (lhs: Self, rhs: Self) -> Self { + decimalAdd(lhs: lhs, rhs: rhs) + } + public static func - (lhs: Self, rhs: Self) -> Self { + decimalSub(lhs: lhs, rhs: rhs) + } +} +extension Decimal192: SignedNumeric { + public prefix static func - (operand: Self) -> Self { + decimalNeg(decimal: operand) + } +} +extension Decimal192: Numeric { + public typealias Magnitude = Self + + public var magnitude: Magnitude { + decimalAbs(decimal: self) + } + + public static func * (lhs: Self, rhs: Self) -> Self { + decimalMul(lhs: lhs, rhs: rhs) + } + + public static func *= (lhs: inout Self, rhs: Self) { + lhs = lhs * rhs + } + + public init?(exactly source: T) where T: BinaryInteger { + if let i64 = Int64(exactly: source) { + self = newDecimalFromI64(value: i64) + } else if let u64 = UInt64(exactly: source) { + self = newDecimalFromU64(value: u64) + } else { + return nil + } + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/DisplayName+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/DisplayName+Swiftified.swift index 144d13308..830bd0373 100644 --- a/apple/Sources/Sargon/Extensions/Swiftified/DisplayName+Swiftified.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/DisplayName+Swiftified.swift @@ -1,9 +1,4 @@ extension DisplayName: Sendable {} -extension DisplayName { - public init(validating name: String) throws { - self = try newDisplayName(name: name) - } -} #if DEBUG extension DisplayName: ExpressibleByStringLiteral { diff --git a/apple/Sources/Sargon/Extensions/Swiftified/LocaleConfig+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/LocaleConfig+Swiftified.swift new file mode 100644 index 000000000..3eae8b7ca --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/LocaleConfig+Swiftified.swift @@ -0,0 +1,9 @@ +extension LocaleConfig: Sendable {} +extension LocaleConfig { + public init(locale: Locale) { + self.init( + decimalSeparator: locale.decimalSeparator, + groupingSeparator: locale.groupingSeparator + ) + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/Mnemonic+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/Mnemonic+Swiftified.swift new file mode 100644 index 000000000..e360de9b2 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/Mnemonic+Swiftified.swift @@ -0,0 +1 @@ +extension Mnemonic: Sendable {} diff --git a/apple/Sources/UniFFI/Sargon.swift b/apple/Sources/UniFFI/Sargon.swift index 149bd440a..33332b662 100644 --- a/apple/Sources/UniFFI/Sargon.swift +++ b/apple/Sources/UniFFI/Sargon.swift @@ -350,6 +350,19 @@ private struct FfiConverterUInt32: FfiConverterPrimitive { } } +private struct FfiConverterInt32: FfiConverterPrimitive { + typealias FfiType = Int32 + typealias SwiftType = Int32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int32, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + private struct FfiConverterUInt64: FfiConverterPrimitive { typealias FfiType = UInt64 typealias SwiftType = UInt64 @@ -363,6 +376,32 @@ private struct FfiConverterUInt64: FfiConverterPrimitive { } } +private struct FfiConverterInt64: FfiConverterPrimitive { + typealias FfiType = Int64 + typealias SwiftType = Int64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +private struct FfiConverterFloat: FfiConverterPrimitive { + typealias FfiType = Float + typealias SwiftType = Float + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + return try lift(readFloat(&buf)) + } + + public static func write(_ value: Float, into buf: inout [UInt8]) { + writeFloat(&buf, lower(value)) + } +} + private struct FfiConverterBool: FfiConverter { typealias FfiType = Int8 typealias SwiftType = Bool @@ -740,6 +779,18 @@ public protocol WalletProtocol: AnyObject { */ func jsonSnapshot() -> String + /** + * Tries to load the `MnemonicWithPassphrase` for the main "Babylon" + * `DeviceFactorSource` from secure storage. + */ + func mainBdfsMnemonicWithPassphrase() throws -> MnemonicWithPassphrase + + /** + * Tries to load a `MnemonicWithPassphrase` from secure storage + * by `factor_source_id`. + */ + func mnemonicWithPassphraseOfDeviceFactorSourceByFactorSourceId(factorSourceId: FactorSourceId) throws -> MnemonicWithPassphrase + /** * Clone the profile and return it. */ @@ -881,6 +932,31 @@ public class Wallet: ) } + /** + * Tries to load the `MnemonicWithPassphrase` for the main "Babylon" + * `DeviceFactorSource` from secure storage. + */ + public func mainBdfsMnemonicWithPassphrase() throws -> MnemonicWithPassphrase { + return try FfiConverterTypeMnemonicWithPassphrase.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_method_wallet_main_bdfs_mnemonic_with_passphrase(self.uniffiClonePointer(), $0) + } + ) + } + + /** + * Tries to load a `MnemonicWithPassphrase` from secure storage + * by `factor_source_id`. + */ + public func mnemonicWithPassphraseOfDeviceFactorSourceByFactorSourceId(factorSourceId: FactorSourceId) throws -> MnemonicWithPassphrase { + return try FfiConverterTypeMnemonicWithPassphrase.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_method_wallet_mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(self.uniffiClonePointer(), + FfiConverterTypeFactorSourceID.lower(factorSourceId), $0) + } + ) + } + /** * Clone the profile and return it. */ @@ -944,6 +1020,37 @@ public func FfiConverterTypeWallet_lower(_ value: Wallet) -> UnsafeMutableRawPoi return FfiConverterTypeWallet.lower(value) } +public struct AccessControllerAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension AccessControllerAddress: Equatable, Hashable { + public static func == (_: AccessControllerAddress, _: AccessControllerAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeAccessControllerAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> AccessControllerAddress { + return + AccessControllerAddress() + } + + public static func write(_: AccessControllerAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeAccessControllerAddress_lift(_ buf: RustBuffer) throws -> AccessControllerAddress { + return try FfiConverterTypeAccessControllerAddress.lift(buf) +} + +public func FfiConverterTypeAccessControllerAddress_lower(_ value: AccessControllerAddress) -> RustBuffer { + return FfiConverterTypeAccessControllerAddress.lower(value) +} + /** * A network unique account with a unique public address and a set of cryptographic * factors used to control it. @@ -2067,6 +2174,37 @@ public func FfiConverterTypeCollectionOfPhoneNumbers_lower(_ value: CollectionOf return FfiConverterTypeCollectionOfPhoneNumbers.lower(value) } +public struct ComponentAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension ComponentAddress: Equatable, Hashable { + public static func == (_: ComponentAddress, _: ComponentAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeComponentAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> ComponentAddress { + return + ComponentAddress() + } + + public static func write(_: ComponentAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeComponentAddress_lift(_ buf: RustBuffer) throws -> ComponentAddress { + return try FfiConverterTypeComponentAddress.lift(buf) +} + +public func FfiConverterTypeComponentAddress_lower(_ value: ComponentAddress) -> RustBuffer { + return FfiConverterTypeComponentAddress.lower(value) +} + /** * A hint describing the contents of a Profile, acting as a * summary of a Profile used by a ProfileSnapshot Header. @@ -2187,28 +2325,63 @@ public func FfiConverterTypeContentHint_lower(_ value: ContentHint) -> RustBuffe return FfiConverterTypeContentHint.lower(value) } +/** + * `Decimal192` represents a 192 bit representation of a fixed-scale decimal number. + * + * The finite set of values are of the form `m / 10^18`, where `m` is + * an integer such that `-2^(192 - 1) <= m < 2^(192 - 1)`. + * + * Fractional part: ~60 bits/18 digits + * Integer part : 132 bits /40 digits + * Max : 3138550867693340381917894711603833208051.177722232017256447 + * Min : -3138550867693340381917894711603833208051.177722232017256448 + * + * Unless otherwise specified, all operations will panic if underflow/overflow. + * + * Powering it is the [Scrypto Decimal type, see docs][scrypto]. + * + * Note: This type cannot be called `Decimal`, since it results in naming collision + * in the Swift land (clash with `Foundation.Decimal`) instead we have created a + * type alias `Decimal = Decimal192` which we use in Rust land. + * + * [scrypto]: https://github.com/radixdlt/radixdlt-scrypto/blob/fc196e21aacc19c0a3dbb13f3cd313dccf4327ca/radix-engine-common/src/math/decimal.rs#L42 + */ public struct Decimal192 { - public var base10String: String + /** + * @Kotlin / Swift developer: Do NOT use this property/field. Instead use all the provided methods on the `Decimal192` type. + * (which are in fact vendored as freestanding global functions, + * due to limitations in UniFII as of Feb 2024, but you should + * create extension methods on Decimal192 in FFI land, translating + * these functions into methods.) + */ + public var inner: InnerDecimal // Default memberwise initializers are never public by default, so we // declare one manually. public init( - base10String: String) - { - self.base10String = base10String + /** + * @Kotlin / Swift developer: Do NOT use this property/field. Instead use all the provided methods on the `Decimal192` type. + * (which are in fact vendored as freestanding global functions, + * due to limitations in UniFII as of Feb 2024, but you should + * create extension methods on Decimal192 in FFI land, translating + * these functions into methods.) + */ + inner: InnerDecimal + ) { + self.inner = inner } } extension Decimal192: Equatable, Hashable { public static func == (lhs: Decimal192, rhs: Decimal192) -> Bool { - if lhs.base10String != rhs.base10String { + if lhs.inner != rhs.inner { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(base10String) + hasher.combine(inner) } } @@ -2216,12 +2389,12 @@ public struct FfiConverterTypeDecimal192: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Decimal192 { return try Decimal192( - base10String: FfiConverterString.read(from: &buf) + inner: FfiConverterTypeInnerDecimal.read(from: &buf) ) } public static func write(_ value: Decimal192, into buf: inout [UInt8]) { - FfiConverterString.write(value.base10String, into: &buf) + FfiConverterTypeInnerDecimal.write(value.inner, into: &buf) } } @@ -2625,6 +2798,86 @@ public func FfiConverterTypeEd25519PublicKey_lower(_ value: Ed25519PublicKey) -> return FfiConverterTypeEd25519PublicKey.lower(value) } +/** + * Represents an ED25519 signature. + */ +public struct Ed25519Signature { + public var bytes: Hex64Bytes + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + bytes: Hex64Bytes) + { + self.bytes = bytes + } +} + +extension Ed25519Signature: Equatable, Hashable { + public static func == (lhs: Ed25519Signature, rhs: Ed25519Signature) -> Bool { + if lhs.bytes != rhs.bytes { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(bytes) + } +} + +public struct FfiConverterTypeEd25519Signature: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Ed25519Signature { + return + try Ed25519Signature( + bytes: FfiConverterTypeHex64Bytes.read(from: &buf) + ) + } + + public static func write(_ value: Ed25519Signature, into buf: inout [UInt8]) { + FfiConverterTypeHex64Bytes.write(value.bytes, into: &buf) + } +} + +public func FfiConverterTypeEd25519Signature_lift(_ buf: RustBuffer) throws -> Ed25519Signature { + return try FfiConverterTypeEd25519Signature.lift(buf) +} + +public func FfiConverterTypeEd25519Signature_lower(_ value: Ed25519Signature) -> RustBuffer { + return FfiConverterTypeEd25519Signature.lower(value) +} + +public struct ExecutionSummary { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension ExecutionSummary: Equatable, Hashable { + public static func == (_: ExecutionSummary, _: ExecutionSummary) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeExecutionSummary: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> ExecutionSummary { + return + ExecutionSummary() + } + + public static func write(_: ExecutionSummary, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeExecutionSummary_lift(_ buf: RustBuffer) throws -> ExecutionSummary { + return try FfiConverterTypeExecutionSummary.lift(buf) +} + +public func FfiConverterTypeExecutionSummary_lower(_ value: ExecutionSummary) -> RustBuffer { + return FfiConverterTypeExecutionSummary.lower(value) +} + public struct FactorInstance { /** * The ID of the `FactorSource` that was used to produce this @@ -3483,7 +3736,7 @@ public func FfiConverterTypeHeader_lower(_ value: Header) -> RustBuffer { } /** - * Serializable 32 bytes which **always** serializes as a **hex** string, this is useful + * Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful * since in Radix Wallet Kit we almost always want to serialize bytes into hex and this * allows us to skip using */ @@ -3534,156 +3787,309 @@ public func FfiConverterTypeHex32Bytes_lower(_ value: Hex32Bytes) -> RustBuffer } /** - * A virtual hierarchical deterministic `FactorInstance` + * Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful + * since in Radix Wallet Kit we almost always want to serialize bytes into hex and this + * allows us to skip using */ -public struct HierarchicalDeterministicFactorInstance { - public var factorSourceId: FactorSourceIdFromHash - public var publicKey: HierarchicalDeterministicPublicKey +public struct Hex33Bytes { + public var bagOfBytes: BagOfBytes // Default memberwise initializers are never public by default, so we // declare one manually. public init( - factorSourceId: FactorSourceIdFromHash, - publicKey: HierarchicalDeterministicPublicKey - ) { - self.factorSourceId = factorSourceId - self.publicKey = publicKey + bagOfBytes: BagOfBytes) + { + self.bagOfBytes = bagOfBytes } } -extension HierarchicalDeterministicFactorInstance: Equatable, Hashable { - public static func == (lhs: HierarchicalDeterministicFactorInstance, rhs: HierarchicalDeterministicFactorInstance) -> Bool { - if lhs.factorSourceId != rhs.factorSourceId { - return false - } - if lhs.publicKey != rhs.publicKey { +extension Hex33Bytes: Equatable, Hashable { + public static func == (lhs: Hex33Bytes, rhs: Hex33Bytes) -> Bool { + if lhs.bagOfBytes != rhs.bagOfBytes { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(factorSourceId) - hasher.combine(publicKey) + hasher.combine(bagOfBytes) } } -public struct FfiConverterTypeHierarchicalDeterministicFactorInstance: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HierarchicalDeterministicFactorInstance { +public struct FfiConverterTypeHex33Bytes: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Hex33Bytes { return - try HierarchicalDeterministicFactorInstance( - factorSourceId: FfiConverterTypeFactorSourceIDFromHash.read(from: &buf), - publicKey: FfiConverterTypeHierarchicalDeterministicPublicKey.read(from: &buf) + try Hex33Bytes( + bagOfBytes: FfiConverterTypeBagOfBytes.read(from: &buf) ) } - public static func write(_ value: HierarchicalDeterministicFactorInstance, into buf: inout [UInt8]) { - FfiConverterTypeFactorSourceIDFromHash.write(value.factorSourceId, into: &buf) - FfiConverterTypeHierarchicalDeterministicPublicKey.write(value.publicKey, into: &buf) + public static func write(_ value: Hex33Bytes, into buf: inout [UInt8]) { + FfiConverterTypeBagOfBytes.write(value.bagOfBytes, into: &buf) } } -public func FfiConverterTypeHierarchicalDeterministicFactorInstance_lift(_ buf: RustBuffer) throws -> HierarchicalDeterministicFactorInstance { - return try FfiConverterTypeHierarchicalDeterministicFactorInstance.lift(buf) +public func FfiConverterTypeHex33Bytes_lift(_ buf: RustBuffer) throws -> Hex33Bytes { + return try FfiConverterTypeHex33Bytes.lift(buf) } -public func FfiConverterTypeHierarchicalDeterministicFactorInstance_lower(_ value: HierarchicalDeterministicFactorInstance) -> RustBuffer { - return FfiConverterTypeHierarchicalDeterministicFactorInstance.lower(value) +public func FfiConverterTypeHex33Bytes_lower(_ value: Hex33Bytes) -> RustBuffer { + return FfiConverterTypeHex33Bytes.lower(value) } /** - * The **source** of a virtual hierarchical deterministic badge, contains a - * derivation path and public key, from which a private key is derived which - * produces virtual badges (signatures). - * - * The `.device` `FactorSource` produces `FactorInstance`s with this kind if badge source. + * Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful + * since in Radix Wallet Kit we almost always want to serialize bytes into hex and this + * allows us to skip using */ -public struct HierarchicalDeterministicPublicKey { - /** - * The expected public key of the private key derived at `derivationPath` - */ - public var publicKey: PublicKey - /** - * The HD derivation path for the key pair which produces virtual badges (signatures). - */ - public var derivationPath: DerivationPath +public struct Hex64Bytes { + public var bagOfBytes: BagOfBytes // Default memberwise initializers are never public by default, so we // declare one manually. public init( - /** - * The expected public key of the private key derived at `derivationPath` - */ - publicKey: PublicKey, - /** - * The HD derivation path for the key pair which produces virtual badges (signatures). - */ - derivationPath: DerivationPath - ) { - self.publicKey = publicKey - self.derivationPath = derivationPath + bagOfBytes: BagOfBytes) + { + self.bagOfBytes = bagOfBytes } } -extension HierarchicalDeterministicPublicKey: Equatable, Hashable { - public static func == (lhs: HierarchicalDeterministicPublicKey, rhs: HierarchicalDeterministicPublicKey) -> Bool { - if lhs.publicKey != rhs.publicKey { - return false - } - if lhs.derivationPath != rhs.derivationPath { +extension Hex64Bytes: Equatable, Hashable { + public static func == (lhs: Hex64Bytes, rhs: Hex64Bytes) -> Bool { + if lhs.bagOfBytes != rhs.bagOfBytes { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(publicKey) - hasher.combine(derivationPath) + hasher.combine(bagOfBytes) } } -public struct FfiConverterTypeHierarchicalDeterministicPublicKey: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HierarchicalDeterministicPublicKey { +public struct FfiConverterTypeHex64Bytes: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Hex64Bytes { return - try HierarchicalDeterministicPublicKey( - publicKey: FfiConverterTypePublicKey.read(from: &buf), - derivationPath: FfiConverterTypeDerivationPath.read(from: &buf) + try Hex64Bytes( + bagOfBytes: FfiConverterTypeBagOfBytes.read(from: &buf) ) } - public static func write(_ value: HierarchicalDeterministicPublicKey, into buf: inout [UInt8]) { - FfiConverterTypePublicKey.write(value.publicKey, into: &buf) - FfiConverterTypeDerivationPath.write(value.derivationPath, into: &buf) + public static func write(_ value: Hex64Bytes, into buf: inout [UInt8]) { + FfiConverterTypeBagOfBytes.write(value.bagOfBytes, into: &buf) } } -public func FfiConverterTypeHierarchicalDeterministicPublicKey_lift(_ buf: RustBuffer) throws -> HierarchicalDeterministicPublicKey { - return try FfiConverterTypeHierarchicalDeterministicPublicKey.lift(buf) +public func FfiConverterTypeHex64Bytes_lift(_ buf: RustBuffer) throws -> Hex64Bytes { + return try FfiConverterTypeHex64Bytes.lift(buf) } -public func FfiConverterTypeHierarchicalDeterministicPublicKey_lower(_ value: HierarchicalDeterministicPublicKey) -> RustBuffer { - return FfiConverterTypeHierarchicalDeterministicPublicKey.lower(value) +public func FfiConverterTypeHex64Bytes_lower(_ value: Hex64Bytes) -> RustBuffer { + return FfiConverterTypeHex64Bytes.lower(value) } /** - * The address of an identity, used by Personas, a bech32 encoding of a public key hash - * that starts with the prefix `"identity_"`, dependent on NetworkID, meaning the same - * public key used for two IdentityAddresses on two different networks will not have - * the same address. + * Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful + * since in Radix Wallet Kit we almost always want to serialize bytes into hex and this + * allows us to skip using */ -public struct IdentityAddress { - /** - * Human readable address of an identity, which are used by Personas. Always starts with - * the prefix `"identity_"`, for example: - * - * `identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j` - * - * Addresses are checksummed, as per Bech32. **Only** *Identity* addresses starts with - * the prefix `"identity_"`. - */ - public var address: String - /** - * The network this identity address is tied to, i.e. which was used when a public key +public struct Hex65Bytes { + public var bagOfBytes: BagOfBytes + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + bagOfBytes: BagOfBytes) + { + self.bagOfBytes = bagOfBytes + } +} + +extension Hex65Bytes: Equatable, Hashable { + public static func == (lhs: Hex65Bytes, rhs: Hex65Bytes) -> Bool { + if lhs.bagOfBytes != rhs.bagOfBytes { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(bagOfBytes) + } +} + +public struct FfiConverterTypeHex65Bytes: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Hex65Bytes { + return + try Hex65Bytes( + bagOfBytes: FfiConverterTypeBagOfBytes.read(from: &buf) + ) + } + + public static func write(_ value: Hex65Bytes, into buf: inout [UInt8]) { + FfiConverterTypeBagOfBytes.write(value.bagOfBytes, into: &buf) + } +} + +public func FfiConverterTypeHex65Bytes_lift(_ buf: RustBuffer) throws -> Hex65Bytes { + return try FfiConverterTypeHex65Bytes.lift(buf) +} + +public func FfiConverterTypeHex65Bytes_lower(_ value: Hex65Bytes) -> RustBuffer { + return FfiConverterTypeHex65Bytes.lower(value) +} + +/** + * A virtual hierarchical deterministic `FactorInstance` + */ +public struct HierarchicalDeterministicFactorInstance { + public var factorSourceId: FactorSourceIdFromHash + public var publicKey: HierarchicalDeterministicPublicKey + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + factorSourceId: FactorSourceIdFromHash, + publicKey: HierarchicalDeterministicPublicKey + ) { + self.factorSourceId = factorSourceId + self.publicKey = publicKey + } +} + +extension HierarchicalDeterministicFactorInstance: Equatable, Hashable { + public static func == (lhs: HierarchicalDeterministicFactorInstance, rhs: HierarchicalDeterministicFactorInstance) -> Bool { + if lhs.factorSourceId != rhs.factorSourceId { + return false + } + if lhs.publicKey != rhs.publicKey { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(factorSourceId) + hasher.combine(publicKey) + } +} + +public struct FfiConverterTypeHierarchicalDeterministicFactorInstance: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HierarchicalDeterministicFactorInstance { + return + try HierarchicalDeterministicFactorInstance( + factorSourceId: FfiConverterTypeFactorSourceIDFromHash.read(from: &buf), + publicKey: FfiConverterTypeHierarchicalDeterministicPublicKey.read(from: &buf) + ) + } + + public static func write(_ value: HierarchicalDeterministicFactorInstance, into buf: inout [UInt8]) { + FfiConverterTypeFactorSourceIDFromHash.write(value.factorSourceId, into: &buf) + FfiConverterTypeHierarchicalDeterministicPublicKey.write(value.publicKey, into: &buf) + } +} + +public func FfiConverterTypeHierarchicalDeterministicFactorInstance_lift(_ buf: RustBuffer) throws -> HierarchicalDeterministicFactorInstance { + return try FfiConverterTypeHierarchicalDeterministicFactorInstance.lift(buf) +} + +public func FfiConverterTypeHierarchicalDeterministicFactorInstance_lower(_ value: HierarchicalDeterministicFactorInstance) -> RustBuffer { + return FfiConverterTypeHierarchicalDeterministicFactorInstance.lower(value) +} + +/** + * The **source** of a virtual hierarchical deterministic badge, contains a + * derivation path and public key, from which a private key is derived which + * produces virtual badges (signatures). + * + * The `.device` `FactorSource` produces `FactorInstance`s with this kind if badge source. + */ +public struct HierarchicalDeterministicPublicKey { + /** + * The expected public key of the private key derived at `derivationPath` + */ + public var publicKey: PublicKey + /** + * The HD derivation path for the key pair which produces virtual badges (signatures). + */ + public var derivationPath: DerivationPath + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The expected public key of the private key derived at `derivationPath` + */ + publicKey: PublicKey, + /** + * The HD derivation path for the key pair which produces virtual badges (signatures). + */ + derivationPath: DerivationPath + ) { + self.publicKey = publicKey + self.derivationPath = derivationPath + } +} + +extension HierarchicalDeterministicPublicKey: Equatable, Hashable { + public static func == (lhs: HierarchicalDeterministicPublicKey, rhs: HierarchicalDeterministicPublicKey) -> Bool { + if lhs.publicKey != rhs.publicKey { + return false + } + if lhs.derivationPath != rhs.derivationPath { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(publicKey) + hasher.combine(derivationPath) + } +} + +public struct FfiConverterTypeHierarchicalDeterministicPublicKey: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HierarchicalDeterministicPublicKey { + return + try HierarchicalDeterministicPublicKey( + publicKey: FfiConverterTypePublicKey.read(from: &buf), + derivationPath: FfiConverterTypeDerivationPath.read(from: &buf) + ) + } + + public static func write(_ value: HierarchicalDeterministicPublicKey, into buf: inout [UInt8]) { + FfiConverterTypePublicKey.write(value.publicKey, into: &buf) + FfiConverterTypeDerivationPath.write(value.derivationPath, into: &buf) + } +} + +public func FfiConverterTypeHierarchicalDeterministicPublicKey_lift(_ buf: RustBuffer) throws -> HierarchicalDeterministicPublicKey { + return try FfiConverterTypeHierarchicalDeterministicPublicKey.lift(buf) +} + +public func FfiConverterTypeHierarchicalDeterministicPublicKey_lower(_ value: HierarchicalDeterministicPublicKey) -> RustBuffer { + return FfiConverterTypeHierarchicalDeterministicPublicKey.lower(value) +} + +/** + * The address of an identity, used by Personas, a bech32 encoding of a public key hash + * that starts with the prefix `"identity_"`, dependent on NetworkID, meaning the same + * public key used for two IdentityAddresses on two different networks will not have + * the same address. + */ +public struct IdentityAddress { + /** + * Human readable address of an identity, which are used by Personas. Always starts with + * the prefix `"identity_"`, for example: + * + * `identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j` + * + * Addresses are checksummed, as per Bech32. **Only** *Identity* addresses starts with + * the prefix `"identity_"`. + */ + public var address: String + /** + * The network this identity address is tied to, i.e. which was used when a public key * hash was used to bech32 encode it. This means that two public key hashes will result * in two different identity address on two different networks. */ @@ -3997,6 +4403,92 @@ public func FfiConverterTypeLedgerHardwareWalletHint_lower(_ value: LedgerHardwa return FfiConverterTypeLedgerHardwareWalletHint.lower(value) } +public struct LocaleConfig { + public var decimalSeparator: String? + public var groupingSeparator: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + decimalSeparator: String?, + groupingSeparator: String? + ) { + self.decimalSeparator = decimalSeparator + self.groupingSeparator = groupingSeparator + } +} + +extension LocaleConfig: Equatable, Hashable { + public static func == (lhs: LocaleConfig, rhs: LocaleConfig) -> Bool { + if lhs.decimalSeparator != rhs.decimalSeparator { + return false + } + if lhs.groupingSeparator != rhs.groupingSeparator { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(decimalSeparator) + hasher.combine(groupingSeparator) + } +} + +public struct FfiConverterTypeLocaleConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LocaleConfig { + return + try LocaleConfig( + decimalSeparator: FfiConverterOptionString.read(from: &buf), + groupingSeparator: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: LocaleConfig, into buf: inout [UInt8]) { + FfiConverterOptionString.write(value.decimalSeparator, into: &buf) + FfiConverterOptionString.write(value.groupingSeparator, into: &buf) + } +} + +public func FfiConverterTypeLocaleConfig_lift(_ buf: RustBuffer) throws -> LocaleConfig { + return try FfiConverterTypeLocaleConfig.lift(buf) +} + +public func FfiConverterTypeLocaleConfig_lower(_ value: LocaleConfig) -> RustBuffer { + return FfiConverterTypeLocaleConfig.lower(value) +} + +public struct ManifestSummary { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension ManifestSummary: Equatable, Hashable { + public static func == (_: ManifestSummary, _: ManifestSummary) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeManifestSummary: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> ManifestSummary { + return + ManifestSummary() + } + + public static func write(_: ManifestSummary, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeManifestSummary_lift(_ buf: RustBuffer) throws -> ManifestSummary { + return try FfiConverterTypeManifestSummary.lift(buf) +} + +public func FfiConverterTypeManifestSummary_lower(_ value: ManifestSummary) -> RustBuffer { + return FfiConverterTypeManifestSummary.lower(value) +} + public struct Mnemonic { public var words: [Bip39Word] public var wordCount: Bip39WordCount @@ -4260,6 +4752,37 @@ public func FfiConverterTypeNonFungibleGlobalId_lower(_ value: NonFungibleGlobal return FfiConverterTypeNonFungibleGlobalId.lower(value) } +public struct NotarizedTransaction { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension NotarizedTransaction: Equatable, Hashable { + public static func == (_: NotarizedTransaction, _: NotarizedTransaction) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeNotarizedTransaction: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> NotarizedTransaction { + return + NotarizedTransaction() + } + + public static func write(_: NotarizedTransaction, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeNotarizedTransaction_lift(_ buf: RustBuffer) throws -> NotarizedTransaction { + return try FfiConverterTypeNotarizedTransaction.lift(buf) +} + +public func FfiConverterTypeNotarizedTransaction_lower(_ value: NotarizedTransaction) -> RustBuffer { + return FfiConverterTypeNotarizedTransaction.lower(value) +} + /** * Account settings that user has set on the account component * On-Ledger, that is set via a transaction mutating the state @@ -4399,10 +4922,41 @@ public func FfiConverterTypeP2PLink_lower(_ value: P2pLink) -> RustBuffer { return FfiConverterTypeP2PLink.lower(value) } -/** - * A Persona is an identity a user choses to login to a dApp with, using - * RadixConnect - Radix decentralized login solution. A persona is very - * similar to [`Account`]s, in the sense that they are On-Network/On-Ledger +public struct PackageAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension PackageAddress: Equatable, Hashable { + public static func == (_: PackageAddress, _: PackageAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypePackageAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> PackageAddress { + return + PackageAddress() + } + + public static func write(_: PackageAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypePackageAddress_lift(_ buf: RustBuffer) throws -> PackageAddress { + return try FfiConverterTypePackageAddress.lift(buf) +} + +public func FfiConverterTypePackageAddress_lower(_ value: PackageAddress) -> RustBuffer { + return FfiConverterTypePackageAddress.lower(value) +} + +/** + * A Persona is an identity a user choses to login to a dApp with, using + * RadixConnect - Radix decentralized login solution. A persona is very + * similar to [`Account`]s, in the sense that they are On-Network/On-Ledger * components, with a unique network dependent address ([`IdentityAddress`]) * and with a security state (see [`EntitySecurityState`]) knowing which * factor instances that control this component, but with one important @@ -5325,6 +5879,37 @@ public func FfiConverterTypeProfileNetwork_lower(_ value: ProfileNetwork) -> Rus return FfiConverterTypeProfileNetwork.lower(value) } +public struct RetDecimal { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension RetDecimal: Equatable, Hashable { + public static func == (_: RetDecimal, _: RetDecimal) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeRETDecimal: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> RetDecimal { + return + RetDecimal() + } + + public static func write(_: RetDecimal, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeRETDecimal_lift(_ buf: RustBuffer) throws -> RetDecimal { + return try FfiConverterTypeRETDecimal.lift(buf) +} + +public func FfiConverterTypeRETDecimal_lower(_ value: RetDecimal) -> RustBuffer { + return FfiConverterTypeRETDecimal.lower(value) +} + /** * The hash of the connection password is used to connect to the Radix Connect Signaling Server, * over web sockets. The actual `ConnectionPassword` is used to encrypt all messages sent via @@ -5496,6 +6081,37 @@ public func FfiConverterTypeResourceAddress_lower(_ value: ResourceAddress) -> R return FfiConverterTypeResourceAddress.lower(value) } +public struct ResourcePoolAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension ResourcePoolAddress: Equatable, Hashable { + public static func == (_: ResourcePoolAddress, _: ResourcePoolAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeResourcePoolAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> ResourcePoolAddress { + return + ResourcePoolAddress() + } + + public static func write(_: ResourcePoolAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeResourcePoolAddress_lift(_ buf: RustBuffer) throws -> ResourcePoolAddress { + return try FfiConverterTypeResourcePoolAddress.lift(buf) +} + +public func FfiConverterTypeResourcePoolAddress_lower(_ value: ResourcePoolAddress) -> RustBuffer { + return FfiConverterTypeResourcePoolAddress.lower(value) +} + /** * A `secp256k1` public key used to verify cryptographic signatures (ECDSA signatures). */ @@ -5545,6 +6161,55 @@ public func FfiConverterTypeSecp256k1PublicKey_lower(_ value: Secp256k1PublicKey return FfiConverterTypeSecp256k1PublicKey.lower(value) } +/** + * Represents an Secp256k1 signature. + */ +public struct Secp256k1Signature { + public var bytes: Hex65Bytes + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + bytes: Hex65Bytes) + { + self.bytes = bytes + } +} + +extension Secp256k1Signature: Equatable, Hashable { + public static func == (lhs: Secp256k1Signature, rhs: Secp256k1Signature) -> Bool { + if lhs.bytes != rhs.bytes { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(bytes) + } +} + +public struct FfiConverterTypeSecp256k1Signature: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Secp256k1Signature { + return + try Secp256k1Signature( + bytes: FfiConverterTypeHex65Bytes.read(from: &buf) + ) + } + + public static func write(_ value: Secp256k1Signature, into buf: inout [UInt8]) { + FfiConverterTypeHex65Bytes.write(value.bytes, into: &buf) + } +} + +public func FfiConverterTypeSecp256k1Signature_lift(_ buf: RustBuffer) throws -> Secp256k1Signature { + return try FfiConverterTypeSecp256k1Signature.lift(buf) +} + +public func FfiConverterTypeSecp256k1Signature_lower(_ value: Secp256k1Signature) -> RustBuffer { + return FfiConverterTypeSecp256k1Signature.lower(value) +} + /** * Controls e.g. if Profile Snapshot gets synced to iCloud or not, and whether * developer mode is enabled or not. In future (MFA) we will also save a list of @@ -5939,6 +6604,199 @@ public func FfiConverterTypeThirdPartyDeposits_lower(_ value: ThirdPartyDeposits return FfiConverterTypeThirdPartyDeposits.lower(value) } +public struct TransactionHash { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension TransactionHash: Equatable, Hashable { + public static func == (_: TransactionHash, _: TransactionHash) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeTransactionHash: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> TransactionHash { + return + TransactionHash() + } + + public static func write(_: TransactionHash, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeTransactionHash_lift(_ buf: RustBuffer) throws -> TransactionHash { + return try FfiConverterTypeTransactionHash.lift(buf) +} + +public func FfiConverterTypeTransactionHash_lower(_ value: TransactionHash) -> RustBuffer { + return FfiConverterTypeTransactionHash.lower(value) +} + +public struct TransactionHeader { + public var networkId: NetworkId + public var startEpochInclusive: Epoch + public var endEpochExclusive: Epoch + public var nonce: Nonce + public var notaryPublicKey: PublicKey + public var notaryIsSignatory: Bool + public var tipPercentage: UInt16 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + networkId: NetworkId, + startEpochInclusive: Epoch, + endEpochExclusive: Epoch, + nonce: Nonce, + notaryPublicKey: PublicKey, + notaryIsSignatory: Bool, + tipPercentage: UInt16 + ) { + self.networkId = networkId + self.startEpochInclusive = startEpochInclusive + self.endEpochExclusive = endEpochExclusive + self.nonce = nonce + self.notaryPublicKey = notaryPublicKey + self.notaryIsSignatory = notaryIsSignatory + self.tipPercentage = tipPercentage + } +} + +extension TransactionHeader: Equatable, Hashable { + public static func == (lhs: TransactionHeader, rhs: TransactionHeader) -> Bool { + if lhs.networkId != rhs.networkId { + return false + } + if lhs.startEpochInclusive != rhs.startEpochInclusive { + return false + } + if lhs.endEpochExclusive != rhs.endEpochExclusive { + return false + } + if lhs.nonce != rhs.nonce { + return false + } + if lhs.notaryPublicKey != rhs.notaryPublicKey { + return false + } + if lhs.notaryIsSignatory != rhs.notaryIsSignatory { + return false + } + if lhs.tipPercentage != rhs.tipPercentage { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(networkId) + hasher.combine(startEpochInclusive) + hasher.combine(endEpochExclusive) + hasher.combine(nonce) + hasher.combine(notaryPublicKey) + hasher.combine(notaryIsSignatory) + hasher.combine(tipPercentage) + } +} + +public struct FfiConverterTypeTransactionHeader: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TransactionHeader { + return + try TransactionHeader( + networkId: FfiConverterTypeNetworkID.read(from: &buf), + startEpochInclusive: FfiConverterTypeEpoch.read(from: &buf), + endEpochExclusive: FfiConverterTypeEpoch.read(from: &buf), + nonce: FfiConverterTypeNonce.read(from: &buf), + notaryPublicKey: FfiConverterTypePublicKey.read(from: &buf), + notaryIsSignatory: FfiConverterBool.read(from: &buf), + tipPercentage: FfiConverterUInt16.read(from: &buf) + ) + } + + public static func write(_ value: TransactionHeader, into buf: inout [UInt8]) { + FfiConverterTypeNetworkID.write(value.networkId, into: &buf) + FfiConverterTypeEpoch.write(value.startEpochInclusive, into: &buf) + FfiConverterTypeEpoch.write(value.endEpochExclusive, into: &buf) + FfiConverterTypeNonce.write(value.nonce, into: &buf) + FfiConverterTypePublicKey.write(value.notaryPublicKey, into: &buf) + FfiConverterBool.write(value.notaryIsSignatory, into: &buf) + FfiConverterUInt16.write(value.tipPercentage, into: &buf) + } +} + +public func FfiConverterTypeTransactionHeader_lift(_ buf: RustBuffer) throws -> TransactionHeader { + return try FfiConverterTypeTransactionHeader.lift(buf) +} + +public func FfiConverterTypeTransactionHeader_lower(_ value: TransactionHeader) -> RustBuffer { + return FfiConverterTypeTransactionHeader.lower(value) +} + +public struct TransactionIntent { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension TransactionIntent: Equatable, Hashable { + public static func == (_: TransactionIntent, _: TransactionIntent) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeTransactionIntent: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> TransactionIntent { + return + TransactionIntent() + } + + public static func write(_: TransactionIntent, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeTransactionIntent_lift(_ buf: RustBuffer) throws -> TransactionIntent { + return try FfiConverterTypeTransactionIntent.lift(buf) +} + +public func FfiConverterTypeTransactionIntent_lower(_ value: TransactionIntent) -> RustBuffer { + return FfiConverterTypeTransactionIntent.lower(value) +} + +public struct TransactionManifest { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension TransactionManifest: Equatable, Hashable { + public static func == (_: TransactionManifest, _: TransactionManifest) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeTransactionManifest: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> TransactionManifest { + return + TransactionManifest() + } + + public static func write(_: TransactionManifest, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeTransactionManifest_lift(_ buf: RustBuffer) throws -> TransactionManifest { + return try FfiConverterTypeTransactionManifest.lift(buf) +} + +public func FfiConverterTypeTransactionManifest_lower(_ value: TransactionManifest) -> RustBuffer { + return FfiConverterTypeTransactionManifest.lower(value) +} + /** * User Preferences relating to submission of transactions. */ @@ -6109,6 +6967,68 @@ public func FfiConverterTypeUnsecuredEntityControl_lower(_ value: UnsecuredEntit return FfiConverterTypeUnsecuredEntityControl.lower(value) } +public struct ValidatorAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension ValidatorAddress: Equatable, Hashable { + public static func == (_: ValidatorAddress, _: ValidatorAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeValidatorAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> ValidatorAddress { + return + ValidatorAddress() + } + + public static func write(_: ValidatorAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeValidatorAddress_lift(_ buf: RustBuffer) throws -> ValidatorAddress { + return try FfiConverterTypeValidatorAddress.lift(buf) +} + +public func FfiConverterTypeValidatorAddress_lower(_ value: ValidatorAddress) -> RustBuffer { + return FfiConverterTypeValidatorAddress.lower(value) +} + +public struct VaultAddress { + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init() {} +} + +extension VaultAddress: Equatable, Hashable { + public static func == (_: VaultAddress, _: VaultAddress) -> Bool { + return true + } + + public func hash(into _: inout Hasher) {} +} + +public struct FfiConverterTypeVaultAddress: FfiConverterRustBuffer { + public static func read(from _: inout (data: Data, offset: Data.Index)) throws -> VaultAddress { + return + VaultAddress() + } + + public static func write(_: VaultAddress, into _: inout [UInt8]) {} +} + +public func FfiConverterTypeVaultAddress_lift(_ buf: RustBuffer) throws -> VaultAddress { + return try FfiConverterTypeVaultAddress.lift(buf) +} + +public func FfiConverterTypeVaultAddress_lower(_ value: VaultAddress) -> RustBuffer { + return FfiConverterTypeVaultAddress.lower(value) +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** @@ -6439,7 +7359,7 @@ public enum CommonError { case StringNotHex(message: String) - case InvalidByteCountExpected32(message: String) + case InvalidByteCount(message: String) case InvalidBip32Path(message: String) @@ -6593,6 +7513,8 @@ public enum CommonError { case InvalidUuiDv4(message: String) + case UnrecognizedLocaleIdentifier(message: String) + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { return try FfiConverterTypeCommonError.lift(error) } @@ -6652,7 +7574,7 @@ public struct FfiConverterTypeCommonError: FfiConverterRustBuffer { message: FfiConverterString.read(from: &buf) ) - case 13: return try .InvalidByteCountExpected32( + case 13: return try .InvalidByteCount( message: FfiConverterString.read(from: &buf) ) @@ -6952,6 +7874,10 @@ public struct FfiConverterTypeCommonError: FfiConverterRustBuffer { message: FfiConverterString.read(from: &buf) ) + case 88: return try .UnrecognizedLocaleIdentifier( + message: FfiConverterString.read(from: &buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase } } @@ -6982,7 +7908,7 @@ public struct FfiConverterTypeCommonError: FfiConverterRustBuffer { writeInt(&buf, Int32(11)) case .StringNotHex(_ /* message is ignored*/ ): writeInt(&buf, Int32(12)) - case .InvalidByteCountExpected32(_ /* message is ignored*/ ): + case .InvalidByteCount(_ /* message is ignored*/ ): writeInt(&buf, Int32(13)) case .InvalidBip32Path(_ /* message is ignored*/ ): writeInt(&buf, Int32(14)) @@ -7132,6 +8058,8 @@ public struct FfiConverterTypeCommonError: FfiConverterRustBuffer { writeInt(&buf, Int32(86)) case .InvalidUuiDv4(_ /* message is ignored*/ ): writeInt(&buf, Int32(87)) + case .UnrecognizedLocaleIdentifier(_ /* message is ignored*/ ): + writeInt(&buf, Int32(88)) } } } @@ -7966,6 +8894,47 @@ public func FfiConverterTypeLedgerHardwareWalletModel_lower(_ value: LedgerHardw extension LedgerHardwareWalletModel: Equatable, Hashable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum Message { + case plainText( + string: String + ) +} + +public struct FfiConverterTypeMessage: FfiConverterRustBuffer { + typealias SwiftType = Message + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Message { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return try .plainText( + string: FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Message, into buf: inout [UInt8]) { + switch value { + case let .plainText(string): + writeInt(&buf, Int32(1)) + FfiConverterString.write(string, into: &buf) + } + } +} + +public func FfiConverterTypeMessage_lift(_ buf: RustBuffer) throws -> Message { + return try FfiConverterTypeMessage.lift(buf) +} + +public func FfiConverterTypeMessage_lower(_ value: Message) -> RustBuffer { + return FfiConverterTypeMessage.lower(value) +} + +extension Message: Equatable, Hashable {} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum NetworkId { @@ -8422,9 +9391,107 @@ extension ResourceOrNonFungible: Equatable, Hashable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Elliptic Curves which the SLIP10 derivation algorithm supports. + * Defines the rounding strategy used when you round e.g. `Decimal192`. * - * We use SLIP10 for hierarchical deterministic derivation since we + * Following the same naming convention as https://docs.rs/rust_decimal/latest/rust_decimal/enum.RoundingStrategy.html. + */ +public enum RoundingMode { + /** + * The number is always rounded toward positive infinity, e.g. `3.1 -> 4`, `-3.1 -> -3`. + */ + case toPositiveInfinity + /** + * The number is always rounded toward negative infinity, e.g. `3.1 -> 3`, `-3.1 -> -4`. + */ + case toNegativeInfinity + /** + * The number is always rounded toward zero, e.g. `3.1 -> 3`, `-3.1 -> -3`. + */ + case toZero + /** + * The number is always rounded away from zero, e.g. `3.1 -> 4`, `-3.1 -> -4`. + */ + case awayFromZero + /** + * The number is rounded to the nearest, and when it is halfway between two others, it's rounded toward zero, e.g. `3.5 -> 3`, `-3.5 -> -3`. + */ + case toNearestMidpointTowardZero + /** + * The number is rounded to the nearest, and when it is halfway between two others, it's rounded away from zero, e.g. `3.5 -> 4`, `-3.5 -> -4`. + */ + case toNearestMidpointAwayFromZero + /** + * The number is rounded to the nearest, and when it is halfway between two others, it's rounded toward the nearest even number. Also known as "Bankers Rounding". + */ + case toNearestMidpointToEven +} + +public struct FfiConverterTypeRoundingMode: FfiConverterRustBuffer { + typealias SwiftType = RoundingMode + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RoundingMode { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return .toPositiveInfinity + + case 2: return .toNegativeInfinity + + case 3: return .toZero + + case 4: return .awayFromZero + + case 5: return .toNearestMidpointTowardZero + + case 6: return .toNearestMidpointAwayFromZero + + case 7: return .toNearestMidpointToEven + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: RoundingMode, into buf: inout [UInt8]) { + switch value { + case .toPositiveInfinity: + writeInt(&buf, Int32(1)) + + case .toNegativeInfinity: + writeInt(&buf, Int32(2)) + + case .toZero: + writeInt(&buf, Int32(3)) + + case .awayFromZero: + writeInt(&buf, Int32(4)) + + case .toNearestMidpointTowardZero: + writeInt(&buf, Int32(5)) + + case .toNearestMidpointAwayFromZero: + writeInt(&buf, Int32(6)) + + case .toNearestMidpointToEven: + writeInt(&buf, Int32(7)) + } + } +} + +public func FfiConverterTypeRoundingMode_lift(_ buf: RustBuffer) throws -> RoundingMode { + return try FfiConverterTypeRoundingMode.lift(buf) +} + +public func FfiConverterTypeRoundingMode_lower(_ value: RoundingMode) -> RustBuffer { + return FfiConverterTypeRoundingMode.lower(value) +} + +extension RoundingMode: Equatable, Hashable {} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Elliptic Curves which the SLIP10 derivation algorithm supports. + * + * We use SLIP10 for hierarchical deterministic derivation since we * prefer using Curve25519 - which is incompatible with BIP32 (BIP44). * * For for information see [SLIP10 reference](https://github.com/satoshilabs/slips/blob/master/slip-0010.md) @@ -8540,6 +9607,61 @@ public func FfiConverterTypeSecureStorageKey_lower(_ value: SecureStorageKey) -> extension SecureStorageKey: Equatable, Hashable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Either a Signature on `Curve25519` or `Secp256k1` + */ +public enum Signature { + case secp256k1( + value: Secp256k1Signature + ) + case ed25519( + value: Ed25519Signature + ) +} + +public struct FfiConverterTypeSignature: FfiConverterRustBuffer { + typealias SwiftType = Signature + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Signature { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return try .secp256k1( + value: FfiConverterTypeSecp256k1Signature.read(from: &buf) + ) + + case 2: return try .ed25519( + value: FfiConverterTypeEd25519Signature.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Signature, into buf: inout [UInt8]) { + switch value { + case let .secp256k1(value): + writeInt(&buf, Int32(1)) + FfiConverterTypeSecp256k1Signature.write(value, into: &buf) + + case let .ed25519(value): + writeInt(&buf, Int32(2)) + FfiConverterTypeEd25519Signature.write(value, into: &buf) + } + } +} + +public func FfiConverterTypeSignature_lift(_ buf: RustBuffer) throws -> Signature { + return try FfiConverterTypeSignature.lift(buf) +} + +public func FfiConverterTypeSignature_lower(_ value: Signature) -> RustBuffer { + return FfiConverterTypeSignature.lower(value) +} + +extension Signature: Equatable, Hashable {} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum Variant { @@ -9350,6 +10472,99 @@ public func FfiConverterTypeBagOfBytes_lower(_ value: BagOfBytes) -> RustBuffer return FfiConverterTypeBagOfBytes.lower(value) } +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Epoch = UInt64 +public struct FfiConverterTypeEpoch: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Epoch { + return try FfiConverterUInt64.read(from: &buf) + } + + public static func write(_ value: Epoch, into buf: inout [UInt8]) { + return FfiConverterUInt64.write(value, into: &buf) + } + + public static func lift(_ value: UInt64) throws -> Epoch { + return try FfiConverterUInt64.lift(value) + } + + public static func lower(_ value: Epoch) -> UInt64 { + return FfiConverterUInt64.lower(value) + } +} + +public func FfiConverterTypeEpoch_lift(_ value: UInt64) throws -> Epoch { + return try FfiConverterTypeEpoch.lift(value) +} + +public func FfiConverterTypeEpoch_lower(_ value: Epoch) -> UInt64 { + return FfiConverterTypeEpoch.lower(value) +} + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias InnerDecimal = String +public struct FfiConverterTypeInnerDecimal: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> InnerDecimal { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: InnerDecimal, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> InnerDecimal { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: InnerDecimal) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + +public func FfiConverterTypeInnerDecimal_lift(_ value: RustBuffer) throws -> InnerDecimal { + return try FfiConverterTypeInnerDecimal.lift(value) +} + +public func FfiConverterTypeInnerDecimal_lower(_ value: InnerDecimal) -> RustBuffer { + return FfiConverterTypeInnerDecimal.lower(value) +} + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Nonce = UInt32 +public struct FfiConverterTypeNonce: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Nonce { + return try FfiConverterUInt32.read(from: &buf) + } + + public static func write(_ value: Nonce, into buf: inout [UInt8]) { + return FfiConverterUInt32.write(value, into: &buf) + } + + public static func lift(_ value: UInt32) throws -> Nonce { + return try FfiConverterUInt32.lift(value) + } + + public static func lower(_ value: Nonce) -> UInt32 { + return FfiConverterUInt32.lower(value) + } +} + +public func FfiConverterTypeNonce_lift(_ value: UInt32) throws -> Nonce { + return try FfiConverterTypeNonce.lift(value) +} + +public func FfiConverterTypeNonce_lower(_ value: Nonce) -> UInt32 { + return FfiConverterTypeNonce.lower(value) +} + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -9654,6 +10869,251 @@ public func bagOfBytesPrependDeadbeef(inFrontOf: BagOfBytes) -> BagOfBytes { ) } +/** + * Returns `decimal.abs()`, panics if `decimal` is `Decimal192::MIN` + */ +public func decimalAbs(decimal: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_abs( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * `lhs + rhs`` + */ +public func decimalAdd(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_add( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * Clamps `decimal` to zero, i.e. `max(decimal, 0)` + */ +public func decimalClampedToZero(decimal: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_clamped_to_zero( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * `lhs / rhs`` + */ +public func decimalDiv(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_div( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * `lhs > rhs` + */ +public func decimalGreaterThan(lhs: Decimal192, rhs: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_greater_than( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * `lhs >= rhs` + */ +public func decimalGreaterThanOrEqual(lhs: Decimal192, rhs: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_greater_than_or_equal( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * Whether this decimal is negative. + */ +public func decimalIsNegative(decimal: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_is_negative( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * Whether this decimal is positive. + */ +public func decimalIsPositive(decimal: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_is_positive( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * Whether this decimal is zero. + */ +public func decimalIsZero(decimal: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_is_zero( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * `lhs < rhs` + */ +public func decimalLessThan(lhs: Decimal192, rhs: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_less_than( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * `lhs <= rhs` + */ +public func decimalLessThanOrEqual(lhs: Decimal192, rhs: Decimal192) -> Bool { + return try! FfiConverterBool.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_less_than_or_equal( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * The maximum possible value of `Decimal192`, being: + * `3138550867693340381917894711603833208051.177722232017256447` + */ +public func decimalMax() -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_max($0) + } + ) +} + +/** + * The minimum possible value of `Decimal192`, being: + * `-3138550867693340381917894711603833208051.177722232017256448` + */ +public func decimalMin() -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_min($0) + } + ) +} + +/** + * `lhs * rhs`` + */ +public func decimalMul(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_mul( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * Negates the `decimal` + */ +public func decimalNeg(decimal: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_neg( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + +/** + * Rounds this number to the specified decimal places. + * + * # Panics + * - Panic if the number of decimal places is not within [0..SCALE(=18)] + */ +public func decimalRound(decimal: Decimal192, decimalPlaces: Int32, roundingMode: RoundingMode) throws -> Decimal192 { + return try FfiConverterTypeDecimal192.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_decimal_round( + FfiConverterTypeDecimal192.lower(decimal), + FfiConverterInt32.lower(decimalPlaces), + FfiConverterTypeRoundingMode.lower(roundingMode), $0 + ) + } + ) +} + +/** + * `lhs - rhs`` + */ +public func decimalSub(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_sub( + FfiConverterTypeDecimal192.lower(lhs), + FfiConverterTypeDecimal192.lower(rhs), $0 + ) + } + ) +} + +/** + * `decimal.to_string()` + */ +public func decimalToString(decimal: Decimal192) -> String { + return try! FfiConverterString.lift( + try! rustCall { + uniffi_sargon_fn_func_decimal_to_string( + FfiConverterTypeDecimal192.lower(decimal), $0 + ) + } + ) +} + public func ed25519PublicKeyToBytes(publicKey: Ed25519PublicKey) -> Data { return try! FfiConverterData.lift( try! rustCall { @@ -9828,6 +11288,115 @@ public func newBagOfBytesPlaceholderFade() -> BagOfBytes { ) } +/** + * Creates the Decimal192 `10^exponent` + */ +public func newDecimalExponent(exponent: UInt8) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_exponent( + FfiConverterUInt8.lower(exponent), $0 + ) + } + ) +} + +/** + * Creates a new `Decimal192` from a f32 float, it does + * so by first converting the float to a String, using + * Rust's `to_string` on the float. + */ +public func newDecimalFromF32(value: Float) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_from_f32( + FfiConverterFloat.lower(value), $0 + ) + } + ) +} + +/** + * Tries to creates a new `Decimal192` from a formatted String for + * a specific locale. + */ +public func newDecimalFromFormattedString(formattedString: String, locale: LocaleConfig) throws -> Decimal192 { + return try FfiConverterTypeDecimal192.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_decimal_from_formatted_string( + FfiConverterString.lower(formattedString), + FfiConverterTypeLocaleConfig.lower(locale), $0 + ) + } + ) +} + +/** + * Creates a new `Decimal192` from a i32 integer. + */ +public func newDecimalFromI32(value: Int32) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_from_i32( + FfiConverterInt32.lower(value), $0 + ) + } + ) +} + +/** + * Creates a new `Decimal192` from a i64 integer. + */ +public func newDecimalFromI64(value: Int64) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_from_i64( + FfiConverterInt64.lower(value), $0 + ) + } + ) +} + +/** + * Tries to creates a new `Decimal192` from a String, throws a `CommonError` + * if the `string` was not a valid Decimal192. + */ +public func newDecimalFromString(string: String) throws -> Decimal192 { + return try FfiConverterTypeDecimal192.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_decimal_from_string( + FfiConverterString.lower(string), $0 + ) + } + ) +} + +/** + * Creates a new `Decimal192` from a u32 integer. + */ +public func newDecimalFromU32(value: UInt32) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_from_u32( + FfiConverterUInt32.lower(value), $0 + ) + } + ) +} + +/** + * Creates a new `Decimal192` from a u64 integer. + */ +public func newDecimalFromU64(value: UInt64) -> Decimal192 { + return try! FfiConverterTypeDecimal192.lift( + try! rustCall { + uniffi_sargon_fn_func_new_decimal_from_u64( + FfiConverterUInt64.lower(value), $0 + ) + } + ) +} + public func newDisplayName(name: String) throws -> DisplayName { return try FfiConverterTypeDisplayName.lift( rustCallWithError(FfiConverterTypeCommonError.lift) { @@ -9951,6 +11520,76 @@ public func newHex32BytesFrom(bytes: Data) throws -> Hex32Bytes { ) } +public func newHex32BytesFromBag(bagOfBytes: BagOfBytes) throws -> Hex32Bytes { + return try FfiConverterTypeHex32Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex32_bytes_from_bag( + FfiConverterTypeBagOfBytes.lower(bagOfBytes), $0 + ) + } + ) +} + +public func newHex33BytesFrom(bytes: Data) throws -> Hex33Bytes { + return try FfiConverterTypeHex33Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex33_bytes_from( + FfiConverterData.lower(bytes), $0 + ) + } + ) +} + +public func newHex33BytesFromBag(bagOfBytes: BagOfBytes) throws -> Hex33Bytes { + return try FfiConverterTypeHex33Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex33_bytes_from_bag( + FfiConverterTypeBagOfBytes.lower(bagOfBytes), $0 + ) + } + ) +} + +public func newHex64BytesFrom(bytes: Data) throws -> Hex64Bytes { + return try FfiConverterTypeHex64Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex64_bytes_from( + FfiConverterData.lower(bytes), $0 + ) + } + ) +} + +public func newHex64BytesFromBag(bagOfBytes: BagOfBytes) throws -> Hex64Bytes { + return try FfiConverterTypeHex64Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex64_bytes_from_bag( + FfiConverterTypeBagOfBytes.lower(bagOfBytes), $0 + ) + } + ) +} + +public func newHex65BytesFrom(bytes: Data) throws -> Hex65Bytes { + return try FfiConverterTypeHex65Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex65_bytes_from( + FfiConverterData.lower(bytes), $0 + ) + } + ) +} + +public func newHex65BytesFromBag(bagOfBytes: BagOfBytes) throws -> Hex65Bytes { + return try FfiConverterTypeHex65Bytes.lift( + rustCallWithError(FfiConverterTypeCommonError.lift) { + uniffi_sargon_fn_func_new_hex65_bytes_from_bag( + FfiConverterTypeBagOfBytes.lower(bagOfBytes), $0 + ) + } + ) +} + public func newPrivateHdFactorSource(entropy: Data, walletClientModel: WalletClientModel) throws -> PrivateHierarchicalDeterministicFactorSource { return try FfiConverterTypePrivateHierarchicalDeterministicFactorSource.lift( rustCallWithError(FfiConverterTypeCommonError.lift) { @@ -10139,6 +11778,60 @@ private var initializationResult: InitializationResult { if uniffi_sargon_checksum_func_bag_of_bytes_prepend_deadbeef() != 6895 { return InitializationResult.apiChecksumMismatch } + if uniffi_sargon_checksum_func_decimal_abs() != 44067 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_add() != 697 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_clamped_to_zero() != 64928 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_div() != 19189 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_greater_than() != 48331 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_greater_than_or_equal() != 2033 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_is_negative() != 64178 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_is_positive() != 6024 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_is_zero() != 49608 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_less_than() != 56621 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_less_than_or_equal() != 59719 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_max() != 7974 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_min() != 12755 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_mul() != 26895 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_neg() != 41456 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_round() != 8832 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_sub() != 35224 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_decimal_to_string() != 38303 { + return InitializationResult.apiChecksumMismatch + } if uniffi_sargon_checksum_func_ed25519_public_key_to_bytes() != 22077 { return InitializationResult.apiChecksumMismatch } @@ -10196,6 +11889,30 @@ private var initializationResult: InitializationResult { if uniffi_sargon_checksum_func_new_bag_of_bytes_placeholder_fade() != 10852 { return InitializationResult.apiChecksumMismatch } + if uniffi_sargon_checksum_func_new_decimal_exponent() != 14207 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_f32() != 3137 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_formatted_string() != 41914 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_i32() != 18727 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_i64() != 60170 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_string() != 44503 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_u32() != 9150 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_decimal_from_u64() != 426 { + return InitializationResult.apiChecksumMismatch + } if uniffi_sargon_checksum_func_new_display_name() != 12714 { return InitializationResult.apiChecksumMismatch } @@ -10235,6 +11952,27 @@ private var initializationResult: InitializationResult { if uniffi_sargon_checksum_func_new_hex32_bytes_from() != 59466 { return InitializationResult.apiChecksumMismatch } + if uniffi_sargon_checksum_func_new_hex32_bytes_from_bag() != 38499 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex33_bytes_from() != 56282 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex33_bytes_from_bag() != 31999 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex64_bytes_from() != 2534 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex64_bytes_from_bag() != 5701 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex65_bytes_from() != 50943 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_func_new_hex65_bytes_from_bag() != 55989 { + return InitializationResult.apiChecksumMismatch + } if uniffi_sargon_checksum_func_new_private_hd_factor_source() != 4402 { return InitializationResult.apiChecksumMismatch } @@ -10307,6 +12045,12 @@ private var initializationResult: InitializationResult { if uniffi_sargon_checksum_method_wallet_json_snapshot() != 24850 { return InitializationResult.apiChecksumMismatch } + if uniffi_sargon_checksum_method_wallet_main_bdfs_mnemonic_with_passphrase() != 59906 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_sargon_checksum_method_wallet_mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id() != 48090 { + return InitializationResult.apiChecksumMismatch + } if uniffi_sargon_checksum_method_wallet_profile() != 5221 { return InitializationResult.apiChecksumMismatch } diff --git a/examples/iOS/Planbok.xcworkspace/contents.xcworkspacedata b/examples/iOS/Planbok.xcworkspace/contents.xcworkspacedata index 362494596..92d35cb57 100644 --- a/examples/iOS/Planbok.xcworkspace/contents.xcworkspacedata +++ b/examples/iOS/Planbok.xcworkspace/contents.xcworkspacedata @@ -22,12 +22,24 @@ name = "Extensions"> + name = "WrapFunctions"> + location = "group:/Users/sajjon/Developer/Radix/sargon/apple/Sources/Sargon/Extensions/Methods/SecureStorageKey+Wrap+Functions.swift"> + location = "group:/Users/sajjon/Developer/Radix/sargon/apple/Sources/Sargon/Extensions/Methods/Decimal192+Wrap+Functions.swift"> + + + + + + + + + + + + () + public var nameAccount: NameNewAccountFeature.State + + public init(walletHolder: WalletHolder) { + self.walletHolder = walletHolder + self.nameAccount = NameNewAccountFeature.State(walletHolder: walletHolder) + } + + public init(wallet: Wallet) { + self.init(walletHolder: .init(wallet: wallet)) + } + } + + public enum Action { + public enum DelegateAction { + case createdAccount + } + case path(StackAction) + case nameAccount(NameNewAccountFeature.Action) + case delegate(DelegateAction) + } + + public struct View: SwiftUI.View { + @Bindable var store: StoreOf + public init(store: StoreOf) { + self.store = store + } + public var body: some SwiftUI.View { + NavigationStack(path: $store.scope(state: \.path, action: \.path)) { + NameNewAccountFeature.View( + store: store.scope(state: \.nameAccount, action: \.nameAccount) + ) + } destination: { store in + switch store.state { + case .selectGradient: + if let store = store.scope(state: \.selectGradient, action: \.selectGradient) { + SelectGradientFeature.View(store: store) + } + } + } + } + } + + public init() {} + + public var body: some ReducerOf { + Scope(state: \.nameAccount, action: \.nameAccount) { + NameNewAccountFeature() + } + + Reduce { state, action in + switch action { + + case let .nameAccount(.delegate(.named(name))): + state.path.append(.selectGradient(.init(name: name))) + return .none + + case .path(let pathAction): + switch pathAction { + + case let .element( + id: _, + action: .selectGradient(.delegate(.selected(appearanceID, displayName))) + ): + do { + let wallet = state.walletHolder.wallet + var account = try wallet.createNewAccount( + networkId: .mainnet, + name: displayName + ) + account.appearanceId = appearanceID + + try wallet.addAccount(account: account) + + return .send(.delegate(.createdAccount)) + + } catch { + fatalError("TODO error handling: \(error)") + } + + case .element(id: _, action: _): + return .none + case .popFrom(id: _): + return .none + case .push(id: _, state: _): + return .none + } + return .none + + case .nameAccount(.view): + return .none + + case .delegate: + return .none + } + } + .forEach(\.path, action: \.path) + } +} diff --git a/examples/iOS/Sources/Planbok/Features/CreateAccountFeature.swift b/examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift similarity index 53% rename from examples/iOS/Sources/Planbok/Features/CreateAccountFeature.swift rename to examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift index 7fe903449..7030ff97e 100644 --- a/examples/iOS/Sources/Planbok/Features/CreateAccountFeature.swift +++ b/examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift @@ -1,5 +1,5 @@ @Reducer -public struct CreateAccountFeature { +public struct NameNewAccountFeature { @ObservableState public struct State: Equatable { @@ -14,22 +14,30 @@ public struct CreateAccountFeature { } } - public enum Action { - case accountNameChanged(String) - case createAccountButtonTapped - case createdAccount + public enum Action: ViewAction { + public enum Delegate { + case named(DisplayName) + } + @CasePathable + public enum ViewAction { + case accountNameChanged(String) + case continueButtonTapped + } + case delegate(Delegate) + case view(ViewAction) } + @ViewAction(for: NameNewAccountFeature.self) public struct View: SwiftUI.View { - @Bindable var store: StoreOf - public init(store: StoreOf) { + @Bindable public var store: StoreOf + public init(store: StoreOf) { self.store = store } public var body: some SwiftUI.View { VStack { - Text("Create Account").font(.largeTitle) + Text("Name Account").font(.largeTitle) Spacer() - LabeledTextField(label: "Account Name", text: $store.accountName.sending(\.accountNameChanged)) + LabeledTextField(label: "Account Name", text: $store.accountName.sending(\.view.accountNameChanged)) if let error = store.state.errorMessage { Text("\(error)") .foregroundStyle(Color.red) @@ -37,10 +45,10 @@ public struct CreateAccountFeature { .fontWeight(.bold) } Spacer() - Button("Create Account") { - store.send(.createAccountButtonTapped) + Button("Continue") { + send(.continueButtonTapped) } - + .buttonStyle(.borderedProminent) } .padding() } @@ -51,44 +59,25 @@ public struct CreateAccountFeature { public var body: some ReducerOf { Reduce { state, action in switch action { - case let .accountNameChanged(name): + case let .view(.accountNameChanged(name)): state.errorMessage = nil state.accountName = name return .none - case .createAccountButtonTapped: + case .view(.continueButtonTapped): state.errorMessage = nil do { let displayName = try DisplayName(validating: state.accountName) - do { - _ = try state.walletHolder.wallet.createAndSaveNewAccount( - networkId: .mainnet, - name: displayName - ) - return .send(.createdAccount) - } catch { - state.errorMessage = "Failed to create and save account. This is really bad." - return .none - } + return .send(.delegate(.named(displayName))) } catch { state.errorMessage = "Invalid DisplayName, can't be empty or too long." return .none } - case .createdAccount: + case .delegate: return .none + } } } } - -public struct LabeledTextField: SwiftUI.View { - public let label: LocalizedStringKey - @Binding public var text: String - public var body: some View { - VStack { - Text(label) - TextField(label, text: $text) - } - } -} diff --git a/examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift b/examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift new file mode 100644 index 000000000..da4352d5d --- /dev/null +++ b/examples/iOS/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift @@ -0,0 +1,105 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-02-16. +// + +import Foundation + +@Reducer +public struct SelectGradientFeature { + + @ObservableState + public struct State: Equatable { + public let name: DisplayName + public var gradient: AppearanceID + public init( + name: DisplayName, + gradient: AppearanceID = AppearanceID.allCases.first! + ) { + self.name = name + self.gradient = gradient + } + } + + @CasePathable + public enum Action: ViewAction { + public enum Delegate { + case selected(AppearanceID, DisplayName) + } + @CasePathable + public enum ViewAction { + case selectedGradient(AppearanceID) + case confirmedGradientButtonTapped + } + case view(ViewAction) + case delegate(Delegate) + } + + @ViewAction(for: SelectGradientFeature.self) + public struct View: SwiftUI.View { + public let store: StoreOf + public init(store: StoreOf) { + self.store = store + } + public var body: some SwiftUI.View { + VStack { + Text("Select account gradient").font(.title) + ScrollView { + let height: CGFloat = 20 + ForEach(AppearanceID.allCases) { appearanceID in + let isSelected = appearanceID == store.state.gradient + Button.init(action: { send(.selectedGradient(appearanceID)) }, label: { + HStack { + Text("Gradient \(String(describing: appearanceID))") + .font(isSelected ? .headline : .subheadline) + .fontWeight(isSelected ? .bold : .regular) + + Spacer() + + if isSelected { + Image(systemName: "checkmark") + .resizable() + .scaledToFit() + } + } + }) + + .foregroundColor(.app.white) + .frame(maxWidth: .infinity, idealHeight: height, alignment: .leading) + .padding() + .background(appearanceID.gradient) + .cornerRadius(height) + + } + } + Button("Confirm Gradient") { + send(.confirmedGradientButtonTapped) + } + .buttonStyle(.borderedProminent) + } + .padding() + } + } + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + + case let .view(.selectedGradient(gradient)): + state.gradient = gradient + return .none + + case .view(.confirmedGradientButtonTapped): + return .send(.delegate(.selected(state.gradient, state.name))) + + default: + return .none + + } + } + } +} diff --git a/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift new file mode 100644 index 000000000..ab2a65e70 --- /dev/null +++ b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift @@ -0,0 +1,119 @@ +@Reducer +public struct OnboardingFeature { + + @Reducer(state: .equatable) + public enum Path { + case writeDownMnemonic(WriteDownMnemonicFeature) + } + + @Reducer(state: .equatable) + public enum Destination { + case createAccount(CreateAccountFlowFeature) + } + + @ObservableState + public struct State: Equatable { + public let walletHolder: WalletHolder + public var path = StackState() + public var welcome: WelcomeFeature.State + + @Presents var destination: Destination.State? + + public init(walletHolder: WalletHolder) { + self.walletHolder = walletHolder + self.welcome = WelcomeFeature.State() + } + + public init(wallet: Wallet) { + self.init(walletHolder: .init(wallet: wallet)) + } + } + + @CasePathable + public enum Action { + @CasePathable + public enum DelegateAction { + case createdAccount(with: WalletHolder) + } + + case destination(PresentationAction) + case path(StackAction) + case welcome(WelcomeFeature.Action) + case delegate(DelegateAction) + } + + public init() {} + + public var body: some ReducerOf { + Scope(state: \.welcome, action: \.welcome) { + WelcomeFeature() + } + Reduce { state, action in + switch action { + + case .path(let pathAction): + switch pathAction { + case .element(id: _, action: .writeDownMnemonic(.delegate(.done))): + return .send(.delegate(.createdAccount(with: state.walletHolder))) + case .popFrom(id: _): + return .none + case .push(id: _, state: _): + return .none + default: + return .none + } + case .welcome(.delegate(.done)): + state.destination = .createAccount(CreateAccountFlowFeature.State(walletHolder: state.walletHolder)) + return .none + case .welcome(.view): + return .none + case .delegate: + return .none + case .destination(.presented(.createAccount(.delegate(.createdAccount)))): + state.destination = nil + state.path.append(.writeDownMnemonic(.init(walletHolder: state.walletHolder))) + return .none + + default: + return .none + } + } + .forEach(\.path, action: \.path) + .ifLet(\.$destination, action: \.destination) + } + + public struct View: SwiftUI.View { + @Bindable var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + NavigationStack(path: $store.scope(state: \.path, action: \.path)) { + WelcomeFeature.View( + store: store.scope(state: \.welcome, action: \.welcome) + ) + } destination: { store in + switch store.case { + case .writeDownMnemonic: + if let store = store.scope(state: \.writeDownMnemonic, action: \.writeDownMnemonic) { + WriteDownMnemonicFeature.View(store: store) + } + } + } + .sheet( + item: $store.scope( + state: \.destination?.createAccount, + action: \.destination.createAccount + ) + ) { store in + CreateAccountFlowFeature.View(store: store) + } + + } + + } + + +} diff --git a/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift new file mode 100644 index 000000000..e95e2e6f2 --- /dev/null +++ b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift @@ -0,0 +1,70 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-02-16. +// + +import Foundation + +@Reducer +public struct WelcomeFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public init() {} + } + + public enum Action: ViewAction { + public enum DelegateAction { + case done + } + public enum ViewAction { + case continueButtonTapped + } + case delegate(DelegateAction) + case view(ViewAction) + } + + @ViewAction(for: WelcomeFeature.self) + public struct View: SwiftUI.View { + public let store: StoreOf + + public var body: some SwiftUI.View { + VStack { + Text("Welcome to Sargon demo").font(.title) + ScrollView { + Text( +""" +This tiny app demonstrates how Sargon written in Rust can be used in an iOS app, thanks to the Swift bindings that we have generated with UniFFI. + +The build artifacts of UniFFI are have three major components: +1) A set of binaries we have grouped to together with lipo and put in a .xcframework vendored as a binaryTarget in the Sargon Swift Package. + +2) A single HUGE Swift file with Swift models exported from Rust and with function pointers that use the binaryTarget in the Sargon Swift Package + +3) A set of Swift extension's on the bindgen generated Swift models, e.g. making `Decimal192` conform to `ExpressibleByIntegerLiteral` which of course is a pure Swift construct. Also marking all types as `Sendable` and `CustomStringConvertible` making use of their `std::fmt::Display` impl in Rust land. +""" + ) + .padding() + } + Button("Start") { + send(.continueButtonTapped) + } + } + .padding() + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.continueButtonTapped): + .send(.delegate(.done)) + case .delegate: + .none + } + } + } +} diff --git a/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift new file mode 100644 index 000000000..44b0350d3 --- /dev/null +++ b/examples/iOS/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift @@ -0,0 +1,84 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-02-16. +// + +import Foundation + +@Reducer +public struct WriteDownMnemonicFeature { + + @Dependency(\.keychain) var keychain + + public init() {} + + @ObservableState + public struct State: Equatable { + public let walletHolder: WalletHolder + public var mnemonic: String? + public init(walletHolder: WalletHolder) { + self.walletHolder = walletHolder + } + } + + public enum Action: ViewAction { + public enum DelegateAction { + case done + } + public enum ViewAction { + case revealMnemonicButtonTapped + case continueButtonTapped + } + case delegate(DelegateAction) + case view(ViewAction) + } + + @ViewAction(for: WriteDownMnemonicFeature.self) + public struct View: SwiftUI.View { + public let store: StoreOf + + public var body: some SwiftUI.View { + VStack { + Text("Write down your mnemonic on a piece of paper and put it in a safe") + .font(.title) + Spacer() + if let mnemonic = store.state.mnemonic { + Text("`\(mnemonic)`") + .border(.yellow) + } else { + Button("Reveal") { + send(.revealMnemonicButtonTapped) + } + } + Spacer() + Button("Continue") { + send(.continueButtonTapped) + } + } + .padding() + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.revealMnemonicButtonTapped): + let wallet = state.walletHolder.wallet + + do { + let bdfsMnemonic = try wallet.mainBdfsMnemonicWithPassphrase() + state.mnemonic = bdfsMnemonic.mnemonic.phrase + } catch { + fatalError("handle error: \(error)") + } + return .none + case .view(.continueButtonTapped): + return .send(.delegate(.done)) + case .delegate: + return .none + } + } + } +} diff --git a/examples/iOS/Sources/Planbok/Features/MainFeature.swift b/examples/iOS/Sources/Planbok/Features/MainFeature.swift index 9eb80dc7b..34d322dbc 100644 --- a/examples/iOS/Sources/Planbok/Features/MainFeature.swift +++ b/examples/iOS/Sources/Planbok/Features/MainFeature.swift @@ -1,6 +1,47 @@ @Reducer public struct MainFeature { + @Reducer(state: .equatable) + public enum Destination { + case createAccount(CreateAccountFlowFeature) + case alert(AlertState) + + public enum Alert { + case confirmedDeleteWallet + } + } + + @ObservableState + public struct State: Equatable { + + @Presents var destination: Destination.State? + + public var accounts: AccountsFeature.State + public let walletHolder: WalletHolder + + public init(walletHolder: WalletHolder) { + self.walletHolder = walletHolder + self.accounts = AccountsFeature.State(walletHolder: walletHolder) + } + + public init(wallet: Wallet) { + self.init(walletHolder: .init(wallet: wallet)) + } + } + + @CasePathable + public enum Action { + @CasePathable + public enum DelegateAction { + case deletedWallet + } + case destination(PresentationAction) + case accounts(AccountsFeature.Action) + + case delegate(DelegateAction) + + } + @Dependency(\.keychain) var keychain public init() {} @@ -27,7 +68,7 @@ public struct MainFeature { case .accounts(.delegate(.createNewAccount)): state.destination = .createAccount( - CreateAccountFeature.State( + CreateAccountFlowFeature.State( walletHolder: state.walletHolder ) ) @@ -45,7 +86,7 @@ public struct MainFeature { fatalError("Fix error handling, error: \(error)") } - case .destination(.presented(.createAccount(.createdAccount))): + case .destination(.presented(.createAccount(.delegate(.createdAccount)))): state.destination = nil state.accounts.refresh() // FIXME: we really do not want this. return .none @@ -57,44 +98,7 @@ public struct MainFeature { .ifLet(\.$destination, action: \.destination) } - @Reducer(state: .equatable) - public enum Destination { - case createAccount(CreateAccountFeature) - case alert(AlertState) - - public enum Alert { - case confirmedDeleteWallet - } - } - - @ObservableState - public struct State: Equatable { - - @Presents var destination: Destination.State? - - public var accounts: AccountsFeature.State - public let walletHolder: WalletHolder - - public init(walletHolder: WalletHolder) { - self.walletHolder = walletHolder - self.accounts = AccountsFeature.State(walletHolder: walletHolder) - } - - public init(wallet: Wallet) { - self.init(walletHolder: .init(wallet: wallet)) - } - } - - public enum Action { - public enum DelegateAction { - case deletedWallet - } - case destination(PresentationAction) - case accounts(AccountsFeature.Action) - - case delegate(DelegateAction) - - } + public struct View: SwiftUI.View { @@ -121,7 +125,7 @@ public struct MainFeature { action: \.destination.createAccount ) ) { store in - CreateAccountFeature.View(store: store) + CreateAccountFlowFeature.View(store: store) } .alert($store.scope(state: \.destination?.alert, action: \.destination.alert)) } diff --git a/examples/iOS/Sources/Planbok/Features/OnboardingFeature.swift b/examples/iOS/Sources/Planbok/Features/OnboardingFeature.swift deleted file mode 100644 index b651ab6e0..000000000 --- a/examples/iOS/Sources/Planbok/Features/OnboardingFeature.swift +++ /dev/null @@ -1,64 +0,0 @@ -@Reducer -public struct OnboardingFeature { - - - @Reducer(state: .equatable) - public enum Path { - case createAccount(CreateAccountFeature) - } - - @ObservableState - public struct State: Equatable { - public var path = StackState() - public var createAccount: CreateAccountFeature.State - - public init(walletHolder: WalletHolder) { - self.createAccount = CreateAccountFeature.State(walletHolder: walletHolder) - } - - public init(wallet: Wallet) { - self.init(walletHolder: .init(wallet: wallet)) - } - } - - public enum Action { - case path(StackAction) - case createAccount(CreateAccountFeature.Action) - - case createdAccount(with: WalletHolder) - } - - public struct View: SwiftUI.View { - @Bindable var store: StoreOf - public init(store: StoreOf) { - self.store = store - } - public var body: some SwiftUI.View { - NavigationStack(path: $store.scope(state: \.path, action: \.path)) { - CreateAccountFeature.View( - store: store.scope(state: \.createAccount, action: \.createAccount) - ) - } destination: { _ in - Text("Never seen") - } - } - } - - public init() {} - - public var body: some ReducerOf { - Scope(state: \.createAccount, action: \.createAccount) { - CreateAccountFeature() - } - Reduce { state, action in - switch action { - - case .createAccount(.createdAccount): - return .send(.createdAccount(with: state.createAccount.walletHolder)) - default: - return .none - } - } - .forEach(\.path, action: \.path) - } -} diff --git a/examples/iOS/Sources/Planbok/View/Views/AccountView.swift b/examples/iOS/Sources/Planbok/View/Views/AccountView.swift index 1e116b1cf..aea5b0987 100644 --- a/examples/iOS/Sources/Planbok/View/Views/AccountView.swift +++ b/examples/iOS/Sources/Planbok/View/Views/AccountView.swift @@ -13,7 +13,6 @@ public struct AccountView: SwiftUI.View { AddressView(account.address) .foregroundColor(.app.whiteTransparent) - .foregroundColor(.app.whiteTransparent) } .padding(.horizontal, .medium1) .padding(.vertical, .medium2) diff --git a/examples/iOS/Sources/Planbok/View/Views/LabeledTextField.swift b/examples/iOS/Sources/Planbok/View/Views/LabeledTextField.swift new file mode 100644 index 000000000..1c21bdfae --- /dev/null +++ b/examples/iOS/Sources/Planbok/View/Views/LabeledTextField.swift @@ -0,0 +1,14 @@ + +public struct LabeledTextField: SwiftUI.View { + + public let label: LocalizedStringKey + @Binding public var text: String + + public var body: some View { + VStack(alignment: .leading) { + Text(label).padding(.leading, 5) + TextField(label, text: $text) + } + .textFieldStyle(.roundedBorder) + } +} diff --git a/scripts/ios/ensure-not-local.sh b/scripts/ios/ensure-not-local.sh new file mode 100755 index 000000000..5fb08a6ec --- /dev/null +++ b/scripts/ios/ensure-not-local.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +set -e +set -u + +if grep -q "let useLocalFramework = true" Package.swift; then + echo "You MUST let useLocalFramework be set to 'false'. Else Swift release will fail." + exit 1; +fi + +exit 0; \ No newline at end of file diff --git a/scripts/ios/release.sh b/scripts/ios/release.sh index cca2953fc..b94532dac 100755 --- a/scripts/ios/release.sh +++ b/scripts/ios/release.sh @@ -13,8 +13,7 @@ echo "🚢 Start of '$me' (see: '$DIR/$me')" echo "🚢 PWD: $PWD" echo "🚢 Ensure 'useLocalFramework' is set to 'false' in Package.swift" -sed -i '' 's/let useLocalFramework = true/let useLocalFramework = false/' Package.swift - +sh ./scripts/ios/ensure-not-local.sh || exit $? `git fetch --prune --tags` function last_tag() { diff --git a/src/core/error/common_error.rs b/src/core/error/common_error.rs index e62ea2703..cef62787b 100644 --- a/src/core/error/common_error.rs +++ b/src/core/error/common_error.rs @@ -52,8 +52,8 @@ pub enum CommonError { #[error("String not hex {bad_value}")] StringNotHex { bad_value: String } = 10011, - #[error("Invalid byte count, expected 32, found: {bad_value}")] - InvalidByteCountExpected32 { bad_value: u64 } = 10012, + #[error("Invalid byte count, expected {expected}, found: {found}")] + InvalidByteCount { expected: u64, found: u64 } = 10012, #[error("Invalid BIP32 path '{bad_value}'.")] InvalidBIP32Path { bad_value: String } = 10013, @@ -315,6 +315,9 @@ pub enum CommonError { #[error("Invalid UUID (v4), got: {bad_value}")] InvalidUUIDv4 { bad_value: String } = 10086, + + #[error("Unrecognized Locale Identifier: {bad_value}")] + UnrecognizedLocaleIdentifier { bad_value: String } = 10087, } /* diff --git a/src/core/secure_random_bytes.rs b/src/core/secure_random_bytes.rs index d4ec3c8f3..f94ecc9f6 100644 --- a/src/core/secure_random_bytes.rs +++ b/src/core/secure_random_bytes.rs @@ -17,6 +17,13 @@ pub fn generate_32_bytes() -> Vec { generate_bytes::<32>() } +/// Generates `64` random bytes using a cryptographically +/// secure random generator and returns these bytes as +/// a Vec. +pub fn generate_64_bytes() -> Vec { + generate_bytes::<64>() +} + #[cfg(test)] mod tests { use std::collections::HashSet; diff --git a/src/core/types/bag_of_bytes.rs b/src/core/types/bag_of_bytes.rs index 1a43d8c72..fc9a3e1e8 100644 --- a/src/core/types/bag_of_bytes.rs +++ b/src/core/types/bag_of_bytes.rs @@ -346,12 +346,12 @@ mod tests { #[test] fn default_is_empty() { assert_eq!(BagOfBytes::default(), BagOfBytes::new()); - assert_eq!(BagOfBytes::default().is_empty(), true); + assert!(BagOfBytes::default().is_empty()); } #[test] fn is_empty() { - assert_eq!(BagOfBytes::placeholder().is_empty(), false); + assert!(!BagOfBytes::placeholder().is_empty()); } #[test] @@ -421,7 +421,7 @@ mod tests { #[test] fn from_vec_roundtrip() { let vec = Vec::from([0u8; 32]); - let sut: BagOfBytes = vec.clone().try_into().unwrap(); + let sut: BagOfBytes = vec.clone().into(); assert_eq!(sut.to_vec(), vec); } diff --git a/src/core/types/decimal.rs b/src/core/types/decimal.rs deleted file mode 100644 index bc4d2155f..000000000 --- a/src/core/types/decimal.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::prelude::*; -use radix_engine_common::math::Decimal as ScryptoDecimal; - -// FIXME: Use RET's type! -#[derive( - Clone, - Debug, - Eq, - Default, - SerializeDisplay, - DeserializeFromStr, - uniffi::Record, -)] -pub struct Decimal192 { - base10_string: String, -} - -impl PartialEq for Decimal192 { - fn eq(&self, other: &Self) -> bool { - self.native().eq(&other.native()) - } -} -impl std::hash::Hash for Decimal192 { - fn hash(&self, state: &mut H) { - self.native().hash(state); - } -} -impl PartialOrd for Decimal192 { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Ord for Decimal192 { - fn cmp(&self, other: &Self) -> Ordering { - let lhs = &self.native(); - let rhs = &other.native(); - if lhs.eq(rhs) { - Ordering::Equal - } else if lhs.le(rhs) { - return Ordering::Less; - } else { - assert!(lhs.gt(rhs), "!(LHS == RHS || LHS < RHS), thus we expected LHS > RHS, but it was not. Most likely the implementation of RET's Decimal192 has changed, maybe to involve NaN?"); - return Ordering::Greater; - } - } -} -impl Decimal192 { - fn native(&self) -> ScryptoDecimal { - ScryptoDecimal::from_str(&self.base10_string).unwrap() - } - fn from_native(decimal: ScryptoDecimal) -> Self { - Self { - base10_string: decimal.to_string(), - } - } -} - -impl Decimal192 { - pub fn try_from_str(s: &str) -> Result { - Self::new(s.to_string()) - } -} - -impl std::fmt::Display for Decimal192 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.native()) - } -} - -impl FromStr for Decimal192 { - type Err = crate::CommonError; - - fn from_str(s: &str) -> Result { - s.parse::() - .map(Self::from_native) - .map_err(|_| CommonError::DecimalError) - } -} - -impl Decimal192 { - pub fn new(value: String) -> Result { - value.parse() - } - - pub fn zero() -> Self { - Self::from_native(ScryptoDecimal::zero()) - } - - pub fn one() -> Self { - Self::from_native(ScryptoDecimal::one()) - } - - pub fn is_zero(&self) -> bool { - self.native().is_zero() - } - - pub fn is_positive(&self) -> bool { - self.native().is_positive() - } - - pub fn is_negative(&self) -> bool { - self.native().is_negative() - } -} - -impl TryInto for &str { - type Error = crate::CommonError; - - fn try_into(self) -> Result { - Decimal192::try_from_str(self) - } -} - -impl TryFrom<&[u8]> for Decimal192 { - type Error = crate::CommonError; - - fn try_from(slice: &[u8]) -> Result { - ScryptoDecimal::try_from(slice) - .map(Self::from_native) - .map_err(|_| CommonError::DecimalError) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - - #[test] - fn eq() { - assert_eq!(Decimal192::zero(), Decimal192::zero()); - assert_eq!(Decimal192::one(), Decimal192::one()); - assert_eq!(Decimal192::zero(), Decimal192::try_from_str("0").unwrap()); - assert_eq!(Decimal192::one(), Decimal192::try_from_str("1").unwrap()); - } - - #[test] - fn inequality() { - assert_ne!(Decimal192::one(), Decimal192::zero()); - } - - #[test] - fn is_zero() { - assert!(Decimal192::zero().is_zero()); - assert!(!Decimal192::one().is_zero()); - } - - #[test] - fn is_positive() { - assert!(!Decimal192::zero().is_positive()); - assert!(Decimal192::one().is_positive()); - } - - #[test] - fn is_negative() { - assert!(Decimal192::try_from_str("-1").unwrap().is_negative()); - assert!(!Decimal192::zero().is_negative()); - assert!(!Decimal192::one().is_negative()); - } - - #[test] - fn not_less() { - assert!(Decimal192::zero() >= Decimal192::zero()); - assert!(Decimal192::one() >= Decimal192::one()); - assert!(Decimal192::one() >= Decimal192::zero()); - } - - #[test] - fn less() { - assert!(Decimal192::zero() < Decimal192::one()); - } - - #[test] - fn leq() { - assert!(Decimal192::zero() <= Decimal192::zero()); - assert!(Decimal192::one() <= Decimal192::one()); - - assert!(Decimal192::one() > Decimal192::zero()); - } - - #[test] - fn not_greater_than() { - assert!(Decimal192::zero() <= Decimal192::zero()); - assert!(Decimal192::one() <= Decimal192::one()); - assert!(Decimal192::zero() <= Decimal192::one()); - } - - #[test] - fn geq() { - assert!(Decimal192::zero() >= Decimal192::zero()); - assert!(Decimal192::one() >= Decimal192::one()); - - assert!(Decimal192::zero() < Decimal192::one()); - } - - #[test] - fn greater() { - assert!(Decimal192::one() > Decimal192::zero()); - } - - #[test] - fn from_str() { - let a = Decimal192::try_from_str( - "3138550867693340381917894711603833208051.177722232017256447", - ) - .unwrap(); - let b = Decimal192::try_from_str( - "3036550867693340381917894711603833208050.177722232017256447", - ) - .unwrap(); - assert!(a > b); - } - - #[test] - fn try_from_invalid_str() { - assert_eq!( - Decimal192::try_from_str("apabanan"), - Err(CommonError::DecimalError) - ); - } - - #[test] - fn try_from_invalid_bytes() { - assert_eq!( - Decimal192::try_from(generate_32_bytes().as_slice()), - Err(CommonError::DecimalError) - ); - } - - #[test] - fn try_from_valid_bytes() { - assert!(Decimal192::try_from(generate_bytes::<24>().as_slice()).is_ok()); - } - - #[test] - fn display() { - let s = "3138550867693340381917894711603833208051.177722232017256447"; - let a: Decimal192 = s.try_into().unwrap(); - assert_eq!(format!("{}", a), s); - } - - #[test] - fn json_roundtrip() { - let a: Decimal192 = - "3138550867693340381917894711603833208051.177722232017256447" - .try_into() - .unwrap(); - - assert_json_value_eq_after_roundtrip( - &a, - json!( - "3138550867693340381917894711603833208051.177722232017256447" - ), - ); - assert_json_roundtrip(&a); - assert_json_value_ne_after_roundtrip(&a, json!("3.1415")); - } - - #[test] - fn hash() { - let n = 100; - let set = (0..n) - .map(|_| { - Decimal192::try_from(generate_bytes::<24>().as_slice()).unwrap() - }) - .collect::>(); - assert_eq!(set.len(), n); - } -} diff --git a/src/core/types/decimal192.rs b/src/core/types/decimal192.rs new file mode 100644 index 000000000..f4238e15e --- /dev/null +++ b/src/core/types/decimal192.rs @@ -0,0 +1,1156 @@ +use crate::prelude::*; +use delegate::delegate; +use radix_engine_common::math::{ + Decimal as ScryptoDecimal192, RoundingMode as ScryptoRoundingMode, +}; + +/// UniFFI conversion for InnerDecimal using String as builtin. +impl crate::UniffiCustomTypeConverter for InnerDecimal { + type Builtin = String; + + #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests + fn into_custom(val: Self::Builtin) -> uniffi::Result { + val.parse::().map_err(|e| e.into()) + } + + #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +/// A 192 bit precision Decimal wrapping Scrypto's `Decimal` type, but +/// giving it UniFFI conversion. +/// +/// The purpose of this "Inner" Decimal is ensuring that the `Decimal` type +/// is not exported as a `String` in FFI land (Swift/Kotlin). The current +/// design ensure that `Decimal` is converted into a Swift `struct` / Kotlin +/// `data class` that **has** a `inner: String` +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + derive_more::Display, +)] +pub struct InnerDecimal(pub(crate) ScryptoDecimal192); +impl FromStr for InnerDecimal { + type Err = crate::CommonError; + + fn from_str(s: &str) -> Result { + ScryptoDecimal192::from_str(s) + .map(InnerDecimal) + .map_err(|_| CommonError::DecimalError) + } +} + +/// `Decimal192` represents a 192 bit representation of a fixed-scale decimal number. +/// +/// The finite set of values are of the form `m / 10^18`, where `m` is +/// an integer such that `-2^(192 - 1) <= m < 2^(192 - 1)`. +/// +/// Fractional part: ~60 bits/18 digits +/// Integer part : 132 bits /40 digits +/// Max : 3138550867693340381917894711603833208051.177722232017256447 +/// Min : -3138550867693340381917894711603833208051.177722232017256448 +/// +/// Unless otherwise specified, all operations will panic if underflow/overflow. +/// +/// Powering it is the [Scrypto Decimal type, see docs][scrypto]. +/// +/// Note: This type cannot be called `Decimal`, since it results in naming collision +/// in the Swift land (clash with `Foundation.Decimal`) instead we have created a +/// type alias `Decimal = Decimal192` which we use in Rust land. +/// +/// [scrypto]: https://github.com/radixdlt/radixdlt-scrypto/blob/fc196e21aacc19c0a3dbb13f3cd313dccf4327ca/radix-engine-common/src/math/decimal.rs#L42 +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + SerializeDisplay, + DeserializeFromStr, + derive_more::Display, + uniffi::Record, +)] +#[display("{}", self.native())] +pub struct Decimal192 { + /// @Kotlin / Swift developer: Do NOT use this property/field. Instead use all the provided methods on the `Decimal192` type. + /// (which are in fact vendored as freestanding global functions, + /// due to limitations in UniFII as of Feb 2024, but you should + /// create extension methods on Decimal192 in FFI land, translating + /// these functions into methods.) + __inner: InnerDecimal, // Strange field name to try as much as possible hide it in FFI land. +} + +/// Internally (in Rust land) we would like to call `Decimal192` just `Decimal`. +/// Reusing the naming convention set by Scrypto. +pub type Decimal = Decimal192; + +impl From for ScryptoDecimal192 { + fn from(value: Decimal) -> Self { + value.native() + } +} +impl From for Decimal { + fn from(value: ScryptoDecimal192) -> Self { + Self::from_native(value) + } +} +impl Decimal { + fn native(&self) -> ScryptoDecimal192 { + self.__inner.0 + } + + fn from_native(decimal: ScryptoDecimal192) -> Self { + Decimal { + __inner: InnerDecimal(decimal), + } + } +} + +impl FromStr for Decimal192 { + type Err = crate::CommonError; + + fn from_str(s: &str) -> Result { + s.parse::() + .map(Self::from_native) + .map_err(|_| CommonError::DecimalError) + } +} + +impl From for Decimal { + fn from(value: u32) -> Self { + ScryptoDecimal192::from(value).into() + } +} +impl From for Decimal { + fn from(value: u64) -> Self { + ScryptoDecimal192::from(value).into() + } +} +impl From for Decimal { + fn from(value: i32) -> Self { + ScryptoDecimal192::from(value).into() + } +} +impl From for Decimal { + fn from(value: i64) -> Self { + ScryptoDecimal192::from(value).into() + } +} + +impl From for Decimal { + fn from(value: f32) -> Self { + value.to_string().parse::().expect( + "Expected to always be able to create a Decimal192 from 'f32'.", + ) + } +} + +impl Decimal { + pub fn new(value: String) -> Result { + value.parse() + } + + pub fn zero() -> Self { + Self::from_native(ScryptoDecimal192::zero()) + } + + pub fn min() -> Self { + Self::from_native(ScryptoDecimal192::MIN) + } + + pub fn max() -> Self { + Self::from_native(ScryptoDecimal192::MAX) + } + + pub fn one() -> Self { + Self::from_native(ScryptoDecimal192::one()) + } + + pub fn two() -> Self { + Self::from_native(ScryptoDecimal192::from(2)) + } + + pub fn three() -> Self { + Self::from_native(ScryptoDecimal192::from(3)) + } +} + +impl Add for Decimal { + type Output = Self; + /// self + rhs + fn add(self, rhs: Self) -> Self::Output { + Into::into(self.native() + rhs.native()) + } +} +impl Sub for Decimal { + type Output = Self; + /// self - rhs + fn sub(self, rhs: Self) -> Self::Output { + Into::into(self.native() - rhs.native()) + } +} +impl Mul for Decimal { + type Output = Self; + /// self * rhs + fn mul(self, rhs: Self) -> Self::Output { + Into::into(self.native() * rhs.native()) + } +} +impl Div for Decimal { + type Output = Self; + /// self / rhs + fn div(self, rhs: Self) -> Self::Output { + Into::into(self.native() / rhs.native()) + } +} + +impl Neg for Decimal { + type Output = Self; + + /// `-self` + fn neg(self) -> Self::Output { + self.native().neg().into() + } +} + +impl Decimal { + delegate! { + to self.native() { + + /// Whether this decimal is zero. + pub fn is_zero(&self) -> bool; + + /// Whether this decimal is positive. + pub fn is_positive(&self) -> bool; + + /// Whether this decimal is negative. + pub fn is_negative(&self) -> bool; + } + } +} + +impl Decimal { + pub fn checked_powi(&self, exp: i64) -> Option { + self.native().checked_powi(exp).map(|n| n.into()) + } + + /// Creates the Decimal `10^exponent` + pub fn pow(exponent: u8) -> Self { + Self::from(10) + .checked_powi(exponent as i64) + .expect("Too large exponent, 10^39 is max.") + } + + /// `abs(self)` + /// Panics if Self is Self::MIN. + pub fn abs(&self) -> Self { + self.native().checked_abs().expect("Expected clients of Sargon to not use so large negative numbers (Self::MIN).").into() + } + + /// `max(self, 0)`, which is often called + /// "clamping to zero" + pub fn clamped_to_zero(self) -> Self { + if self.is_negative() { + Self::zero() + } else { + self + } + } + + /// Rounds this number to the specified decimal places. + /// + /// # Panics + /// - Panic if the number of decimal places is not within [0..SCALE(=18)] + pub fn round( + &self, + decimal_places: i32, + rounding_mode: RoundingMode, + ) -> Result { + self.native() + .checked_round(decimal_places, rounding_mode.into()) + .ok_or(CommonError::DecimalError) + .map(Into::::into) + } +} + +impl Decimal192 { + pub const MACHINE_READABLE_DECIMAL_SEPARATOR: &'static str = "."; + + /// Parse a local respecting string + pub fn new_with_formatted_string( + formatted_string: impl AsRef, + locale: LocaleConfig, + ) -> Result { + let formatted_string = formatted_string.as_ref().to_owned(); + // Pad with a leading zero, to make numbers with leading decimal separator parsable + let mut string = format!("0{}", formatted_string); + + // If the locale recognizes a grouping separator, we strip that from the string + if let Some(grouping_separator) = locale.grouping_separator { + string = string.replace(&grouping_separator, ""); + } + // `num` crate defines some pretty specific grouping separators: `"\u{a0}"` and `"\u{202f}"` for + // for some locales, but in unit tests we might use _normal_ space (`"U+0020"`), so we remove + // those (being a bit lenient...). + string = string.replace(' ', ""); + + // If the locale recognizes a decimal separator that is different from the machine readable one, we replace it with that + if let Some(decimal_separator) = locale.decimal_separator { + if decimal_separator != Self::MACHINE_READABLE_DECIMAL_SEPARATOR { + // If `decimal_separator != Self::MACHINE_READABLE_DECIMAL_SEPARATOR`, + // but if the string contains it, it might have been used incorrectly as + // a grouping separator. i.e. often "." is used in Swedish as a grouping + // separator, even though a space is the canonical one. So BEFORE + // we replace occurrences of decimal separator with "." + // (`Self::MACHINE_READABLE_DECIMAL_SEPARATOR`), we replace + // occurrences of `Self::MACHINE_READABLE_DECIMAL_SEPARATOR` with "". + string = string + .replace(Self::MACHINE_READABLE_DECIMAL_SEPARATOR, ""); + + string = string.replace( + &decimal_separator, + Self::MACHINE_READABLE_DECIMAL_SEPARATOR, + ); + } + } + + string.parse::() + } +} + +impl TryInto for &str { + type Error = crate::CommonError; + + fn try_into(self) -> Result { + self.parse::() + } +} + +impl TryFrom<&[u8]> for Decimal192 { + type Error = crate::CommonError; + + fn try_from(slice: &[u8]) -> Result { + ScryptoDecimal192::try_from(slice) + .map(Self::from_native) + .map_err(|_| CommonError::DecimalError) + } +} + +/// Tries to creates a new `Decimal192` from a String, throws a `CommonError` +/// if the `string` was not a valid Decimal192. +#[uniffi::export] +pub fn new_decimal_from_string(string: String) -> Result { + Decimal192::new(string) +} + +/// Tries to creates a new `Decimal192` from a formatted String for +/// a specific locale. +#[uniffi::export] +pub fn new_decimal_from_formatted_string( + formatted_string: String, + locale: LocaleConfig, +) -> Result { + Decimal192::new_with_formatted_string(formatted_string, locale) +} + +/// Creates a new `Decimal192` from a u32 integer. +#[uniffi::export] +pub fn new_decimal_from_u32(value: u32) -> Decimal192 { + value.into() +} + +/// Creates a new `Decimal192` from a u64 integer. +#[uniffi::export] +pub fn new_decimal_from_u64(value: u64) -> Decimal192 { + value.into() +} + +/// Creates a new `Decimal192` from a i32 integer. +#[uniffi::export] +pub fn new_decimal_from_i32(value: i32) -> Decimal192 { + value.into() +} + +/// Creates a new `Decimal192` from a i64 integer. +#[uniffi::export] +pub fn new_decimal_from_i64(value: i64) -> Decimal192 { + value.into() +} + +/// Creates a new `Decimal192` from a f32 float, it does +/// so by first converting the float to a String, using +/// Rust's `to_string` on the float. +#[uniffi::export] +pub fn new_decimal_from_f32(value: f32) -> Decimal192 { + value.into() +} + +/// The minimum possible value of `Decimal192`, being: +/// `-3138550867693340381917894711603833208051.177722232017256448` +#[uniffi::export] +pub fn decimal_min() -> Decimal192 { + Decimal192::min() +} + +/// The maximum possible value of `Decimal192`, being: +/// `3138550867693340381917894711603833208051.177722232017256447` +#[uniffi::export] +pub fn decimal_max() -> Decimal192 { + Decimal192::max() +} + +/// Creates the Decimal192 `10^exponent` +#[uniffi::export] +pub fn new_decimal_exponent(exponent: u8) -> Decimal192 { + Decimal192::pow(exponent) +} + +/// `decimal.to_string()` +#[uniffi::export] +pub fn decimal_to_string(decimal: &Decimal192) -> String { + decimal.to_string() +} + +/// `lhs < rhs` +#[uniffi::export] +pub fn decimal_less_than(lhs: &Decimal192, rhs: &Decimal192) -> bool { + lhs < rhs +} + +/// `lhs <= rhs` +#[uniffi::export] +pub fn decimal_less_than_or_equal(lhs: &Decimal192, rhs: &Decimal192) -> bool { + lhs <= rhs +} + +/// `lhs > rhs` +#[uniffi::export] +pub fn decimal_greater_than(lhs: &Decimal192, rhs: &Decimal192) -> bool { + lhs > rhs +} + +/// `lhs >= rhs` +#[uniffi::export] +pub fn decimal_greater_than_or_equal( + lhs: &Decimal192, + rhs: &Decimal192, +) -> bool { + lhs >= rhs +} + +/// Whether this decimal is zero. +#[uniffi::export] +pub fn decimal_is_zero(decimal: &Decimal192) -> bool { + decimal.is_zero() +} + +/// Whether this decimal is positive. +#[uniffi::export] +pub fn decimal_is_positive(decimal: &Decimal192) -> bool { + decimal.is_positive() +} + +/// Whether this decimal is negative. +#[uniffi::export] +pub fn decimal_is_negative(decimal: &Decimal192) -> bool { + decimal.is_negative() +} + +/// `lhs + rhs`` +#[uniffi::export] +pub fn decimal_add(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + lhs + rhs +} + +/// `lhs - rhs`` +#[uniffi::export] +pub fn decimal_sub(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + lhs - rhs +} + +/// `lhs * rhs`` +#[uniffi::export] +pub fn decimal_mul(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + lhs * rhs +} + +/// `lhs / rhs`` +#[uniffi::export] +pub fn decimal_div(lhs: Decimal192, rhs: Decimal192) -> Decimal192 { + lhs / rhs +} + +/// Negates the `decimal` +#[uniffi::export] +pub fn decimal_neg(decimal: &Decimal192) -> Decimal192 { + decimal.neg() +} + +/// Returns `decimal.abs()`, panics if `decimal` is `Decimal192::MIN` +#[uniffi::export] +pub fn decimal_abs(decimal: &Decimal192) -> Decimal192 { + decimal.abs() +} + +/// Clamps `decimal` to zero, i.e. `max(decimal, 0)` +#[uniffi::export] +pub fn decimal_clamped_to_zero(decimal: &Decimal192) -> Decimal192 { + decimal.clamped_to_zero() +} + +/// Rounds this number to the specified decimal places. +/// +/// # Panics +/// - Panic if the number of decimal places is not within [0..SCALE(=18)] +#[uniffi::export] +pub fn decimal_round( + decimal: &Decimal192, + decimal_places: i32, + rounding_mode: RoundingMode, +) -> Result { + decimal.round(decimal_places, rounding_mode) +} + +#[cfg(test)] +mod test_inner { + use super::*; + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = InnerDecimal; + + #[test] + fn string_roundtrip() { + let s = "3.1415"; + let sut: SUT = s.parse().unwrap(); + assert_eq!(sut.to_string(), s.to_owned()); + } + + #[test] + fn from_str_invalid() { + assert_eq!("invalid".parse::(), Err(CommonError::DecimalError)); + } +} + +#[cfg(test)] +mod test_decimal { + + use super::*; + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Decimal; + + #[test] + fn eq() { + assert_eq!(Decimal::zero(), Decimal::zero()); + assert_eq!(Decimal::one(), Decimal::one()); + assert_eq!(Decimal::zero(), "0".parse().unwrap()); + assert_eq!(Decimal::one(), "1".parse().unwrap()); + } + + #[test] + fn scrypto_decimal_roundtrip() { + let scrypto: ScryptoDecimal192 = "2.718281828".parse().unwrap(); + let sut: SUT = scrypto.into(); + let scrypto_again: ScryptoDecimal192 = sut.into(); + assert_eq!(scrypto, scrypto_again); + } + + #[test] + fn inequality() { + assert_ne!(Decimal192::one(), Decimal192::zero()); + } + + #[test] + fn is_zero() { + assert!(Decimal192::zero().is_zero()); + assert!(!Decimal192::one().is_zero()); + } + + #[test] + fn is_positive() { + assert!(!Decimal192::zero().is_positive()); + assert!(Decimal192::one().is_positive()); + } + + #[test] + fn is_negative() { + assert!("-1".parse::().unwrap().is_negative()); + assert!(!Decimal::zero().is_negative()); + assert!(!Decimal::one().is_negative()); + } + + #[test] + fn not_less() { + assert!(Decimal192::zero() >= Decimal192::zero()); + assert!(Decimal192::one() >= Decimal192::one()); + assert!(Decimal192::one() >= Decimal192::zero()); + } + + #[test] + fn less() { + assert!(Decimal192::zero() < Decimal192::one()); + } + + #[test] + fn leq() { + assert!(Decimal192::zero() <= Decimal192::zero()); + assert!(Decimal192::one() <= Decimal192::one()); + + assert!(Decimal192::one() > Decimal192::zero()); + } + + #[test] + fn not_greater_than() { + assert!(Decimal192::zero() <= Decimal192::zero()); + assert!(Decimal192::one() <= Decimal192::one()); + assert!(Decimal192::zero() <= Decimal192::one()); + } + + #[test] + fn geq() { + assert!(Decimal192::zero() >= Decimal192::zero()); + assert!(Decimal192::one() >= Decimal192::one()); + + assert!(Decimal192::zero() < Decimal192::one()); + } + + #[test] + fn greater() { + assert!(Decimal192::one() > Decimal192::zero()); + } + + #[test] + fn add_two_large() { + let a: Decimal = "958947355801916604025588861116008628224.01234" + .parse() + .unwrap(); + let b: Decimal = "58947355801916604025588861116008628224.04321" + .parse() + .unwrap(); + let c: Decimal = "1017894711603833208051177722232017256448.05555" + .parse() + .unwrap(); + assert_eq!(a + b, c); + } + + #[test] + fn from_str() { + let a: Decimal = + "3138550867693340381917894711603833208051.177722232017256447" + .parse() + .unwrap(); + let b: Decimal = + "3036550867693340381917894711603833208050.177722232017256447" + .parse() + .unwrap(); + assert!(a > b); + } + + #[test] + fn try_from_invalid_str() { + assert_eq!("foobar".parse::(), Err(CommonError::DecimalError)); + } + + #[test] + fn try_from_invalid_bytes() { + assert_eq!( + Decimal192::try_from(generate_32_bytes().as_slice()), + Err(CommonError::DecimalError) + ); + } + + #[test] + fn try_from_valid_bytes() { + assert!(Decimal192::try_from(generate_bytes::<24>().as_slice()).is_ok()); + } + + #[test] + fn display() { + let s = "3138550867693340381917894711603833208051.177722232017256447"; + let a: Decimal192 = s.try_into().unwrap(); + assert_eq!(format!("{}", a), s); + } + + #[test] + fn json_roundtrip() { + let a: Decimal192 = + "3138550867693340381917894711603833208051.177722232017256447" + .try_into() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!( + "3138550867693340381917894711603833208051.177722232017256447" + ), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip(&a, json!("3.1415")); + } + + #[test] + fn hash() { + let n = 100; + let set = (0..n) + .map(|_| { + Decimal192::try_from(generate_bytes::<24>().as_slice()).unwrap() + }) + .collect::>(); + assert_eq!(set.len(), n); + } + + #[test] + fn many_zeros() { + let s = "0.000000000000000123"; + let d: Decimal = s.parse().unwrap(); + assert_eq!(d.to_string(), s); + } + + #[test] + fn arithmetic() { + assert_eq!( + SUT::two() + SUT::three(), + SUT::two() * SUT::three() - SUT::one() + ); + } + + #[test] + fn neg() { + assert_eq!(SUT::two() - SUT::three(), -SUT::one()); + } + + #[test] + fn from_negative_string() { + let sut: SUT = "-3.2".parse().unwrap(); + assert_eq!(sut * sut, "10.24".parse().unwrap()); + } + + #[test] + fn test_parse_formatted_decimal() { + let test = |s: &str, l: &LocaleConfig, exp: &str| { + assert_eq!( + Decimal192::new_with_formatted_string(s, l.clone()).unwrap(), + exp.parse::().unwrap() + ) + }; + let fail = |s: &str, l: &LocaleConfig| { + assert!(Decimal192::new_with_formatted_string(s, l.clone()).is_err()) + }; + let swedish = LocaleConfig::swedish(); + let us = LocaleConfig::us(); + test(",005", &swedish, "0.005"); + test(".005", &us, "0.005"); + test("1,001", &swedish, "1.001"); + test("1,001", &us, "1001"); + test("1\u{a0}001,45", &swedish, "1001.45"); + test("1 001,45", &swedish, "1001.45"); + test("1.001,45", &swedish, "1001.45"); + test("1.001,45", &us, "1.00145"); + + fail("1.000.000", &us); + test("1.000.000", &swedish, "1000000"); + + fail("1.000.000,23", &us); + test("1.000.000,23", &swedish, "1000000.23"); + + test("1 000 000,23", &us, "100000023"); + test("1 000 000,23", &swedish, "1000000.23"); + + test("1 000 000.23", &us, "1000000.23"); + test("1 000 000.23", &swedish, "100000023"); + + fail("1,000,000", &swedish); + test("1,000,000", &us, "1000000"); + + fail("1,000,000.23", &swedish); + test("1,000,000.23", &us, "1000000.23"); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Decimal192; + + #[test] + fn to_string() { + let s = "58947355801916604025588861116008628224.04321"; + let a: Decimal192 = s.parse().unwrap(); + + assert_eq!(decimal_to_string(&a), s); + } + + #[test] + fn arithmetic() { + let zero = new_decimal_from_i32(0); + let one = new_decimal_from_i64(1); + let two = new_decimal_from_u32(2); + let three = new_decimal_from_u64(3); + let four = new_decimal_from_string("4".to_string()).unwrap(); + let five = new_decimal_from_i32(5); + let six = new_decimal_from_i32(6); + let seven = new_decimal_from_i32(7); + let eight = new_decimal_from_i32(8); + let nine = new_decimal_from_i32(9); + let ten = new_decimal_from_i32(10); + + assert_eq!(zero + zero, zero); + assert_eq!(one + zero, one); + assert_eq!(one + one, two); + assert_eq!(one + two, three); + assert_eq!(two + one, three); + assert_eq!(two + two, four); + assert_eq!(two + three, five); + assert_eq!(three + three, six); + assert_eq!(three + four, seven); + assert_eq!(four + four, eight); + assert_eq!(four + five, nine); + assert_eq!(five + four, nine); + assert_eq!(five + five, ten); + assert_eq!(ten + zero, ten); + + assert_eq!(zero * zero, zero); + assert_eq!(one * zero, zero); + assert_eq!(two * zero, zero); + assert_eq!(three * zero, zero); + + assert_eq!(one * one, one); + assert_eq!(one * two, two); + assert_eq!(one * three, three); + assert_eq!(one * four, four); + + assert_eq!(two * two, four); + assert_eq!(two * three, six); + assert_eq!(two * four, eight); + assert_eq!(two * five, ten); + assert_eq!(three * three, nine); + + assert_eq!(one / one, one); + assert_eq!(two / one, two); + assert_eq!(three / one, three); + assert_eq!(four / one, four); + + assert_eq!(two / two, one); + assert_eq!(three / three, one); + assert_eq!(four / four, one); + assert_eq!(five / five, one); + assert_eq!(ten / ten, one); + assert_eq!(nine / three, three); + + assert_eq!(ten - ten, zero); + assert_eq!(nine - nine, zero); + assert_eq!(two - two, zero); + assert_eq!(zero - zero, zero); + assert_eq!(seven - two, five); + + assert_eq!(decimal_add(zero, zero), zero); + assert_eq!(decimal_add(one, zero), one); + + assert_eq!(decimal_add(one, one), two); + assert_eq!(decimal_add(one, two), three); + assert_eq!(decimal_add(two, one), three); + assert_eq!(decimal_add(two, two), four); + assert_eq!(decimal_add(two, three), five); + assert_eq!(decimal_add(three, three), six); + assert_eq!(decimal_add(three, four), seven); + assert_eq!(decimal_add(three, four), seven); + assert_eq!(decimal_add(four, four), eight); + assert_eq!(decimal_add(four, five), nine); + assert_eq!(decimal_add(five, four), nine); + assert_eq!(decimal_add(five, five), ten); + assert_eq!(decimal_add(ten, zero), ten); + + assert_eq!(decimal_mul(zero, zero), zero); + assert_eq!(decimal_mul(zero, zero), zero); + assert_eq!(decimal_mul(one, zero), zero); + assert_eq!(decimal_mul(two, zero), zero); + assert_eq!(decimal_mul(zero, zero), zero); + + assert_eq!(decimal_mul(one, one), one); + assert_eq!(decimal_mul(one, two), two); + assert_eq!(decimal_mul(one, three), three); + assert_eq!(decimal_mul(one, four), four); + + assert_eq!(decimal_mul(two, two), four); + assert_eq!(decimal_mul(two, three), six); + assert_eq!(decimal_mul(two, four), eight); + assert_eq!(decimal_mul(two, five), ten); + assert_eq!(decimal_mul(three, three), nine); + + assert_eq!(decimal_div(one, one), one); + assert_eq!(decimal_div(two, one), two); + assert_eq!(decimal_div(three, one), three); + assert_eq!(decimal_div(four, one), four); + + assert_eq!(decimal_div(two, two), one); + assert_eq!(decimal_div(three, three), one); + assert_eq!(decimal_div(four, four), one); + assert_eq!(decimal_div(five, five), one); + assert_eq!(decimal_div(ten, ten), one); + assert_eq!(decimal_div(nine, three), three); + + assert_eq!(decimal_sub(ten, ten), zero); + assert_eq!(decimal_sub(nine, nine), zero); + assert_eq!(decimal_sub(zero, zero), zero); + assert_eq!(decimal_sub(seven, two), five); + } + + #[test] + fn exponent() { + assert_eq!(new_decimal_exponent(0).to_string(), "1"); + assert_eq!(new_decimal_exponent(1).to_string(), "10"); + assert_eq!(new_decimal_exponent(2).to_string(), "100"); + assert_eq!(new_decimal_exponent(3).to_string(), "1000"); + assert_eq!(new_decimal_exponent(4).to_string(), "10000"); + assert_eq!( + new_decimal_exponent(20).to_string(), + "100000000000000000000" + ); + assert_eq!( + new_decimal_exponent(39).to_string(), + "1000000000000000000000000000000000000000" + ); + } + + #[test] + #[should_panic(expected = "Too large exponent, 10^39 is max.")] + fn exponent_too_large() { + _ = new_decimal_exponent(40); + } + + #[test] + #[should_panic( + expected = "Expected clients of Sargon to not use so large negative numbers (Self::MIN)." + )] + fn decimal_min_abs() { + _ = SUT::min().abs() + } + + #[test] + fn compare() { + let zero = new_decimal_from_i32(0); + let one = new_decimal_from_i64(1); + let two = new_decimal_from_u32(2); + let three = new_decimal_from_u64(3); + + assert!(decimal_less_than(&zero, &one)); + assert!(decimal_less_than(&zero, &two)); + assert!(decimal_less_than(&one, &two)); + assert!(!decimal_less_than(&zero, &zero)); + assert!(decimal_less_than_or_equal(&zero, &zero)); + assert!(decimal_less_than_or_equal(&zero, &one)); + assert!(decimal_less_than_or_equal(&zero, &two)); + assert!(decimal_less_than_or_equal(&one, &two)); + assert!(decimal_less_than_or_equal(&two, &three)); + assert!(decimal_less_than_or_equal(&three, &three)); + + assert!(!decimal_greater_than(&three, &three)); + assert!(decimal_greater_than_or_equal(&three, &three)); + assert!(decimal_greater_than_or_equal(&three, &two)); + assert!(decimal_greater_than(&three, &two)); + assert!(decimal_greater_than(&three, &one)); + assert!(decimal_greater_than(&two, &one)); + assert!(!decimal_greater_than(&one, &one)); + assert!(decimal_greater_than(&one, &zero)); + } + + #[test] + fn is_zero() { + assert!(decimal_is_zero(&SUT::zero())); + assert!(!decimal_is_zero(&SUT::one())); + } + + #[test] + fn is_positive() { + // `0` is neither positive nor negative + // https://en.wikipedia.org/wiki/0 + assert!(!decimal_is_positive(&SUT::zero())); + + assert!(decimal_is_positive(&SUT::one())); + assert!(!decimal_is_positive(&decimal_neg(&SUT::one()))); + } + + #[test] + fn is_negative() { + // `0` is neither positive nor negative + // https://en.wikipedia.org/wiki/0 + assert!(!decimal_is_negative(&SUT::zero())); + + assert!(!decimal_is_negative(&SUT::one())); + assert!(decimal_is_negative(&decimal_neg(&SUT::one()))); + } + + #[test] + fn min() { + assert_eq!( + decimal_min().to_string(), + "-3138550867693340381917894711603833208051.177722232017256448" + ); + } + + #[test] + fn max() { + assert_eq!( + decimal_max().to_string(), + "3138550867693340381917894711603833208051.177722232017256447" + ); + } + + #[test] + fn from_f32() { + let f: f32 = 208050.17; + assert_eq!(f.to_string(), "208050.17"); + let sut = new_decimal_from_f32(f); + assert_eq!(sut.to_string(), "208050.17"); + assert_eq!( + SUT::from(f32::MAX).to_string(), + "340282350000000000000000000000000000000" + ) + } + + #[test] + fn rounding() { + let mut sut: SUT = "3.1".parse().unwrap(); + let mut mode: RoundingMode = RoundingMode::ToPositiveInfinity; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(4) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-3) + ); + + mode = RoundingMode::ToNegativeInfinity; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(3) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-4) + ); + + mode = RoundingMode::ToZero; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(3) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-3) + ); + + mode = RoundingMode::AwayFromZero; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(4) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-4) + ); + + sut = "3.5".parse().unwrap(); + mode = RoundingMode::ToNearestMidpointTowardZero; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(3) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-3) + ); + + mode = RoundingMode::ToNearestMidpointAwayFromZero; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(4) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-4) + ); + + mode = RoundingMode::ToNearestMidpointToEven; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + new_decimal_from_i32(4) + ); + assert_eq!( + decimal_round(&-sut, 0, mode).unwrap(), + new_decimal_from_i32(-4) + ); + + // more decimals + sut = "2.4595".parse().unwrap(); + mode = RoundingMode::AwayFromZero; + assert_eq!( + decimal_round(&sut, 0, mode).unwrap(), + "3".parse::().unwrap() + ); + assert_eq!( + decimal_round(&sut, 1, mode).unwrap(), + "2.5".parse::().unwrap() + ); + assert_eq!( + decimal_round(&sut, 2, mode).unwrap(), + "2.46".parse::().unwrap() + ); + assert_eq!( + decimal_round(&sut, 3, mode).unwrap(), + "2.46".parse::().unwrap() + ); + } + + #[test] + fn abs() { + let sut = -SUT::one(); + assert_eq!(decimal_abs(&sut), SUT::one()); + } + + #[test] + fn clamped() { + assert_eq!(decimal_clamped_to_zero(&-SUT::one()), SUT::zero()); + assert_eq!(decimal_clamped_to_zero(&SUT::one()), SUT::one()); + } + + #[test] + fn from_formatted_string() { + let test = |s: &str, l: &LocaleConfig, exp: &str| { + assert_eq!( + new_decimal_from_formatted_string(s.to_owned(), l.clone()) + .unwrap(), + exp.parse::().unwrap() + ) + }; + let fail = |s: &str, l: &LocaleConfig| { + assert!(new_decimal_from_formatted_string(s.to_owned(), l.clone()) + .is_err()) + }; + let swedish = LocaleConfig::swedish(); + let us = LocaleConfig::us(); + test(",005", &swedish, "0.005"); + test(".005", &us, "0.005"); + test("1,001", &swedish, "1.001"); + test("1,001", &us, "1001"); + + fail("1,000,000.23", &swedish); + test("1,000,000.23", &us, "1000000.23"); + } +} diff --git a/src/core/types/epoch.rs b/src/core/types/epoch.rs new file mode 100644 index 000000000..5fea3e208 --- /dev/null +++ b/src/core/types/epoch.rs @@ -0,0 +1,62 @@ +pub use crate::prelude::*; + +// use radix_engine_common::types::Epoch as ScryptoEpoch; + +// Generate the FfiConverter needed by UniFFI for newtype `Epoch`. +uniffi::custom_newtype!(Epoch, u64); + +/// A type-safe consensus epoch number. +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + derive_more::Display, + derive_more::Debug, +)] +pub struct Epoch(pub u64); + +impl From for Epoch { + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: Epoch) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn from_u64() { + let test = + |u: u64| assert_eq!(Into::::into(Into::::into(u)), u); + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn to_u64() { + let test = |u: u64| { + assert_eq!( + Into::::into(Into::::into(Into::::into(u))) + .0, + u + ) + }; + test(0); + test(1); + test(2); + test(1337); + } +} diff --git a/src/core/types/hex_32bytes.rs b/src/core/types/hex_32bytes.rs index 6a0489ad0..99975a5b0 100644 --- a/src/core/types/hex_32bytes.rs +++ b/src/core/types/hex_32bytes.rs @@ -2,168 +2,266 @@ use crate::prelude::*; use delegate::delegate; use radix_engine_common::crypto::{Hash, IsHash}; -/// Serializable 32 bytes which **always** serializes as a **hex** string, this is useful -/// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this -/// allows us to skip using -#[derive( - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - SerializeDisplay, - DeserializeFromStr, - derive_more::Display, - derive_more::Debug, - uniffi::Record, -)] -#[display("{}", self.to_hex())] -#[debug("{}", self.to_hex())] -pub struct Hex32Bytes { - bag_of_bytes: BagOfBytes, -} - -impl TryFrom for Hex32Bytes { - type Error = CommonError; - - fn try_from(value: BagOfBytes) -> Result { - if value.len() != 32 { - return Err(CommonError::InvalidByteCountExpected32 { - bad_value: value.len() as u64, - }); +macro_rules! decl_bag_of_n_bytes { + ($struct_name:ident, $byte_count:expr) => { + /// Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful + /// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this + /// allows us to skip using + #[derive( + Clone, + PartialEq, + Eq, + Hash, + SerializeDisplay, + DeserializeFromStr, + derive_more::Display, + derive_more::Debug, + uniffi::Record, + )] + #[display("{}", self.to_hex())] + #[debug("{}", self.to_hex())] + pub struct $struct_name { + bag_of_bytes: BagOfBytes, } - Ok(Self { - bag_of_bytes: value, - }) - } -} -impl Hex32Bytes { - /// Instantiates a new `BagOfBytes` from bytes generated by - /// a CSPRNG. - pub fn generate() -> Self { - Hex32Bytes { - bag_of_bytes: BagOfBytes::from(generate_32_bytes()), + impl TryFrom for $struct_name { + type Error = CommonError; + + fn try_from(value: BagOfBytes) -> Result { + if value.len() != $byte_count { + return Err(CommonError::InvalidByteCount { + expected: $byte_count as u64, + found: value.len() as u64, + }); + } + Ok(Self { + bag_of_bytes: value, + }) + } } - } - /// Tries to decode the string `s` into a `Hex32Bytes`. Will fail - /// if the string is not valid hex or if the decoded bytes does - /// not have length 32. - pub fn from_hex(s: &str) -> Result { - Self::from_str(s) - } + impl $struct_name { + /// Instantiates a new `BagOfBytes` from bytes generated by + /// a CSPRNG. + pub fn generate() -> Self { + $struct_name { + bag_of_bytes: BagOfBytes::from( + generate_bytes::<$byte_count>(), + ), + } + } + + /// Tries to decode the string `s` into a `$struct_name`. Will fail + /// if the string is not valid hex or if the decoded bytes does + /// not have length 32. + pub fn from_hex(s: &str) -> Result { + Self::from_str(s) + } + + /// Instantiates a new `$struct_name` from the $byte_count bytes, by cloning them. + pub fn from_bytes(bytes: &[u8; $byte_count]) -> Self { + let bytes: &[u8] = bytes.as_slice().into(); + let bag_of_bytes: BagOfBytes = bytes.into(); + Self { bag_of_bytes } + } + } - /// Instantiates a new `Hex32Bytes` from the 32 bytes, by cloning them. - pub fn from_bytes(bytes: &[u8; 32]) -> Self { - Self { - bag_of_bytes: bytes.into(), + impl $struct_name { + /// Returns a references to the inner array slice. + pub fn bytes(&self) -> [u8; $byte_count] { + self.bag_of_bytes + .to_vec() + .as_slice() + .try_into() + .expect("$byte_count bytes") + } } - } -} -impl Hex32Bytes { - /// Returns a references to the inner array slice. - pub fn bytes(&self) -> [u8; 32] { - self.bag_of_bytes - .to_vec() - .as_slice() - .try_into() - .expect("32 bytes") - } -} + impl TryFrom> for $struct_name { + type Error = CommonError; -impl TryFrom> for Hex32Bytes { - type Error = CommonError; + fn try_from(value: Vec) -> Result { + BagOfBytes::from(value).try_into() + } + } - fn try_from(value: Vec) -> Result { - BagOfBytes::from(value).try_into() - } -} + impl FromStr for $struct_name { + type Err = CommonError; -impl FromStr for Hex32Bytes { - type Err = CommonError; + fn from_str(s: &str) -> Result { + s.parse::().and_then(|v| v.try_into()) + } + } - fn from_str(s: &str) -> Result { - s.parse::().and_then(|v| v.try_into()) - } + impl $struct_name { + delegate! { + to self.bag_of_bytes{ + pub fn to_hex(&self) -> String; + pub fn to_vec(&self) -> Vec; + } + } + } + }; } -impl From for Hex32Bytes { - /// Instantiates a new `Hex32Bytes` from the `Hash` (32 bytes). - fn from(value: Hash) -> Self { - Self::from_bytes(&value.into_bytes()) - } -} +decl_bag_of_n_bytes!(Hex32Bytes, 32); +decl_bag_of_n_bytes!(Hex64Bytes, 64); +decl_bag_of_n_bytes!(Hex33Bytes, 33); +decl_bag_of_n_bytes!(Hex65Bytes, 65); + +macro_rules! decl_placeholders_for_bag_of_n_bytes { + ($struct_name:ident, $byte_count:expr) => { + impl HasPlaceholder for $struct_name { + /// `deadbeef...`` + /// A placeholder used to facilitate unit tests. + fn placeholder() -> Self { + Self::placeholder_dead() + } + + /// A placeholder used to facilitate unit tests. + fn placeholder_other() -> Self { + Self::placeholder_fade() + } + } -impl Hex32Bytes { - delegate! { - to self.bag_of_bytes{ - pub fn to_hex(&self) -> String; - pub fn to_vec(&self) -> Vec; + impl $struct_name { + /// `aced...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_aced() -> Self { + Self::from_str(&"aced".repeat($byte_count / 2)) + .expect("aced...") + } + + /// `babe...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_babe() -> Self { + Self::from_str(&"babe".repeat($byte_count / 2)) + .expect("babe...") + } + + /// `cafe...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_cafe() -> Self { + Self::from_str(&"cafe".repeat($byte_count / 2)) + .expect("cafe...") + } + + /// `dead...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_dead() -> Self { + Self::from_str(&"dead".repeat($byte_count / 2)) + .expect("dead...") + } + + /// `ecad...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_ecad() -> Self { + Self::from_str(&"ecad".repeat(16)).expect("ecad...") + } + + /// `fade...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_fade() -> Self { + Self::from_str(&"fade".repeat($byte_count / 2)) + .expect("fade...") + } } - } + }; } -impl HasPlaceholder for Hex32Bytes { - /// `deadbeef...`` +// The impl of placeholders require an equal number of bytes +decl_placeholders_for_bag_of_n_bytes!(Hex32Bytes, 32); +decl_placeholders_for_bag_of_n_bytes!(Hex64Bytes, 64); + +impl HasPlaceholder for Hex33Bytes { + /// `33deadbeefdead...`` /// A placeholder used to facilitate unit tests. fn placeholder() -> Self { - Self::placeholder_dead() + let s = "dead".repeat(16); + Self::from_str(&format!("33{s}")) + .expect("Should have declared a valid Hex33Bytes placeholder") } - /// A placeholder used to facilitate unit tests. + /// `33beefbeefbeef...`` + /// Another placeholder used to facilitate unit tests. fn placeholder_other() -> Self { - Self::placeholder_fade() + let s = "beef".repeat(16); + Self::from_str(&format!("33{s}")) + .expect("Should have declared a valid Hex33Bytes placeholder") } } -impl Hex32Bytes { - /// `aced...`` +impl HasPlaceholder for Hex65Bytes { + /// `65deadbeefdead...`` /// A placeholder used to facilitate unit tests. - pub fn placeholder_aced() -> Self { - Self::from_str(&"aced".repeat(16)).expect("aced...") + fn placeholder() -> Self { + let s = "dead".repeat(32); + Self::from_str(&format!("65{s}")) + .expect("Should have declared a valid Hex65Bytes placeholder") } - /// `babe...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_babe() -> Self { - Self::from_str(&"babe".repeat(16)).expect("babe...") + /// `65beefbeefbeef...`` + /// Another placeholder used to facilitate unit tests. + fn placeholder_other() -> Self { + let s = "beef".repeat(32); + Self::from_str(&format!("65{s}")) + .expect("Should have declared a valid Hex65Bytes placeholder") } +} - /// `cafe...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_cafe() -> Self { - Self::from_str(&"cafe".repeat(16)).expect("cafe...") - } +#[uniffi::export] +pub fn new_hex32_bytes_from(bytes: Vec) -> Result { + Hex32Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex32_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex32Bytes::try_from(bag_of_bytes) +} - /// `dead...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_dead() -> Self { - Self::from_str(&"dead".repeat(16)).expect("dead...") +impl From for Hex32Bytes { + /// Instantiates a new `$struct_name` from the `Hash` ($byte_count bytes). + fn from(value: Hash) -> Self { + Self::from_bytes(&value.into_bytes()) } +} - /// `ecad...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_ecad() -> Self { - Self::from_str(&"ecad".repeat(16)).expect("ecad...") - } +#[uniffi::export] +pub fn new_hex64_bytes_from(bytes: Vec) -> Result { + Hex64Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex64_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex64Bytes::try_from(bag_of_bytes) +} - /// `fade...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_fade() -> Self { - Self::from_str(&"fade".repeat(16)).expect("fade...") - } +#[uniffi::export] +pub fn new_hex33_bytes_from(bytes: Vec) -> Result { + Hex33Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex33_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex33Bytes::try_from(bag_of_bytes) } #[uniffi::export] -pub fn new_hex32_bytes_from(bytes: Vec) -> Result { - Hex32Bytes::try_from(bytes) +pub fn new_hex65_bytes_from(bytes: Vec) -> Result { + Hex65Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex65_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex65Bytes::try_from(bag_of_bytes) } #[cfg(test)] -mod tests { +mod tests_hex32_bytes { use crate::prelude::*; @@ -256,7 +354,10 @@ mod tests { fn invalid_len() { assert_eq!( SUT::try_from(Vec::from([0u8; 5])), - Err(CommonError::InvalidByteCountExpected32 { bad_value: 5 }) + Err(CommonError::InvalidByteCount { + expected: 32, + found: 5 + }) ) } @@ -273,7 +374,7 @@ mod tests { } #[cfg(test)] -mod uniffi_tests { +mod hex32_uniffi_tests { use crate::prelude::*; #[test] @@ -285,8 +386,454 @@ mod uniffi_tests { ); } + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<32>(); + assert_eq!( + new_hex32_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + #[test] fn new_fail() { assert!(new_hex32_bytes_from(generate_bytes::<5>()).is_err()); } } + +// Copy paste + +#[cfg(test)] +mod tests_hex64_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex64Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 64]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 64]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 64, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex64_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_64_bytes(); + assert_eq!( + new_hex64_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<64>(); + assert_eq!( + new_hex64_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex64_bytes_from(generate_bytes::<5>()).is_err()); + } +} + +// Copy paste + +#[cfg(test)] +mod tests_hex33_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex33Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "100000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 33]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 33]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 33, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex33_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_bytes::<33>(); + assert_eq!( + new_hex33_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<33>(); + assert_eq!( + new_hex33_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex33_bytes_from(generate_bytes::<5>()).is_err()); + } +} + +// Copy paste + +#[cfg(test)] +mod tests_hex65_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex65Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "6510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 65]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 65]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 65, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex65_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_bytes::<65>(); + assert_eq!( + new_hex65_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<65>(); + assert_eq!( + new_hex65_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex65_bytes_from(generate_bytes::<5>()).is_err()); + } +} diff --git a/src/core/types/keys/ed25519/private_key.rs b/src/core/types/keys/ed25519/private_key.rs index 2b8c7dfbc..d944e5bc5 100644 --- a/src/core/types/keys/ed25519/private_key.rs +++ b/src/core/types/keys/ed25519/private_key.rs @@ -1,8 +1,7 @@ use crate::prelude::*; use radix_engine_common::crypto::{ - Ed25519PrivateKey as ScryptoEd25519PrivateKey, - Ed25519Signature as ScryptoEd25519Signature, IsHash, + Ed25519PrivateKey as ScryptoEd25519PrivateKey, IsHash, }; /// An Ed25519 private key used to create cryptographic signatures, using @@ -35,7 +34,7 @@ impl IsPrivateKey for Ed25519PrivateKey { SLIP10Curve::Curve25519 } - type Signature = ScryptoEd25519Signature; + type Signature = Ed25519Signature; fn public_key(&self) -> Ed25519PublicKey { self.0.public_key().try_into().expect( @@ -43,8 +42,8 @@ impl IsPrivateKey for Ed25519PrivateKey { ) } - fn sign(&self, msg_hash: &impl IsHash) -> ScryptoEd25519Signature { - self.0.sign(msg_hash) + fn sign(&self, msg_hash: &impl IsHash) -> Self::Signature { + self.0.sign(msg_hash).into() } } @@ -111,6 +110,8 @@ impl HasPlaceholder for Ed25519PrivateKey { } impl Ed25519PrivateKey { + /// A placeholder used to facilitate unit tests. + /// /// `833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42` /// /// expected public key: @@ -124,8 +125,10 @@ impl Ed25519PrivateKey { .unwrap() } + /// A placeholder used to facilitate unit tests. + /// /// `1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93`` - + /// /// expected public key: /// `b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde` /// @@ -180,10 +183,12 @@ mod tests { pk.to_hex(), "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29" ); - let sig = ScryptoEd25519Signature::from_str("cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e").unwrap(); + let sig_hex = "cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e"; + let sig = Ed25519Signature::from_str(sig_hex).unwrap(); assert_eq!(sk.sign(&msg), sig); - assert!(pk.is_valid(&sig, &msg)) + assert!(pk.is_valid(&sig, &msg)); + assert_eq!(sig.to_hex(), sig_hex); } #[test] diff --git a/src/core/types/keys/ed25519/public_key.rs b/src/core/types/keys/ed25519/public_key.rs index 4065a4910..70ab4312f 100644 --- a/src/core/types/keys/ed25519/public_key.rs +++ b/src/core/types/keys/ed25519/public_key.rs @@ -78,14 +78,18 @@ pub fn ed25519_public_key_to_bytes(public_key: &Ed25519PublicKey) -> Vec { public_key.to_bytes() } -impl IsPublicKey for Ed25519PublicKey { +impl IsPublicKey for Ed25519PublicKey { /// Verifies an EdDSA signature over Curve25519. fn is_valid( &self, - signature: &ScryptoEd25519Signature, + signature: &Ed25519Signature, for_hash: &impl IsHash, ) -> bool { - scrypto_verify_ed25519(for_hash.as_hash(), &self.to_engine(), signature) + scrypto_verify_ed25519( + for_hash.as_hash(), + &self.to_engine(), + &signature.clone().into(), + ) } } diff --git a/src/core/types/keys/secp256k1/private_key.rs b/src/core/types/keys/secp256k1/private_key.rs index f601145cf..dfaacb4c6 100644 --- a/src/core/types/keys/secp256k1/private_key.rs +++ b/src/core/types/keys/secp256k1/private_key.rs @@ -2,7 +2,6 @@ use crate::prelude::*; use radix_engine_common::crypto::{ IsHash, Secp256k1PrivateKey as ScryptoSecp256k1PrivateKey, - Secp256k1Signature as ScryptoSecp256k1Signature, }; /// A secp256k1 private key used to create cryptographic signatures, more specifically @@ -85,7 +84,7 @@ impl IsPrivateKey for Secp256k1PrivateKey { SLIP10Curve::Secp256k1 } - type Signature = ScryptoSecp256k1Signature; + type Signature = Secp256k1Signature; fn public_key(&self) -> Secp256k1PublicKey { Secp256k1PublicKey::try_from(self.0.public_key()).expect( @@ -94,7 +93,7 @@ impl IsPrivateKey for Secp256k1PrivateKey { } fn sign(&self, msg_hash: &impl IsHash) -> Self::Signature { - self.0.sign(msg_hash) + self.0.sign(msg_hash).into() } } @@ -182,10 +181,12 @@ mod tests { pk.to_hex(), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ); - let sig = ScryptoSecp256k1Signature::from_str("00eb8dcd5bb841430dd0a6f45565a1b8bdb4a204eb868832cd006f963a89a662813ab844a542fcdbfda4086a83fbbde516214113051b9c8e42a206c98d564d7122").unwrap(); + let sig_hex = "00eb8dcd5bb841430dd0a6f45565a1b8bdb4a204eb868832cd006f963a89a662813ab844a542fcdbfda4086a83fbbde516214113051b9c8e42a206c98d564d7122"; + let sig = Secp256k1Signature::from_str(sig_hex).unwrap(); assert_eq!(sk.sign(&msg), sig); - assert!(pk.is_valid(&sig, &msg)) + assert!(pk.is_valid(&sig, &msg)); + assert_eq!(sig.to_hex(), sig_hex); } #[test] diff --git a/src/core/types/keys/secp256k1/public_key.rs b/src/core/types/keys/secp256k1/public_key.rs index 0f35341dd..6f952f3ab 100644 --- a/src/core/types/keys/secp256k1/public_key.rs +++ b/src/core/types/keys/secp256k1/public_key.rs @@ -1,14 +1,12 @@ use crate::{prelude::*, UniffiCustomTypeConverter}; use bip32::secp256k1::PublicKey as BIP32Secp256k1PublicKey; // the bip32 crate actually does validation of the PublicKey whereas `radix_engine_common` does not. + use radix_engine_common::crypto::{ verify_secp256k1, Hash, IsHash, Secp256k1PublicKey as ScryptoSecp256k1PublicKey, Secp256k1Signature as ScryptoSecp256k1Signature, }; -// use transaction::{ -// signing::secp256k1::Secp256k1Signature, validation::verify_secp256k1, -// }; /// A `secp256k1` public key used to verify cryptographic signatures (ECDSA signatures). #[serde_as] @@ -85,14 +83,18 @@ pub fn new_secp256k1_public_key_placeholder_other() -> Secp256k1PublicKey { Secp256k1PublicKey::placeholder_other() } -impl IsPublicKey for Secp256k1PublicKey { +impl IsPublicKey for Secp256k1PublicKey { /// Verifies an ECDSA signature over Secp256k1. fn is_valid( &self, - signature: &ScryptoSecp256k1Signature, + signature: &Secp256k1Signature, for_hash: &impl IsHash, ) -> bool { - verify_secp256k1(for_hash.as_hash(), &self.to_engine(), signature) + verify_secp256k1( + for_hash.as_hash(), + &self.to_engine(), + &signature.clone().into(), + ) } } diff --git a/src/core/types/locale_config.rs b/src/core/types/locale_config.rs new file mode 100644 index 000000000..453116e71 --- /dev/null +++ b/src/core/types/locale_config.rs @@ -0,0 +1,102 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct LocaleConfig { + pub decimal_separator: Option, + pub grouping_separator: Option, +} + +impl LocaleConfig { + pub fn new( + decimal_separator: impl Into>, + grouping_separator: impl Into>, + ) -> Self { + Self { + decimal_separator: decimal_separator.into(), + grouping_separator: grouping_separator.into(), + } + } +} + +impl Default for LocaleConfig { + fn default() -> Self { + Self::new(".".to_owned(), " ".to_owned()) + } +} + +impl From for LocaleConfig { + fn from(value: num_format::Locale) -> Self { + Self::new( + Some(value.decimal().to_owned()), + Some(value.separator().to_owned()), + ) + } +} + +impl LocaleConfig { + /// Tries to create a + /// A BCP-47 language identifier such as `"en_US_POSIX"`, `"sv_FI"` or `"zh_Hant_MO"`, + /// see: [list][list] + /// + /// [list]: https://docs.rs/num-format/0.4.4/src/num_format/locale.rs.html#5565-6444 + pub fn from_identifier(identifier: impl AsRef) -> Result { + let identifier = identifier.as_ref().to_owned(); + num_format::Locale::from_name(identifier.clone()) + .map_err(|_| CommonError::UnrecognizedLocaleIdentifier { + bad_value: identifier, + }) + .map(Into::::into) + } +} + +#[cfg(test)] +impl LocaleConfig { + pub fn swedish() -> Self { + Self::from_identifier("sv").expect("Sweden exists") + } + pub fn us() -> Self { + Self::from_identifier("en_US_POSIX").expect("US exists") + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn swedish() { + assert_eq!(LocaleConfig::swedish().decimal_separator.unwrap(), ","); + assert_eq!( + LocaleConfig::swedish().grouping_separator.unwrap(), + "\u{a0}" + ); + } + + #[test] + fn english_us() { + assert_eq!(LocaleConfig::us().decimal_separator.unwrap(), "."); + assert_eq!(LocaleConfig::us().grouping_separator.unwrap(), ","); + } + + #[test] + fn default_uses_spaces_as_grouping_separator() { + let sut = LocaleConfig::default(); + assert_eq!(&sut.grouping_separator.unwrap(), " "); + } + + #[test] + fn default_uses_dot_as_decimal_separator() { + let sut = LocaleConfig::default(); + assert_eq!(&sut.decimal_separator.unwrap(), "."); + } + + #[test] + fn from_identifier_invalid() { + assert_eq!( + LocaleConfig::from_identifier("foo"), + Err(CommonError::UnrecognizedLocaleIdentifier { + bad_value: "foo".to_owned() + }) + ); + } +} diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index ce1042b89..acaf86ec6 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -1,17 +1,27 @@ mod bag_of_bytes; -mod decimal; +mod decimal192; mod entity_kind; +mod epoch; mod hex_32bytes; mod identified_vec_via; mod keys; +mod locale_config; mod logged_result; +mod nonce; +mod rounding_mode; mod safe_to_log; +mod signatures; pub use bag_of_bytes::*; -pub use decimal::*; +pub use decimal192::*; pub use entity_kind::*; +pub use epoch::*; pub use hex_32bytes::*; pub use identified_vec_via::*; pub use keys::*; +pub use locale_config::*; pub use logged_result::*; +pub use nonce::*; +pub use rounding_mode::*; pub use safe_to_log::*; +pub use signatures::*; diff --git a/src/core/types/nonce.rs b/src/core/types/nonce.rs new file mode 100644 index 000000000..61ab5cb08 --- /dev/null +++ b/src/core/types/nonce.rs @@ -0,0 +1,79 @@ +pub use crate::prelude::*; + +// Generate the FfiConverter needed by UniFFI for newtype `Nonce`. +uniffi::custom_newtype!(Nonce, u32); + +/// A random number generated part of a transaction header, +/// ensuring every transaction os unique even though its +/// transaction manifest might be equal. This nonce is +/// generated by wallets for incoming transactions. +#[derive( + Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::Debug, +)] +pub struct Nonce(pub u32); + +impl Nonce { + /// Generates 4 random bytes and creates a Nonce (u32) from it. + pub fn random() -> Self { + u32::from_be_bytes( + generate_bytes::<4>() + .as_slice() + .try_into() + .expect("It is 4 bytes."), + ) + .into() + } +} + +impl From for Nonce { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: Nonce) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn from_u32() { + let test = + |u: u32| assert_eq!(Into::::into(Into::::into(u)), u); + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn to_u32() { + let test = |u: u32| { + assert_eq!( + Into::::into(Into::::into(Into::::into(u))) + .0, + u + ) + }; + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn generate_new() { + let mut set: HashSet = HashSet::new(); + let n = 100; + for _ in 0..n { + let sut = Nonce::random(); + set.insert(sut); + } + assert_eq!(set.len(), n); // with a low probability this might fail yes. + } +} diff --git a/src/core/types/rounding_mode.rs b/src/core/types/rounding_mode.rs new file mode 100644 index 000000000..2da620c09 --- /dev/null +++ b/src/core/types/rounding_mode.rs @@ -0,0 +1,100 @@ +use crate::prelude::*; +use radix_engine_common::math::RoundingMode as ScryptoRoundingMode; + +/// Defines the rounding strategy used when you round e.g. `Decimal192`. +/// +/// Following the same naming convention as https://docs.rs/rust_decimal/latest/rust_decimal/enum.RoundingStrategy.html. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, enum_iterator::Sequence, uniffi::Enum, +)] +pub enum RoundingMode { + /// The number is always rounded toward positive infinity, e.g. `3.1 -> 4`, `-3.1 -> -3`. + ToPositiveInfinity, + + /// The number is always rounded toward negative infinity, e.g. `3.1 -> 3`, `-3.1 -> -4`. + ToNegativeInfinity, + + /// The number is always rounded toward zero, e.g. `3.1 -> 3`, `-3.1 -> -3`. + ToZero, + + /// The number is always rounded away from zero, e.g. `3.1 -> 4`, `-3.1 -> -4`. + AwayFromZero, + + /// The number is rounded to the nearest, and when it is halfway between two others, it's rounded toward zero, e.g. `3.5 -> 3`, `-3.5 -> -3`. + ToNearestMidpointTowardZero, + + /// The number is rounded to the nearest, and when it is halfway between two others, it's rounded away from zero, e.g. `3.5 -> 4`, `-3.5 -> -4`. + ToNearestMidpointAwayFromZero, + + /// The number is rounded to the nearest, and when it is halfway between two others, it's rounded toward the nearest even number. Also known as "Bankers Rounding". + ToNearestMidpointToEven, +} + +impl From for ScryptoRoundingMode { + fn from(value: RoundingMode) -> Self { + match value { + RoundingMode::ToPositiveInfinity => { + ScryptoRoundingMode::ToPositiveInfinity + } + RoundingMode::ToNegativeInfinity => { + ScryptoRoundingMode::ToNegativeInfinity + } + RoundingMode::ToZero => ScryptoRoundingMode::ToZero, + RoundingMode::AwayFromZero => ScryptoRoundingMode::AwayFromZero, + RoundingMode::ToNearestMidpointTowardZero => { + ScryptoRoundingMode::ToNearestMidpointTowardZero + } + RoundingMode::ToNearestMidpointAwayFromZero => { + ScryptoRoundingMode::ToNearestMidpointAwayFromZero + } + RoundingMode::ToNearestMidpointToEven => { + ScryptoRoundingMode::ToNearestMidpointToEven + } + } + } +} + +impl From for RoundingMode { + fn from(value: ScryptoRoundingMode) -> Self { + match value { + ScryptoRoundingMode::ToPositiveInfinity => { + RoundingMode::ToPositiveInfinity + } + ScryptoRoundingMode::ToNegativeInfinity => { + RoundingMode::ToNegativeInfinity + } + ScryptoRoundingMode::ToZero => RoundingMode::ToZero, + ScryptoRoundingMode::AwayFromZero => RoundingMode::AwayFromZero, + ScryptoRoundingMode::ToNearestMidpointTowardZero => { + RoundingMode::ToNearestMidpointTowardZero + } + ScryptoRoundingMode::ToNearestMidpointAwayFromZero => { + RoundingMode::ToNearestMidpointAwayFromZero + } + ScryptoRoundingMode::ToNearestMidpointToEven => { + RoundingMode::ToNearestMidpointToEven + } + } + } +} + +#[cfg(test)] +mod tests { + use enum_iterator::all; + + use super::*; + use crate::prelude::*; + + #[test] + fn rounding_mode_conversion() { + let test = |m: RoundingMode| { + assert_eq!( + Into::::into(Into::::into( + m + )), + m + ) + }; + all::().for_each(test); + } +} diff --git a/src/core/types/signatures/ed25519_signature.rs b/src/core/types/signatures/ed25519_signature.rs new file mode 100644 index 000000000..6d01cce37 --- /dev/null +++ b/src/core/types/signatures/ed25519_signature.rs @@ -0,0 +1,178 @@ +use crate::prelude::*; + +use radix_engine_common::crypto::Ed25519Signature as ScryptoEd25519Signature; + +/// Represents an ED25519 signature. +#[derive( + Clone, + PartialEq, + Eq, + Hash, + derive_more::Display, + derive_more::Debug, + derive_more::FromStr, + uniffi::Record, +)] +#[display("{}", self.to_hex())] +#[debug("{}", self.to_hex())] +pub struct Ed25519Signature { + pub bytes: Hex64Bytes, +} + +impl From for Ed25519Signature { + fn from(value: ScryptoEd25519Signature) -> Self { + Self { + bytes: Hex64Bytes::from_bytes(&value.0), + } + } +} + +impl From for ScryptoEd25519Signature { + fn from(value: Ed25519Signature) -> Self { + ScryptoEd25519Signature(value.bytes.bytes()) + } +} + +impl Ed25519Signature { + pub fn to_bytes(&self) -> Vec { + self.bytes.to_vec() + } + + pub fn to_hex(&self) -> String { + hex_encode(self.to_bytes()) + } +} + +impl HasPlaceholder for Ed25519Signature { + /// Returns a valid Ed25519Signature, see doc test below, + /// with the value: + /// + /// `"2150c2f6b6c496d197ae03afb23f6adf23b275c675394f23786250abd006d5a2c7543566403cb414f70d0e229b0a9b55b4c74f42fc38cdf1aba2307f97686f0b"` + /// + /// ``` + /// extern crate sargon; + /// use sargon::prelude::*; + /// + /// let mnemonic: Mnemonic = "bright club bacon dinner achieve pull grid save ramp cereal blush woman humble limb repeat video sudden possible story mask neutral prize goose mandate".parse().unwrap(); + /// + /// let path: AccountPath = "m/44H/1022H/1H/525H/1460H/0H".parse().unwrap(); + /// + /// let mwp = MnemonicWithPassphrase::with_passphrase(mnemonic, BIP39Passphrase::new("radix")); + /// + /// let hd_private_key = mwp.derive_private_key(path); + /// + /// assert_eq!(&hd_private_key.private_key.to_hex(), "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003"); + /// + /// assert_eq!(&hd_private_key.public_key().to_hex(), "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b"); + /// + /// let message = "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!"; + /// + /// let hash = hash(message.as_bytes()); + /// + /// let signature: Ed25519Signature = "2150c2f6b6c496d197ae03afb23f6adf23b275c675394f23786250abd006d5a2c7543566403cb414f70d0e229b0a9b55b4c74f42fc38cdf1aba2307f97686f0b".parse().unwrap(); + /// + /// assert_eq!( + /// &hd_private_key + /// .private_key + /// .public_key() + /// .into_ed25519() + /// .unwrap() + /// .is_valid(&signature, &hash), + /// &true + /// ); + /// ``` + /// + fn placeholder() -> Self { + "2150c2f6b6c496d197ae03afb23f6adf23b275c675394f23786250abd006d5a2c7543566403cb414f70d0e229b0a9b55b4c74f42fc38cdf1aba2307f97686f0b".parse().expect("Should produce a valid placeholder Ed25519Signature") + } + + /// Returns a valid Ed25519Signature, see doc test below, + /// with the value: + /// + /// `"06cd3772c5c70d44819db80192a5b2521525e2529f770bff970ec4edc7c1bd76e41fcfa8e59ff93b1675c48f4af3b1697765286d999ee8b5bb8257691e3b7b09"` + /// + /// ``` + /// extern crate sargon; + /// use sargon::prelude::*; + /// + /// let mnemonic: Mnemonic = "bright club bacon dinner achieve pull grid save ramp cereal blush woman humble limb repeat video sudden possible story mask neutral prize goose mandate".parse().unwrap(); + /// + /// let path: AccountPath = "m/44H/1022H/1H/525H/1460H/1H".parse().unwrap(); + /// + /// let mwp = MnemonicWithPassphrase::with_passphrase(mnemonic, BIP39Passphrase::new("radix")); + /// + /// let hd_private_key = mwp.derive_private_key(path); + /// + /// assert_eq!(&hd_private_key.private_key.to_hex(), "6b736e59d41c5ba47dc427ebee9990426441e01db4abee5c44192492c269d8e0"); + /// + /// assert_eq!(&hd_private_key.public_key().to_hex(), "08740a2fd178c40ce71966a6537f780978f7f00548cfb59196344b5d7d67e9cf"); + /// + /// let message = "All those moments will be lost in time, like tears in rain. Time to die..."; + /// + /// let hash = hash(message.as_bytes()); + /// + /// let signature: Ed25519Signature = "06cd3772c5c70d44819db80192a5b2521525e2529f770bff970ec4edc7c1bd76e41fcfa8e59ff93b1675c48f4af3b1697765286d999ee8b5bb8257691e3b7b09".parse().unwrap(); + /// + /// assert_eq!( + /// &hd_private_key + /// .private_key + /// .public_key() + /// .into_ed25519() + /// .unwrap() + /// .is_valid(&signature, &hash), + /// &true + /// ); + /// ``` + /// + fn placeholder_other() -> Self { + "06cd3772c5c70d44819db80192a5b2521525e2529f770bff970ec4edc7c1bd76e41fcfa8e59ff93b1675c48f4af3b1697765286d999ee8b5bb8257691e3b7b09".parse().expect("Should produce a valid placeholder Ed25519Signature") + } +} + +#[cfg(test)] +mod tests { + use crate::HasPlaceholder; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Ed25519Signature; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn scrypto_roundtrip() { + let sut = SUT::placeholder(); + assert_eq!( + Into::::into(Into::::into( + sut.clone() + )), + sut + ); + } + + #[test] + fn scrypto_roundtrip_start_scrypto() { + let sig: ScryptoEd25519Signature = "2150c2f6b6c496d197ae03afb23f6adf23b275c675394f23786250abd006d5a2c7543566403cb414f70d0e229b0a9b55b4c74f42fc38cdf1aba2307f97686f0b".parse().unwrap(); + assert_eq!( + Into::::into(Into::::into( + sig.clone() + )), + sig + ); + } + + #[test] + fn to_hex() { + assert_eq!(SUT::placeholder_other().to_hex(), "06cd3772c5c70d44819db80192a5b2521525e2529f770bff970ec4edc7c1bd76e41fcfa8e59ff93b1675c48f4af3b1697765286d999ee8b5bb8257691e3b7b09"); + } +} diff --git a/src/core/types/signatures/mod.rs b/src/core/types/signatures/mod.rs new file mode 100644 index 000000000..985f78b57 --- /dev/null +++ b/src/core/types/signatures/mod.rs @@ -0,0 +1,9 @@ +mod ed25519_signature; +mod secp256k1_signature; +mod signature; +mod signature_with_public_key; + +pub use ed25519_signature::*; +pub use secp256k1_signature::*; +pub use signature::*; +pub use signature_with_public_key::*; diff --git a/src/core/types/signatures/secp256k1_signature.rs b/src/core/types/signatures/secp256k1_signature.rs new file mode 100644 index 000000000..297bd6127 --- /dev/null +++ b/src/core/types/signatures/secp256k1_signature.rs @@ -0,0 +1,179 @@ +use crate::prelude::*; + +use radix_engine_common::crypto::Secp256k1Signature as ScryptoSecp256k1Signature; + +/// Represents an Secp256k1 signature. +#[derive( + Clone, + PartialEq, + Eq, + Hash, + derive_more::Display, + derive_more::Debug, + derive_more::FromStr, + uniffi::Record, +)] +#[display("{}", self.to_hex())] +#[debug("{}", self.to_hex())] +pub struct Secp256k1Signature { + // recovery id + signature + pub bytes: Hex65Bytes, +} + +impl From for Secp256k1Signature { + fn from(value: ScryptoSecp256k1Signature) -> Self { + Self { + bytes: Hex65Bytes::from_bytes(&value.0), + } + } +} + +impl From for ScryptoSecp256k1Signature { + fn from(value: Secp256k1Signature) -> Self { + ScryptoSecp256k1Signature(value.bytes.bytes()) + } +} + +impl Secp256k1Signature { + pub fn to_bytes(&self) -> Vec { + self.bytes.to_vec() + } + + pub fn to_hex(&self) -> String { + hex_encode(self.to_bytes()) + } +} + +impl HasPlaceholder for Secp256k1Signature { + /// Returns a valid Secp256k1Signature, see doc test below, + /// with the value: + /// + /// `"018ad795353658a0cd1b513c4414cbafd0f990d329522977f8885a27876976a7d41ed8a81c1ac34551819627689cf940c4e27cacab217f00a0a899123c021ff6ef"` + /// + /// ``` + /// extern crate sargon; + /// use sargon::prelude::*; + /// + /// let mnemonic: Mnemonic = "habit special recipe upon giraffe manual evil badge dwarf welcome inspire shrug post arrive van".parse().unwrap(); + /// + /// let path: BIP44LikePath = "m/44H/1022H/0H/0/5H".parse().unwrap(); + /// + /// let mwp = MnemonicWithPassphrase::with_passphrase(mnemonic, BIP39Passphrase::default()); + /// + /// let hd_private_key = mwp.derive_private_key(path); + /// + /// assert_eq!(&hd_private_key.private_key.to_hex(), "111323d507d9d690836798e3ef2e5292cfd31092b75b9b59fa584ff593a3d7e4"); + /// + /// assert_eq!(&hd_private_key.public_key().to_hex(), "03e78cdb2e0b7ea6e55e121a58560ccf841a913d3a4a9b8349e0ef00c2102f48d8"); + /// + /// let message = "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!"; + /// + /// let hash = hash(message.as_bytes()); + /// + /// let signature: Secp256k1Signature = "018ad795353658a0cd1b513c4414cbafd0f990d329522977f8885a27876976a7d41ed8a81c1ac34551819627689cf940c4e27cacab217f00a0a899123c021ff6ef".parse().unwrap(); + /// + /// assert_eq!( + /// &hd_private_key + /// .private_key + /// .public_key() + /// .into_secp256k1() + /// .unwrap() + /// .is_valid(&signature, &hash), + /// &true + /// ); + /// ``` + /// + fn placeholder() -> Self { + "018ad795353658a0cd1b513c4414cbafd0f990d329522977f8885a27876976a7d41ed8a81c1ac34551819627689cf940c4e27cacab217f00a0a899123c021ff6ef".parse().expect("Should construct valid placeholders.") + } + + /// Returns a valid Secp256k1Signature, see doc test below, + /// with the value: + /// + /// `"01aa1c4f46f8437b7f8ec9008ae10e6f33bb8be3e81e35c63f3498070dfbd6a20b2daee6073ead3c9e72d8909bc32a02e46cede3885cf8568d4c380ac97aa7fbcd"` + /// + /// ``` + /// extern crate sargon; + /// use sargon::prelude::*; + /// + /// let mnemonic: Mnemonic = "habit special recipe upon giraffe manual evil badge dwarf welcome inspire shrug post arrive van".parse().unwrap(); + /// + /// let path: BIP44LikePath = "m/44H/1022H/0H/0/1H".parse().unwrap(); + /// + /// let mwp = MnemonicWithPassphrase::with_passphrase(mnemonic, BIP39Passphrase::default()); + /// + /// let hd_private_key = mwp.derive_private_key(path); + /// + /// assert_eq!(&hd_private_key.private_key.to_hex(), "84d8a5991e8f2885fe49d77da0ee6ee9f3f03ef419ac9c19a48cd32e10244ecd"); + /// + /// assert_eq!(&hd_private_key.public_key().to_hex(), "02f0d85a3b9082683f689e6115f37e1e24b7448fff14b14877e3a4e750e86fba8b"); + /// + /// let message = "All those moments will be lost in time, like tears in rain. Time to die..."; + /// + /// let hash = hash(message.as_bytes()); + /// + /// let signature: Secp256k1Signature = "01aa1c4f46f8437b7f8ec9008ae10e6f33bb8be3e81e35c63f3498070dfbd6a20b2daee6073ead3c9e72d8909bc32a02e46cede3885cf8568d4c380ac97aa7fbcd".parse().unwrap(); + /// + /// assert_eq!( + /// &hd_private_key + /// .private_key + /// .public_key() + /// .into_secp256k1() + /// .unwrap() + /// .is_valid(&signature, &hash), + /// &true + /// ); + /// ``` + /// + fn placeholder_other() -> Self { + "01aa1c4f46f8437b7f8ec9008ae10e6f33bb8be3e81e35c63f3498070dfbd6a20b2daee6073ead3c9e72d8909bc32a02e46cede3885cf8568d4c380ac97aa7fbcd".parse().expect("Should construct valid placeholders.") + } +} + +#[cfg(test)] +mod tests { + use crate::HasPlaceholder; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Secp256k1Signature; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn scrypto_roundtrip() { + let sut = SUT::placeholder(); + assert_eq!( + Into::::into(Into::::into( + sut.clone() + )), + sut + ); + } + + #[test] + fn scrypto_roundtrip_start_scrypto() { + let sig: ScryptoSecp256k1Signature = "01aa1c4f46f8437b7f8ec9008ae10e6f33bb8be3e81e35c63f3498070dfbd6a20b2daee6073ead3c9e72d8909bc32a02e46cede3885cf8568d4c380ac97aa7fbcd".parse().unwrap(); + assert_eq!( + Into::::into(Into::::into( + sig.clone() + )), + sig + ); + } + + #[test] + fn to_hex() { + assert_eq!(SUT::placeholder().to_hex(), "018ad795353658a0cd1b513c4414cbafd0f990d329522977f8885a27876976a7d41ed8a81c1ac34551819627689cf940c4e27cacab217f00a0a899123c021ff6ef"); + } +} diff --git a/src/core/types/signatures/signature.rs b/src/core/types/signatures/signature.rs new file mode 100644 index 000000000..c357bd1d7 --- /dev/null +++ b/src/core/types/signatures/signature.rs @@ -0,0 +1,70 @@ +use crate::prelude::*; + +/// Either a Signature on `Curve25519` or `Secp256k1` +#[derive( + Clone, + PartialEq, + Eq, + Hash, + EnumAsInner, + derive_more::Display, + derive_more::Debug, + uniffi::Enum, +)] +pub enum Signature { + Secp256k1 { value: Secp256k1Signature }, + Ed25519 { value: Ed25519Signature }, +} + +impl From for Signature { + fn from(signature: Secp256k1Signature) -> Self { + Self::Secp256k1 { value: signature } + } +} + +impl From for Signature { + fn from(signature: Ed25519Signature) -> Self { + Self::Ed25519 { value: signature } + } +} + +impl HasPlaceholder for Signature { + fn placeholder() -> Self { + Ed25519Signature::placeholder().into() + } + + fn placeholder_other() -> Self { + Secp256k1Signature::placeholder().into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Signature; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn enum_as_inner() { + assert_eq!( + SUT::placeholder().as_ed25519().unwrap(), + &Ed25519Signature::placeholder() + ); + assert_eq!( + SUT::placeholder_other().as_secp256k1().unwrap(), + &Secp256k1Signature::placeholder() + ); + } +} diff --git a/src/core/types/signatures/signature_with_public_key.rs b/src/core/types/signatures/signature_with_public_key.rs new file mode 100644 index 000000000..24f088283 --- /dev/null +++ b/src/core/types/signatures/signature_with_public_key.rs @@ -0,0 +1,42 @@ +// use crate::prelude::*; + +// use radix_engine_common::crypto::Ed25519Signature as ScryptoEd25519Signature; +// use radix_engine_common::crypto::Secp256k1Signature as ScryptoSecp256k1Signature; + +// /// Represents any natively supported signature, including public key. +// #[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum, EnumAsInner)] +// pub enum SignatureWithPublicKeyV1 { +// Secp256k1 { +// signature: ScryptoSecp256k1Signature, +// }, +// Ed25519 { +// public_key: Ed25519PublicKey, +// signature: ScryptoEd25519Signature, +// }, +// } + +// impl SignatureWithPublicKeyV1 { +// pub fn signature(&self) -> SignatureV1 { +// match &self { +// Self::Secp256k1 { signature } => signature.clone().into(), +// Self::Ed25519 { signature, .. } => signature.clone().into(), +// } +// } +// } + +// impl From for SignatureWithPublicKeyV1 { +// fn from(signature: Secp256k1Signature) -> Self { +// Self::Secp256k1 { signature } +// } +// } + +// impl From<(Ed25519PublicKey, Ed25519Signature)> for SignatureWithPublicKeyV1 { +// fn from( +// (public_key, signature): (Ed25519PublicKey, Ed25519Signature), +// ) -> Self { +// Self::Ed25519 { +// public_key, +// signature, +// } +// } +// } diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs b/src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs index a119f529a..a108c5f8b 100644 --- a/src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs +++ b/src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs @@ -2,7 +2,7 @@ use crate::prelude::*; // TODO: Merge trait IsEntityPath into trait EntityCAP26Path ? pub trait IsEntityPath: - EntityCAP26Path + Into + TryFrom + EntityCAP26Path + Into + TryFrom + std::fmt::Display { fn network_id(&self) -> NetworkID; fn key_kind(&self) -> CAP26KeyKind; diff --git a/src/lib.rs b/src/lib.rs index 3a8fa60a4..94bbf3344 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod core; mod hierarchical_deterministic; mod profile; mod wallet; +mod wrapped_radix_engine_toolkit; pub mod prelude { @@ -11,6 +12,7 @@ pub mod prelude { pub use crate::hierarchical_deterministic::*; pub use crate::profile::*; pub use crate::wallet::*; + pub use crate::wrapped_radix_engine_toolkit::*; pub(crate) use std::collections::{BTreeSet, HashMap, HashSet}; @@ -32,6 +34,7 @@ pub mod prelude { pub(crate) use serde_repr::{Deserialize_repr, Serialize_repr}; pub(crate) use serde_with::*; pub(crate) use std::cmp::Ordering; + pub(crate) use std::ops::{Add, Div, Mul, Neg, Sub}; pub(crate) use std::str::FromStr; pub(crate) use std::sync::Arc; pub(crate) use strum::FromRepr; diff --git a/src/profile/v100/app_preferences/gateways/gateways.rs b/src/profile/v100/app_preferences/gateways/gateways.rs index 0085109e3..0ab52bf20 100644 --- a/src/profile/v100/app_preferences/gateways/gateways.rs +++ b/src/profile/v100/app_preferences/gateways/gateways.rs @@ -236,7 +236,7 @@ mod tests { ) .unwrap(); assert_eq!(sut.clone().len(), 1 + 2); - assert_eq!(sut.is_empty(), false); + assert!(!sut.is_empty()); } #[test] diff --git a/src/profile/v100/app_preferences/p2p_links/p2p_link.rs b/src/profile/v100/app_preferences/p2p_links/p2p_link.rs index a14a560a8..e7fd59d47 100644 --- a/src/profile/v100/app_preferences/p2p_links/p2p_link.rs +++ b/src/profile/v100/app_preferences/p2p_links/p2p_link.rs @@ -11,8 +11,6 @@ use radix_engine_common::crypto::Hash; Clone, PartialEq, Eq, - Ord, - PartialOrd, Hash, derive_more::Debug, derive_more::Display, diff --git a/src/profile/v100/app_preferences/p2p_links/radix_connect_password.rs b/src/profile/v100/app_preferences/p2p_links/radix_connect_password.rs index e181bf3d7..134927e9f 100644 --- a/src/profile/v100/app_preferences/p2p_links/radix_connect_password.rs +++ b/src/profile/v100/app_preferences/p2p_links/radix_connect_password.rs @@ -10,8 +10,6 @@ use radix_engine_common::crypto::Hash; Clone, PartialEq, Eq, - PartialOrd, - Ord, Hash, derive_more::Display, derive_more::Debug, diff --git a/src/profile/v100/app_preferences/transaction_preferences.rs b/src/profile/v100/app_preferences/transaction_preferences.rs index 97934997c..3f2238d51 100644 --- a/src/profile/v100/app_preferences/transaction_preferences.rs +++ b/src/profile/v100/app_preferences/transaction_preferences.rs @@ -33,12 +33,12 @@ impl Default for TransactionPreferences { impl HasPlaceholder for TransactionPreferences { /// A placeholder used to facilitate unit tests. fn placeholder() -> Self { - Self::new(Decimal192::try_from_str("0.975").unwrap()) + Self::new("0.975".parse().unwrap()) } /// A placeholder used to facilitate unit tests. fn placeholder_other() -> Self { - Self::new(Decimal192::try_from_str("0.765").unwrap()) + Self::new("0.765".parse().unwrap()) } } @@ -67,8 +67,8 @@ mod tests { #[test] fn get_decimal() { - let value = Decimal192::new("0.975".to_string()).unwrap(); - let sut = TransactionPreferences::new(value.clone()); + let value = Decimal::new("0.975".to_string()).unwrap(); + let sut = TransactionPreferences::new(value); assert_eq!(sut.default_deposit_guarantee, value) } diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs index d13741138..86c798bb9 100644 --- a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs +++ b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs @@ -108,8 +108,7 @@ mod tests { #[test] fn new_from_string() { assert_eq!( - PersonaDataEntryEmailAddress::new("alan@turing.hero".to_string()) - .unwrap(), + PersonaDataEntryEmailAddress::new("alan@turing.hero").unwrap(), PersonaDataEntryEmailAddress::placeholder() ); } diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs index b466d6737..fbdc5ebe9 100644 --- a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs +++ b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs @@ -111,10 +111,9 @@ mod tests { fn new_from_string_multiple_given_names() { let name = PersonaDataEntryName::new( Variant::Western, - "Långstrump".to_string(), - "Pippilotta Viktualia Rullgardina Krusmynta Efraimsdotter" - .to_string(), - "Pippi".to_string(), + "Långstrump", + "Pippilotta Viktualia Rullgardina Krusmynta Efraimsdotter", + "Pippi", ) .unwrap(); assert_eq!( diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs index 9b5e40d37..f0fcd93cb 100644 --- a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs +++ b/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs @@ -110,8 +110,7 @@ mod tests { #[test] fn new_from_string() { assert_eq!( - PersonaDataEntryPhoneNumber::new("+46123456789".to_string()) - .unwrap(), + PersonaDataEntryPhoneNumber::new("+46123456789").unwrap(), PersonaDataEntryPhoneNumber::placeholder() ); } diff --git a/src/profile/v100/factors/factor_source_id_from_hash.rs b/src/profile/v100/factors/factor_source_id_from_hash.rs index 1be118b8f..94d5d814f 100644 --- a/src/profile/v100/factors/factor_source_id_from_hash.rs +++ b/src/profile/v100/factors/factor_source_id_from_hash.rs @@ -9,8 +9,6 @@ use radix_engine_common::crypto::{blake2b_256_hash, Hash}; Clone, PartialEq, Eq, - PartialOrd, - Ord, Hash, derive_more::Display, derive_more::Debug, diff --git a/src/profile/v100/networks/network/authorized_dapp/requested_quantity.rs b/src/profile/v100/networks/network/authorized_dapp/requested_quantity.rs index f4a5042e0..724831ede 100644 --- a/src/profile/v100/networks/network/authorized_dapp/requested_quantity.rs +++ b/src/profile/v100/networks/network/authorized_dapp/requested_quantity.rs @@ -109,9 +109,9 @@ mod tests { #[test] fn at_least_fulfills_false() { - assert_eq!(SUT::at_least(1).is_fulfilled_by_ids(0), false); - assert_eq!(SUT::at_least(10).is_fulfilled_by_ids(0), false); - assert_eq!(SUT::at_least(10).is_fulfilled_by_ids(9), false); + assert!(!SUT::at_least(1).is_fulfilled_by_ids(0)); + assert!(!SUT::at_least(10).is_fulfilled_by_ids(0)); + assert!(!SUT::at_least(10).is_fulfilled_by_ids(9)); } #[test] @@ -122,9 +122,9 @@ mod tests { #[test] fn exactly_fulfills_false() { - assert_eq!(SUT::exactly(1).is_fulfilled_by_ids(0), false); - assert_eq!(SUT::exactly(1).is_fulfilled_by_ids(2), false); - assert_eq!(SUT::exactly(10).is_fulfilled_by_ids(9), false); - assert_eq!(SUT::exactly(10).is_fulfilled_by_ids(11), false); + assert!(!SUT::exactly(1).is_fulfilled_by_ids(0)); + assert!(!SUT::exactly(1).is_fulfilled_by_ids(2)); + assert!(!SUT::exactly(10).is_fulfilled_by_ids(9)); + assert!(!SUT::exactly(10).is_fulfilled_by_ids(11)); } } diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs b/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs index f343ed091..402960329 100644 --- a/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs +++ b/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs @@ -32,7 +32,7 @@ macro_rules! declare_shared_with_dapp { impl $struct_name { /// Constructs a new $struct_name where `ids` "fulfills" the `request`. /// - /// # Panic + /// # Panics /// Panics if `ids` does not fulfill `request`, for more information /// see [`RequestedQuantity::is_fulfilled_by_ids`] pub fn new( diff --git a/src/sargon.udl b/src/sargon.udl index 04125e929..663490df1 100644 --- a/src/sargon.udl +++ b/src/sargon.udl @@ -10,4 +10,7 @@ typedef string Timestamp; [Custom] typedef sequence BagOfBytes; +[Custom] +typedef string InnerDecimal; + namespace sargon {}; \ No newline at end of file diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 896e85acf..599d3679f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,9 +1,11 @@ mod secure_storage; mod wallet; mod wallet_accounts; +mod wallet_device_factor_sources; mod wallet_profile_io; pub use secure_storage::*; pub use wallet::*; pub use wallet_accounts::*; +pub use wallet_device_factor_sources::*; pub use wallet_profile_io::*; diff --git a/src/wallet/wallet_accounts.rs b/src/wallet/wallet_accounts.rs index 9ecf3e690..c4ea5d1a4 100644 --- a/src/wallet/wallet_accounts.rs +++ b/src/wallet/wallet_accounts.rs @@ -72,49 +72,6 @@ impl Wallet { bad_value: factor_source.factor_source_id(), }) } - - /// Loads a `MnemonicWithPassphrase` with the `id` of `device_factor_source`, - /// from SecureStorage, and returns a `PrivateHierarchicalDeterministicFactorSource` - /// built from both. - /// - /// Useful for when you will want to sign transactions or derive public keys for - /// creation of new entities. - /// - /// Returns `Err` if loading or decoding of `MnemonicWithPassphrase` from - /// SecureStorage fails. - pub fn load_private_device_factor_source( - &self, - device_factor_source: &DeviceFactorSource, - ) -> Result { - info!( - "Load Private DeviceFactorSource from SecureStorage, factor source id: {}", - &device_factor_source.id - ); - self.wallet_client_storage - .load_mnemonic_with_passphrase(&device_factor_source.id) - .map(|mwp| { - PrivateHierarchicalDeterministicFactorSource::new(mwp, device_factor_source.clone()) - }) - .log_info("Successfully loaded Private DeviceFactorSource from SecureStorage") - } - - /// Loads a `MnemonicWithPassphrase` with the `id` of `device_factor_source`, - /// from SecureStorage, and returns a `PrivateHierarchicalDeterministicFactorSource` - /// built from both. - /// - /// Useful for when you will want to sign transactions or derive public keys for - /// creation of new entities. - /// - /// Returns `Err` if loading or decoding of `MnemonicWithPassphrase` from - /// SecureStorage fails. - pub fn load_private_device_factor_source_by_id( - &self, - id: &FactorSourceIDFromHash, - ) -> Result { - let device_factor_source = - self.profile().device_factor_source_by_id(id)?; - self.load_private_device_factor_source(&device_factor_source) - } } //======== diff --git a/src/wallet/wallet_device_factor_sources.rs b/src/wallet/wallet_device_factor_sources.rs new file mode 100644 index 000000000..3eb2e345a --- /dev/null +++ b/src/wallet/wallet_device_factor_sources.rs @@ -0,0 +1,127 @@ +use crate::prelude::*; + +impl Wallet { + /// Tries to load a `MnemonicWithPassphrase` from secure storage + /// by `id` of type `FactorSourceIDFromHash`. + pub fn mnemonic_with_passphrase_of_device_factor_source_by_id( + &self, + id: &FactorSourceIDFromHash, + ) -> Result { + self.wallet_client_storage.load_mnemonic_with_passphrase(id) + } + + /// Loads a `MnemonicWithPassphrase` with the `id` of `device_factor_source`, + /// from SecureStorage, and returns a `PrivateHierarchicalDeterministicFactorSource` + /// built from both. + /// + /// Useful for when you will want to sign transactions or derive public keys for + /// creation of new entities. + /// + /// Returns `Err` if loading or decoding of `MnemonicWithPassphrase` from + /// SecureStorage fails. + pub fn load_private_device_factor_source( + &self, + device_factor_source: &DeviceFactorSource, + ) -> Result { + info!( + "Load Private DeviceFactorSource from SecureStorage, factor source id: {}", + &device_factor_source.id + ); + self.mnemonic_with_passphrase_of_device_factor_source_by_id( + &device_factor_source.id, + ) + .map(|mwp| { + PrivateHierarchicalDeterministicFactorSource::new( + mwp, + device_factor_source.clone(), + ) + }) + .log_info( + "Successfully loaded Private DeviceFactorSource from SecureStorage", + ) + } + + /// Loads a `MnemonicWithPassphrase` with the `id` of `device_factor_source`, + /// from SecureStorage, and returns a `PrivateHierarchicalDeterministicFactorSource` + /// built from both. + /// + /// Useful for when you will want to sign transactions or derive public keys for + /// creation of new entities. + /// + /// Returns `Err` if loading or decoding of `MnemonicWithPassphrase` from + /// SecureStorage fails. + pub fn load_private_device_factor_source_by_id( + &self, + id: &FactorSourceIDFromHash, + ) -> Result { + let device_factor_source = + self.profile().device_factor_source_by_id(id)?; + self.load_private_device_factor_source(&device_factor_source) + } +} + +#[uniffi::export] +impl Wallet { + /// Tries to load a `MnemonicWithPassphrase` from secure storage + /// by `factor_source_id`. + pub fn mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id( + &self, + factor_source_id: &FactorSourceID, + ) -> Result { + factor_source_id + .clone() + .into_hash() + .map_err(|_| CommonError::FactorSourceIDNotFromHash) + .and_then(|id| { + self.mnemonic_with_passphrase_of_device_factor_source_by_id(&id) + }) + } + + /// Tries to load the `MnemonicWithPassphrase` for the main "Babylon" + /// `DeviceFactorSource` from secure storage. + pub fn main_bdfs_mnemonic_with_passphrase( + &self, + ) -> Result { + let profile = &self.profile(); + let bdfs = profile.bdfs(); + self.mnemonic_with_passphrase_of_device_factor_source_by_id(&bdfs.id) + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn main_bdfs_mnemonic_with_passphrase() { + let private = + PrivateHierarchicalDeterministicFactorSource::placeholder(); + let dfs = private.factor_source; + let profile = Profile::placeholder(); + let (wallet, storage) = Wallet::ephemeral(profile.clone()); + let data = + serde_json::to_vec(&private.mnemonic_with_passphrase).unwrap(); + let key = SecureStorageKey::DeviceFactorSourceMnemonic { + factor_source_id: dfs.id.clone(), + }; + storage.save_data(key.clone(), data.clone()).unwrap(); + assert_eq!( + wallet.main_bdfs_mnemonic_with_passphrase().unwrap(), + MnemonicWithPassphrase::placeholder() + ); + assert_eq!( + wallet.mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(&dfs.factor_source_id()).unwrap(), + MnemonicWithPassphrase::placeholder() + ); + } + + #[test] + fn mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id_fail_not_factor_source_id_from_hash( + ) { + let (wallet, _) = Wallet::ephemeral(Profile::placeholder()); + assert_eq!( + wallet.mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(&FactorSourceIDFromAddress::placeholder().into()), + Err(CommonError::FactorSourceIDNotFromHash) + ); + } +} diff --git a/src/wrapped_radix_engine_toolkit/dummy_types.rs b/src/wrapped_radix_engine_toolkit/dummy_types.rs new file mode 100644 index 000000000..04df5f6b4 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/dummy_types.rs @@ -0,0 +1,28 @@ +use crate::prelude::*; + +macro_rules! dummy_sargon { + ($struct_name:ident) => { + #[derive( + Serialize, + Deserialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + uniffi::Record, + )] + pub struct $struct_name {} + }; +} + +dummy_sargon!(PackageAddress); +dummy_sargon!(ComponentAddress); +dummy_sargon!(AccessControllerAddress); +dummy_sargon!(VaultAddress); +dummy_sargon!(ValidatorAddress); +dummy_sargon!(ResourcePoolAddress); + +// Rename and use Decimal type! +dummy_sargon!(RETDecimal); diff --git a/src/wrapped_radix_engine_toolkit/execution_summary.rs b/src/wrapped_radix_engine_toolkit/execution_summary.rs new file mode 100644 index 000000000..c8103ebd0 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/execution_summary.rs @@ -0,0 +1,6 @@ +use crate::prelude::*; + +pub type Blob = BagOfBytes; +pub type Blobs = Vec; + +dummy_sargon!(ExecutionSummary); diff --git a/src/wrapped_radix_engine_toolkit/manifest_summary.rs b/src/wrapped_radix_engine_toolkit/manifest_summary.rs new file mode 100644 index 000000000..9e02ad856 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/manifest_summary.rs @@ -0,0 +1,21 @@ +use crate::prelude::*; + +dummy_sargon!(ManifestSummary); + +impl ManifestSummary { + pub fn addresses_of_accounts_deposited_into(&self) -> Vec { + todo!() + } + + pub fn addresses_of_accounts_withdrawn_from(&self) -> Vec { + todo!() + } + + pub fn addresses_of_accounts_requiring_auth(&self) -> Vec { + todo!() + } + + pub fn addresses_of_personas_requiring_auth(&self) -> Vec { + todo!() + } +} diff --git a/src/wrapped_radix_engine_toolkit/message.rs b/src/wrapped_radix_engine_toolkit/message.rs new file mode 100644 index 000000000..b8e72cee6 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/message.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + EnumAsInner, + Eq, + Hash, + PartialOrd, + Ord, + uniffi::Enum, +)] +pub enum Message { + PlainText { string: String }, +} diff --git a/src/wrapped_radix_engine_toolkit/mod.rs b/src/wrapped_radix_engine_toolkit/mod.rs new file mode 100644 index 000000000..388b17602 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/mod.rs @@ -0,0 +1,23 @@ +#[macro_use] +mod dummy_types; + +mod execution_summary; +mod manifest_summary; +mod message; +mod notarized_transaction; +mod signed_intent; +mod transaction_hash; +mod transaction_header; +mod transaction_intent; +mod transaction_manifest; + +pub use dummy_types::*; +pub use execution_summary::*; +pub use manifest_summary::*; +pub use message::*; +pub use notarized_transaction::*; +pub use signed_intent::*; +pub use transaction_hash::*; +pub use transaction_header::*; +pub use transaction_intent::*; +pub use transaction_manifest::*; diff --git a/src/wrapped_radix_engine_toolkit/notarized_transaction.rs b/src/wrapped_radix_engine_toolkit/notarized_transaction.rs new file mode 100644 index 000000000..0c61ba1ef --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/notarized_transaction.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +dummy_sargon!(NotarizedTransaction); + +// impl NotarizedTransaction { +// pub fn new +/* +public init( + signedIntent: SignedIntent, + notarySignature: SLIP10.Signature + ) throws { + sargon() + } + + public func compile() throws -> Data { + sargon() + } +*/ +// } diff --git a/src/wrapped_radix_engine_toolkit/signed_intent.rs b/src/wrapped_radix_engine_toolkit/signed_intent.rs new file mode 100644 index 000000000..e0c7e6c0f --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/signed_intent.rs @@ -0,0 +1,39 @@ +// use crate::prelude::*; + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct IntentSignature(pub SignatureWithPublicKeyV1); + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct IntentSignatures { +// pub signatures: Vec, +// } + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct SignedIntent { +// pub intent: TransactionIntent, +// pub intent_signatures: IntentSignatures, +// } diff --git a/src/wrapped_radix_engine_toolkit/transaction_hash.rs b/src/wrapped_radix_engine_toolkit/transaction_hash.rs new file mode 100644 index 000000000..82e4c638c --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_hash.rs @@ -0,0 +1,7 @@ +use radix_engine_toolkit::models::transaction_hash::TransactionHash as RETTransactionHash; + +use crate::prelude::*; + +dummy_sargon!(TransactionHash); + +impl TransactionIntent {} diff --git a/src/wrapped_radix_engine_toolkit/transaction_header.rs b/src/wrapped_radix_engine_toolkit/transaction_header.rs new file mode 100644 index 000000000..56d0504b9 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_header.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; + +#[derive( + Debug, Clone, PartialEq, Eq, Hash, derive_more::Display, uniffi::Record, +)] +#[display("{} nonce: {}", network_id, nonce)] +pub struct TransactionHeader { + pub network_id: NetworkID, + pub start_epoch_inclusive: Epoch, + pub end_epoch_exclusive: Epoch, + pub nonce: Nonce, + pub notary_public_key: PublicKey, + pub notary_is_signatory: bool, + pub tip_percentage: u16, +} + +impl TransactionHeader { + /// Creates a new `TransactionHeader` + /// + /// # Panics + /// Panics if `end_epoch_exclusive < start_epoch_inclusive` + pub fn new( + network_id: NetworkID, + start_epoch_inclusive: Epoch, + end_epoch_exclusive: Epoch, + nonce: Nonce, + notary_public_key: PublicKey, + notary_is_signatory: bool, + tip_percentage: u16, + ) -> Self { + assert!( + end_epoch_exclusive >= start_epoch_inclusive, + "End epoch MUST be greater than or equal start epoch." + ); + Self { + network_id, + start_epoch_inclusive, + end_epoch_exclusive, + nonce, + notary_public_key, + notary_is_signatory, + tip_percentage, + } + } +} diff --git a/src/wrapped_radix_engine_toolkit/transaction_intent.rs b/src/wrapped_radix_engine_toolkit/transaction_intent.rs new file mode 100644 index 000000000..82132b8d1 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_intent.rs @@ -0,0 +1,22 @@ +use crate::prelude::*; + +dummy_sargon!(TransactionIntent); + +#[allow(unused_variables)] +impl TransactionIntent { + pub fn new( + header: TransactionHeader, + manifest: TransactionManifest, + message: Message, + ) -> Self { + todo!() + } + + pub fn intent_hash(&self) -> TransactionHash { + todo!() + } + + pub fn compile(&self) -> Result { + todo!() + } +} diff --git a/src/wrapped_radix_engine_toolkit/transaction_manifest.rs b/src/wrapped_radix_engine_toolkit/transaction_manifest.rs new file mode 100644 index 000000000..f040eee3f --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_manifest.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; + +dummy_sargon!(TransactionManifest); + +#[allow(unused_variables)] +impl TransactionManifest { + pub fn new( + instructions_string: String, + network_id: NetworkID, + blobs: Blobs, + ) -> Self { + todo!() + } + + pub fn resource_addresses_to_refresh( + &self, + ) -> Option> { + todo!() + } + + pub fn instructions_string(&self) -> String { + todo!() + } + + pub fn blobs(&self) -> Blobs { + todo!() + } + + pub fn summary(&self, network_id: NetworkID) -> ManifestSummary { + todo!() + } + + pub fn execution_summary( + &self, + network_id: NetworkID, + encoded_receipt: BagOfBytes, // TODO: Replace with TYPE - read from GW. + ) -> ExecutionSummary { + todo!() + } +} diff --git a/tests/uniffi/bindings/test_decimal192.swift b/tests/uniffi/bindings/test_decimal192.swift new file mode 100644 index 000000000..1b06b04bd --- /dev/null +++ b/tests/uniffi/bindings/test_decimal192.swift @@ -0,0 +1,59 @@ +import Foundation +import Sargon + +extension Decimal192 { + public init(_ string: String) throws { + self = try newDecimalFromString(string: string) + } +} + +extension Decimal192: CustomStringConvertible { + public var description: String { + decimalToString(decimal: self) + } +} + +extension Decimal192 { + public static func + (lhs: Self, rhs: Self) -> Self { + decimalAdd(lhs: lhs, rhs: rhs) + } +} +extension Decimal192: ExpressibleByStringLiteral { + public init(stringLiteral string: String) { + try! self.init(string) + } +} +extension Decimal192: ExpressibleByIntegerLiteral { + public init(integerLiteral i64: Int64) { + self = newDecimalFromI64(value: i64) + } +} + +extension Decimal192: ExpressibleByFloatLiteral { + public init(floatLiteral value: Float32) { + self = newDecimalFromF32(value: value) + } +} + +func test() throws { + let one: Decimal192 = 1 + let two: Decimal192 = 2 + let three: Decimal192 = 3 + + assert(one + two == three) + + var a: Decimal192 = + "958947355801916604025588861116008628224.01234" + + var b: Decimal192 = "58947355801916604025588861116008628224.04321" + var c: Decimal192 = "1017894711603833208051177722232017256448.05555" + assert(a + b == c) + + a = 0.000000000000000123 + b = 0.000000000000000321 + c = 0.000000000000000444 + assert(String(describing: a) == "0.000000000000000123") + assert(a + b == c) +} + +try! test() diff --git a/tests/uniffi/main.rs b/tests/uniffi/main.rs index 658f73253..26a2a2e3f 100644 --- a/tests/uniffi/main.rs +++ b/tests/uniffi/main.rs @@ -64,4 +64,8 @@ mod tests { "tests/uniffi/bindings/test_bag_of_bytes.swift", "tests/uniffi/bindings/test_bag_of_bytes.kts" ); + + uniffi::build_foreign_language_testcases!( + "tests/uniffi/bindings/test_decimal192.swift", + ); } From 8203f9a613ee34f59d74251d0e054ca90ceca626 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:27:40 +0100 Subject: [PATCH 2/4] =?UTF-8?q?Make=20sure=20Release=20script=20in=20fact?= =?UTF-8?q?=20commits=20before=20tagging,=20otherwise=20ta=E2=80=A6=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ios/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ios/release.sh b/scripts/ios/release.sh index b94532dac..08822e01b 100755 --- a/scripts/ios/release.sh +++ b/scripts/ios/release.sh @@ -36,9 +36,9 @@ XCFRAME_ZIP_PATH=`echo "$OUTPUT_OF_BUILD" | cut -d ";" -f 2` || exit $? echo "🚢 CHECKSUM: $CHECKSUM" echo "🚢 XCFRAME_ZIP_PATH: $XCFRAME_ZIP_PATH" - +`git commit -m "Release of '$NEXT_TAG' (updated Package.swift with new checksum and path to zip on Github). This commit is not merged into main/develop branch (and need not be)."` `git tag $NEXT_TAG` -echo "🚢 🏷️ 📡 Pushing tag: $(last_tag)" +echo "🚢 🏷️ 📡 Pushing tag: $(NEXT_TAG), but only tag, not commit." `git push origin $NEXT_TAG` # This MUST match whatever you we have declared in `$PROJECT_ROOT/Package.swift` From f54c581ef8ffaf73ed660189c0c24451868d318b Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:04:59 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Fix=20CD=20second=20attempt,=20forgot=20to?= =?UTF-8?q?=20escape=20message,=20also=20should=20not=20run=20=E2=80=A6=20?= =?UTF-8?q?(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ios/release.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/ios/release.sh b/scripts/ios/release.sh index 08822e01b..59946ceec 100755 --- a/scripts/ios/release.sh +++ b/scripts/ios/release.sh @@ -20,7 +20,7 @@ function last_tag() { local out=`git tag --sort=taggerdate | tail -1` echo $out } -echo "🚢 🏷️ Last tag: $(last_tag)" +echo "🚢 🏷️ Last tag: $(last_tag)" # one liner from: https://stackoverflow.com/a/8653732 NEXT_TAG=$(echo $(last_tag) | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}') @@ -36,7 +36,8 @@ XCFRAME_ZIP_PATH=`echo "$OUTPUT_OF_BUILD" | cut -d ";" -f 2` || exit $? echo "🚢 CHECKSUM: $CHECKSUM" echo "🚢 XCFRAME_ZIP_PATH: $XCFRAME_ZIP_PATH" -`git commit -m "Release of '$NEXT_TAG' (updated Package.swift with new checksum and path to zip on Github). This commit is not merged into main/develop branch (and need not be)."` +GIT_COMMIT_CMD="git commit -m \"Release of '$NEXT_TAG' (updated Package.swift with new checksum and path to zip on Github). This commit is not merged into main/develop branch (and need not be).\" --no-verify" +eval $GIT_COMMIT_CMD `git tag $NEXT_TAG` echo "🚢 🏷️ 📡 Pushing tag: $(NEXT_TAG), but only tag, not commit." `git push origin $NEXT_TAG` From dd4eda66c4341c593dc6c55b92fbbc40bc248761 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:42:30 +0100 Subject: [PATCH 4/4] Git add before git commit (#9) --- scripts/ios/release.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/ios/release.sh b/scripts/ios/release.sh index 59946ceec..7b25e9325 100755 --- a/scripts/ios/release.sh +++ b/scripts/ios/release.sh @@ -36,8 +36,15 @@ XCFRAME_ZIP_PATH=`echo "$OUTPUT_OF_BUILD" | cut -d ";" -f 2` || exit $? echo "🚢 CHECKSUM: $CHECKSUM" echo "🚢 XCFRAME_ZIP_PATH: $XCFRAME_ZIP_PATH" -GIT_COMMIT_CMD="git commit -m \"Release of '$NEXT_TAG' (updated Package.swift with new checksum and path to zip on Github). This commit is not merged into main/develop branch (and need not be).\" --no-verify" + +GIT_ADD_CMD="git add Package.swift apple/Sources/UniFFI/Sargon.swift" +echo "🚢 Staging (potentially) changed files with: $GIT_ADD_CMD" +eval $GIT_ADD_CMD + +GIT_COMMIT_CMD="git commit -m \"Release of '$NEXT_TAG' (updated Package.swift with new checksum and path to zip on Github, and maybe apple/Sources/UniFFI/Sargon.swift). This commit is not merged into main/develop branch (and need not be).\" --no-verify" +echo "🚢 Git commiting changes to Package.swift (and maybe Sargon.swift)" eval $GIT_COMMIT_CMD + `git tag $NEXT_TAG` echo "🚢 🏷️ 📡 Pushing tag: $(NEXT_TAG), but only tag, not commit." `git push origin $NEXT_TAG`